【问题标题】:Iterating over list of arrays遍历数组列表
【发布时间】:2015-07-29 07:02:48
【问题描述】:

我的设置如下所示:

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);
}

我对此有 2 个问题:

  1. 我记得在某些讲座中听说递归总是可以被循环替换,但我不能在这种情况下这样做。这个循环版本的外观如何?

  2. 有没有更好的方法来解决这个问题?

【问题讨论】:

  • 您真的应该写出您希望拥有输入数组元素的所有可能组合。我以为这都是关于迭代数组列表!
  • 我相信您可以使用堆栈以某种方式迭代地实现这一点。我这样说是因为这个问题类似于树遍历(即使您没有使用树),并且这些算法往往在这样的fashion 中迭代地实现。话虽如此,您可以通过使用树(特别是Trie)来解决这个问题,它是否是解决它的“更好”方法是有争议的,并且很大程度上取决于上下文......这只是一个您可以采取不同的方法。
  • @SimonEismann 实现这个迭代会比递归复杂得多。
  • 其实很简单。您甚至不需要堆栈或树结构来执行此操作。你稍等一会儿。我先测试我的代码。
  • @Simon Eismann 看看下面我的解决方案。纯粹用几个循环完成。没有堆栈,没有递归。只有一个一维数组。

标签: java arrays recursion


【解决方案1】:

有没有更好的方法来解决这个问题?

更抽象的方法:

  1. 在列表的开头和结尾添加一个标记。
  2. 构造一个格子,其中每个项目(列表中的标记或数组)都是一个级别。
  3. 打印出通过该格子的每条路径。

这是因为您的问题可以建模为通过分布式系统列出所有可能的执行,其中进程总数等于列表中最长数组的长度。

【讨论】:

    【解决方案2】:

    这是没有使用任何stacks/queues/linked list 的解决方案。完全用简单的循环完成。我使用的唯一数据结构是一个一维数组。

        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;           
        }
    }
    

    这个想法是使用一维数组来记住状态。状态用一维数组表示,以确定要打印的数组索引。

    输出:

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

    【讨论】:

    • 你的解决方案的计算复杂度是多少?
    • @Pétur 复杂度为 n,其中 n 是组合的数量。它不做任何不必要的检查,从而实现线性复杂度。
    【解决方案3】:

    这是一种输出数组元素所有组合的非递归方法。它肯定比递归解决方案更复杂。它通过在补充数组中记录最近在列表中的每个数组中输出的数字来工作。

    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
    

    【讨论】:

    • 对于这个问题有很多类似的很好的解决方案,但我选择这个作为“正确”的解决方案,因为我觉得代码真的很干净:-)
    【解决方案4】:
    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]
    

    【讨论】:

      【解决方案5】:

      好吧,如果你维护一个数组、一个列表或任何其他告诉你你在每个数组中的位置的东西,它可以在没有递归的情况下完成。

      假设我们有一个这样的元素列表:

      /**
       * 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);
          }
      

      【讨论】:

        【解决方案6】:

        如果你的意思是遍历一个数组列表,这样的东西会打印出每个数组中的每个值:

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

        【讨论】:

        • 这应该会打印出列表中的所有内容,例如您在帖子中的显示方式。
        • 真的吗?您是否尝试将您的输出与他的输出进行比较?
        猜你喜欢
        • 1970-01-01
        • 2023-03-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-03-08
        • 1970-01-01
        • 1970-01-01
        • 2018-05-06
        相关资源
        最近更新 更多