簡體   English   中英

迭代數組列表

[英]Iterating over list of arrays

我有一個看起來像這樣的設置:

List<int[]> list = new LinkedList<int[]>();
list.add(new int[] {1, 3, 4});
list.add(new int[] {4, 5});
list.add(new int[] {1, 4, 6});

編寫代碼時我不知道數組的大小。 我試圖遍歷整個設置以生成所有可能的組合:

141
144
146
151
154
156
341
...

我目前正在使用遞歸來實現這個目的:

public static void recursive(List<int[]> list) {
    recursive(list, 0, "");
}

private static void recursive(List<int[]> list, int counter, String string)  {
    if (counter == list.size())
        System.out.println(string);
    else
        for (int i: list.get(counter))
            recursive(list, counter + 1, string + i);
}

我有兩個問題:

  1. 我記得在一些講座中聽到遞歸總是可以用循環代替,但我不能為這種情況做這件事。 這個循環版本怎么樣?

  2. 有沒有更好的方法來解決這個問題?

這是一個輸出所有數組元素組合的非遞歸方法。 它絕對比遞歸解決方案更復雜。 它的工作原理是將記錄保存在一個補充數組中,該數組最近在列表中的每個數組中輸出了該數字。

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class Iter {

    public static void main(String[] args) {
        List<int[]> list = new LinkedList<int[]>();
        list.add(new int[] { 1, 3, 4 });
        list.add(new int[] { 4, 5 });
        list.add(new int[] { 1, 4, 6 });

        iter(list);
    }

    private static void iter(List<int[]> list) {
        int[] index = new int[list.size()];
        Arrays.fill(index, 0);
        boolean done = false;

        do {
            // Output digits for this row
            for (int i = 0; i < list.size(); i++) {
                System.out.print(list.get(i)[index[i]]);
            }
            System.out.println();

            // Rollover digits, starting from last
            for (int j = list.size() - 1; j >= 0; j--) {
                index[j] = (index[j] + 1) % list.get(j).length;
                if (index[j] > 0) break;
                if (j == 0) done = true;
            }
        } while (!done);
    }

}

輸出:

141
144
146
151
154
156
341
344
346
351
354
356
441
444
446
451
454
456
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

public class Test {
    public static <T> void combinations( final List<T[]> listOfArrays ){
        // Can't iterate a vanilla array so this is just a container for the
        // input converted to something Iterable.
        final ArrayList<List<T>> listOfIterables = new ArrayList<>();

        // Stack containing iterators indicating the current position within
        // the combination.
        final Stack<Iterator<T>> iterators = new Stack<>();

        // The current combination to output.
        final LinkedList<T> values = new LinkedList<>();

        final int len = listOfArrays.size();

        // Initialise the previous lists.
        for ( final T[] ts : listOfArrays ) {
            final List<T> l = Arrays.asList( ts );
            final Iterator<T> i = l.iterator();
            listOfIterables.add( l );
            iterators.push( i );
            values.addLast( i.next() );
        }

        while( true ){
            System.out.println( values );
            // Pop iterators that have finished and their corresponsing values.
            int i = len;
            while ( !iterators.isEmpty() && !iterators.peek().hasNext() ){
                iterators.pop();
                values.removeLast();
                i--;
            }
            // If all iterators are finished then we're done.
            if ( iterators.isEmpty() )
                return;
            // Increment to the next value in the combination.
            values.removeLast();
            values.add( iterators.peek().next() );
            // If iteraters were finished then replace them in the stack with
            // refreshed iterators.
            for ( ; i < len; i++ ){
                final Iterator<T> iterator = listOfIterables.get( i ).iterator();
                iterators.push( iterator );
                values.addLast( iterator.next() );
            }
        }
    }

    public static void main( String[] args ){
        List<Integer[]> list = new LinkedList<>();
        list.add( new Integer[]{ 1, 3, 4 } );
        list.add( new Integer[]{ 4, 5 } );
        list.add( new Integer[]{ 1, 4, 6 } );

        combinations( list );
    }
}

輸出

[1, 4, 1]
[1, 4, 4]
[1, 4, 6]
[1, 5, 1]
[1, 5, 4]
[1, 5, 6]
[3, 4, 1]
[3, 4, 4]
[3, 4, 6]
[3, 5, 1]
[3, 5, 4]
[3, 5, 6]
[4, 4, 1]
[4, 4, 4]
[4, 4, 6]
[4, 5, 1]
[4, 5, 4]
[4, 5, 6]

好吧,它可以在沒有遞歸的情況下完成,如果你維護一個數組,一個列表或其他任何告訴你在每個數組中的位置。

假設我們保留了這樣的元素列表:

/**
 * Class to contain an index and a length. 
 */
private static class Pair {
    private int currIndex = 0;
    int length;

    /**
     * Constructor - we pass the length of the respective array.
     * This does not change during the lifetime of this program.
     * @param length The length of the respective array.
     */
    public Pair( int length ) {
        this.length = length;
    }

    /**
     * Increment the index by one. If we reach the length, start
     * from zero, and indicate that there is carry. That is, that
     * the next element will need to be updated.
     * @return True if next index down needs to be updated.
     */
    public boolean updateAndCheckCarry() {
        currIndex ++;
        if ( currIndex >= length ) {
            currIndex = 0;
            return true;
        }
        return false;
    }

    /**
     * Getter for the index itself
     * @return The current index.
     */
    public int getIndex() {
        return currIndex;
    }
}

我們的想法是通過每個數組,比如{4, 5}數組。 我們從這四個開始,因為我們將通過循環,我們將更新到五個。 但是上面的元素發生了變化,我們需要再次進入四個元素。 這門課幫我們做到了。

所以我們准備了我們的索引列表:

/**
 * Prepare an index list, which for each element of the original list,
 * will contain a current index and the limit. This allows us to keep
 * track of which element we are in in every array.
 * 
 * @param listToIndex
 * @return The index list
 */
public static LinkedList<Pair> prepareIndexList(List<int[]> listToIndex) {
    LinkedList<Pair> result = new LinkedList<>();

    for ( int[] element : listToIndex ) {
        Pair item = new Pair(element.length);
        result.add(item);
    }
    return result;
}

這很簡單 - 我們只需瀏覽我們的列表並收集長度,以便我們以后能夠知道何時將每個索引清零。

在每次迭代中,我們應該遍歷列表並在每個數組的當前索引中打印數字。 因此,如果我們有2的指數為第一陣列,1第二次和0的最后,我們將收集451從你的榜樣。

/**
 * Get the current value to print from the list. That is, go through the
 * list and collect the appropriate element from each array, into a string.
 * 
 * @param valuesList The list of integer arrays to go through
 * @param indexList  The list of current indices
 * @return String representing the collected current value.
 */
public static String getNextValue(List<int[]> valuesList, List<Pair> indexList) {
    StringBuilder sb = new StringBuilder(valuesList.size());
    Iterator<Pair> indexIter = indexList.iterator();
    for ( int[] element : valuesList ) {
        int index = indexIter.next().getIndex();
        sb.append(element[index]);
    }
    return sb.toString();
}

現在,這個解決方案真正的“肉”是索引的更新。 這就像在數字中加1一樣。 想象一下,你有1958的數字,你加1。 它變成1959 現在再次添加1。 所以, 9變為0 ,你需要攜帶1到5.你現在有1960 保持這種狀態,你將升到1999 此時,你加1,將9加零,向左移動,然后將其歸零,然后向左移動,然后將其歸零,然后向左移動,到達2000

以同樣的方式 - 從右邊開始,當我們需要攜帶1時,我們也會更新我們的索引列表:

/**
 * Update the index list. Starting from the end and going backwards, we
 * increment each index. Each index is zeroed if it gets past the respective
 * array size, and also returns true to indicate that the next level needs
 * to be updated as well.
 * 
 * @param indexList The list of indices to be updated
 * @return true if the updates bubbled all the way to the first element,
 *         and it, too, was zeroed. This means we have completed printing
 *         the tree.
 */
public static boolean updateIndexList(LinkedList<Pair> indexList) {
    Iterator<Pair> iter = indexList.descendingIterator();
    boolean hasCarry = true;

    while ( iter.hasNext() && hasCarry ) {
        hasCarry =  iter.next().updateAndCheckCarry();
    }

    return hasCarry;
}

如果我們從最左邊的索引“攜帶” - 屬於我們原始列表的頭部的索引 - 這意味着我們已經完成了程序,因為我們已經遍歷了第一個數組中的所有元素。 發生這種情況時,上述方法返回true。

現在我們只需要調用我們的方法:

    LinkedList indexList = prepareIndexList(list);
    boolean completed = false;

    while ( ! completed ) {
        System.out.println(getNextValue( list, indexList  ));
        completed = updateIndexList(indexList);
    }

這是沒有使用任何stacks / queues / linked list的解決方案。 純粹用簡單的循環完成。 我使用的唯一數據結構是一個1D陣列。

    int[] state = new int[list.size()];
    int incIdx = list.size()-1;     //increment index

    while(true){                
        for(int x=0; x<list.size(); x++)
            System.out.print(list.get(x)[state[x]);     
        System.out.println();                       
        state[list.size()-1]++;         //last one always increase

        while(state[incIdx] == list.get(incIdx).length){      //replaces back tracking      
            state[incIdx] = 0;
            incIdx--;               
                if(incIdx < 0) break; //solution found, exit loop
            state[incIdx]++;    
        }
        if(incIdx < 0) break; //solution found, exit loop
        incIdx = list.size()-1;
        if(state[list.size()-1] == list.get(list.size()-1).length)
            state[list.size()-1] = 0;           
    }
}

我們的想法是使用一維數組來記住狀態。 用1D數組表示的狀態,以確定要打印的數組索引。

OUTPUT:

141
144
146
151
154
156
341
344
346
351
354
356
441
444
446
451
454
456

有沒有更好的方法來解決這個問題?

一種更抽象的方法:

  1. 在列表的開頭和結尾添加令牌。
  2. 構造一個格子,其中每個項目(列表中的標記或數組)都是一個級別。
  3. 打印出通過該格子的每條路徑。

這可以解決您的問題,可以通過分布式系統列出每個可能的執行,其中進程總數等於列表中找到的最長數組的長度。

如果你的意思是迭代一個數組列表,那么這將打印出每個數組中的每個值:

for(int=0; i<list.size(); i++){
      for(int j=0; j<list.get(i).length; j++){
          System.out.println(j);
       }
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM