【问题标题】:Is there a Java equivalent of Python's 'enumerate' function?是否有与 Python 的“枚举”函数等效的 Java?
【发布时间】:2011-11-02 07:03:18
【问题描述】:

在 Python 中,enumerate 函数允许您遍历 (index, value) 对的序列。例如:

>>> numbers = ["zero", "one", "two"]
>>> for i, s in enumerate(numbers):
...     print i, s
... 
0 zero
1 one
2 two

在 Java 中有什么方法可以做到这一点吗?

【问题讨论】:

    标签: java python iterator


    【解决方案1】:

    对于实现List接口的集合,可以调用listIterator()方法获取ListIterator。迭代器(除其他外)有两种方法 - nextIndex(),用于获取索引;和next(),获取值(与其他迭代器一样)。

    所以上面 Python 的 Java 等价物可能是:

    import java.util.ListIterator;  
    import java.util.List;
    
    List<String> numbers = Arrays.asList("zero", "one", "two");
    ListIterator<String> it = numbers.listIterator();
    while (it.hasNext()) {
        System.out.println(it.nextIndex() + " " + it.next());
    }
    

    与 Python 一样,输出:

    0 zero
    1 one
    2 two
    

    【讨论】:

    • 那么it.next() 有副作用吗?在同一个表达式中混合it.nextIndex()it.next() 是否保证安全?
    • 是的,它转到下一个元素。请参阅 download.oracle.com/javase/6/docs/api/java/util/… 了解 ListIterator 的工作原理。
    • 正如@JB Nizet 所说,是的,next() 具有将迭代器推进一个元素的副作用。但是,Java 语言规范保证 + 运算符的操作数是从左到右计算的。见section 15.7
    • 这是一个替代方案enumerate 的工作方式完全不同。 python 的enumerate 将独立于其内部索引状态索引任意序列。它产生一个以 (index, element) 对作为元素的“替代”可迭代序列。它接受一个start 参数,该参数为索引添加偏移量 - 可以在循环中完成,但仍然可以。它与 for-each like 循环一起工作。
    【解决方案2】:

    我发现这与 python 方法最相似。

    用法

    public static void main(String [] args) {
        List<String> strings = Arrays.asList("zero", "one", "two");
        for(EnumeratedItem<String> stringItem : ListUtils.enumerate(strings)) {
            System.out.println(stringItem.index + " " + stringItem.item);
        }
        System.out.println();
        for(EnumeratedItem<String> stringItem : ListUtils.enumerate(strings, 3)) {
            System.out.println(stringItem.index + " " + stringItem.item);
        }
    }
    

    输出

    0 zero
    1 one
    2 two
    
    3 zero
    4 one
    5 two
    

    特点

    • 适用于任何可迭代对象
    • 不创建内存列表副本(适用于大型列表)
    • 每种语法都支持原生
    • 接受可以添加到索引的开始参数

    实施

    import java.util.Iterator;
    
    public class ListUtils {
    
        public static class EnumeratedItem<T> {
            public T item;
            public int index;
    
            private EnumeratedItem(T item, int index) {
                this.item = item;
                this.index = index;
            }
        }
    
        private static class ListEnumerator<T> implements Iterable<EnumeratedItem<T>> {
    
            private Iterable<T> target;
            private int start;
    
            public ListEnumerator(Iterable<T> target, int start) {
                this.target = target;
                this.start = start;
            }
    
            @Override
            public Iterator<EnumeratedItem<T>> iterator() {
                final Iterator<T> targetIterator = target.iterator();
                return new Iterator<EnumeratedItem<T>>() {
    
                    int index = start;
    
                    @Override
                    public boolean hasNext() {
                        return targetIterator.hasNext();
                    }
    
                    @Override
                    public EnumeratedItem<T> next() {
                        EnumeratedItem<T> nextIndexedItem = new EnumeratedItem<T>(targetIterator.next(), index);
                        index++;
                        return nextIndexedItem;
                    }
    
                };
            }
    
        }
    
        public static <T> Iterable<EnumeratedItem<T>> enumerate(Iterable<T> iterable, int start) {
            return new ListEnumerator<T>(iterable, start);
        }
    
        public static <T> Iterable<EnumeratedItem<T>> enumerate(Iterable<T> iterable) {
            return enumerate(iterable, 0);
        }
    
    }
    

    【讨论】:

    • 我只是在考虑实现这种东西。我认为这是标准库的绝佳候选者。
    【解决方案3】:

    严格来说不可以,因为 Python 中的 enumerate() 函数返回的是元组列表,而 Java 中不存在元组。

    但是,如果您感兴趣的只是打印一个索引和一个值,那么您可以按照 Richard Fearn 的建议并在迭代器上使用 nextIndex() 和 next()。

    还要注意 enumerate() 可以使用更通用的 zip() 函数(使用 Python 语法)来定义:

    mylist = list("abcd")
    zip(range(len(mylist)), mylist)
    

    给出 [(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]

    如果您定义自己的 Tuple 类(请参阅Using Pairs or 2-tuples in Java 作为起点),那么您当然可以轻松地用 Java 编写自己的 zip() 函数来使用它(使用链接中定义的 Tuple 类) :

    public static <X,Y> List<Tuple<X,Y>> zip(List<X> list_a, List<Y> list_b) {
        Iterator<X> xiter = list_a.iterator();
        Iterator<Y> yiter = list_b.iterator();
    
        List<Tuple<X,Y>> result = new LinkedList<Tuple<X,Y>>();
    
        while (xiter.hasNext() && yiter.hasNext()) {
            result.add(new Tuple<X,Y>(xiter.next(), yiter.next()));
        }
    
        return result;
    }
    

    一旦有了 zip(),实现 enumerate() 就很简单了。

    编辑:工作缓慢的一天,所以结束它:

    public static <X> List<Tuple<Integer,X>> enumerate (List<X> list_in) {
        List<Integer> nums = new ArrayList<Integer>(list_in.size());
        for (int x = 0; x < list_in.size(); x++) { 
            nums.add(Integer.valueOf(x));
        }
    
        return zip (nums, list_in);
    }
    

    编辑 2:正如该问题的 cmets 所指出的,这并不完全等同。虽然它产生与 Python 的枚举相同的值,但它并没有以与 Python 的枚举相同的生成方式这样做。因此,对于大型馆藏,这种方法可能会让人望而却步。

    【讨论】:

    • 我想从技术上讲,使用初始化为两个输入列表长度的 Math.min 的 ArrayList 将是返回列表的更好选择,但想法是一样的。
    • 但是,这是不准确的:python 的 enumerate 确实 not 返回元组列表。它返回一个“枚举对象”,它是 iterable,因为 enumeratedesignedgenerator
    • ziprange 创建的列表在非常大的列表上内存效率低下。迭代器和 enumerate 一样,只处理当前元素和生成下一个元素的函数。在 python 2.x 中,itertools.izipxrange 更接近于模拟 enumerate
    • @naxa:很公平,从效率的角度来看,它们并不相等,但从最终输出的角度来看,它们是相等的。将用这个更新答案。
    【解决方案4】:

    根据 Python 文档 (here),这是使用 Java 可以获得的最接近的方法,并且不再冗长:

    String[] numbers = {"zero", "one", "two"}
    for (int i = 0; i < numbers.length; i++) // Note that length is a property of an array, not a function (hence the lack of () )
        System.out.println(i + " " + numbers[i]);
    }
    

    如果您需要使用List 类...

    List<String> numbers = Arrays.asList("zero", "one", "two");
    for (int i = 0; i < numbers.size(); i++) {
        System.out.println(i + " " + numbers.get(i));
    }
    

    *注意:如果您需要在遍历列表时修改列表,则需要使用 Iterator 对象,因为它能够在不引发 ConcurrentModificationException 的情况下修改列表。

    【讨论】:

    • 这种方法不适合链表,因为查找时间很慢。
    【解决方案5】:

    简单明了

    public static <T> void enumerate(Iterable<T> iterable, java.util.function.ObjIntConsumer<T> consumer) {
        int i = 0;
        for(T object : iterable) {
            consumer.accept(object, i);
            i++;
        }
    }
    

    示例用法:

    void testEnumerate() {
        List<String> strings = Arrays.asList("foo", "bar", "baz");
        enumerate(strings, (str, i) -> {
            System.out.println(String.format("Index:%d String:%s", i, str));
        });
    }
    

    【讨论】:

      【解决方案6】:

      现在使用 Java 8s Stream API 以及提供 StreamUtils 的小型 ProtonPack 库可以轻松实现。

      第一个示例使用与问题中相同的 for-each 表示法:

      Stream<String> numbers = Arrays.stream("zero one two".split(" "));
      List<Indexed<String>> indexedNumbers = StreamUtils.zipWithIndex(numbers)
                                                        .collect(Collectors.toList());
      for (Indexed<String> indexed : indexedNumbers) {
          System.out.println(indexed.getIndex() + " " + indexed.getValue());
      }
      

      尽管没有像 Python 那样提供惰性求值。 为此,您必须使用forEach() Stream API 方法:

      Stream<String> numbers = Arrays.stream("zero one two".split(" "));
      StreamUtils.zipWithIndex(numbers)
              .forEach(n -> System.out.println(n.getIndex() + " " + n.getValue()));
      

      惰性求值可以通过以下无限流进行验证:

      Stream<Integer> infStream = Stream.iterate(0, i -> i++);
      StreamUtils.zipWithIndex(infStream)
              .limit(196)
              .forEach(n -> System.out.println(n.getIndex() + " " + n.getValue()));
      

      【讨论】:

        【解决方案7】:
        List<String> list = { "foo", "bar", "foobar"};
        int i = 0;
        for (String str : list){
             System.out.println(i++ + str );
        }
        

        【讨论】:

        • 循环结束时缺少 i++。并且初始化列表的语法无效。您必须使用 Arrays.asList(...)
        • @JB Nizet:是的..谢谢。我正在编辑它。我想我可以直接在 println 中使用 i++,因为 i 应该在其值返回后递增
        • 恕我直言,在其中一个语句中包含 i++ 不是一个好的选择,因为如果该语句(有条件地)跳过或执行多次/复制,它可能会导致难以跟踪的错误.最好有一个专门的i++ 线路。
        【解决方案8】:

        没有。也许有一些库可以支持这种功能。但是,如果您求助于标准库,那么您的工作就是数数。

        【讨论】:

        • RichardFearn 的作品完全不同,尽管它可以用于相同的目的。
        【解决方案9】:

        我认为这应该是最类似于python“枚举”的java功能,尽管它相当复杂且效率低下。基本上,只需使用 ListIterator 或 Collector 将列表的索引映射到其元素:

        List<String> list = new LinkedList<>(Arrays.asList("one", "two", "three", "four"));
        Map<Integer, String> enumeration = new Map<>();
        ListIterator iter = list.listIterator();
        while(iter.hasNext){
            map.put(iter.nextIndex(), iter.next());
        }
        

        或使用 lambda 表达式:

        Set<Integer, String> enumeration = IntStream.range(0, list.size()).boxed.collect(Collectors.toMap(index -> index, index -> list.get(index)));
        

        然后您可以将它与增强的 for 循环一起使用:

        for (Map.Entry<Integer, String> entry : enumeration.entrySet){
            System.out.println(entry.getKey() + "\t" + entry.getValue());
        }
        

        【讨论】:

          【解决方案10】:

          通过将泛型与匿名接口相结合,您基本上可以创建一个工厂方法来处理枚举。 Enumerator 回调隐藏了下面迭代器的混乱。

          import java.util.Arrays;
          import java.util.List;
          import java.util.ListIterator;
          
          public class ListUtils2 {
              public static interface Enumerator<T> {
                  void execute(int index, T value);
              };
          
              public static final <T> void enumerate(final List<T> list,
                      final Enumerator<T> enumerator) {
                  for (ListIterator<T> it = list.listIterator(); it.hasNext();) {
                      enumerator.execute(it.nextIndex(), it.next());
                  }
              }
          
              public static final void enumerate(final String[] arr,
                      final Enumerator<String> enumerator) {
                  enumerate(Arrays.asList(arr), enumerator);
              }
          
              public static void main(String[] args) {
                  String[] names = { "John", "Paul", "George", "Ringo" };
          
                  enumerate(names, new Enumerator<String>() {
                      @Override
                      public void execute(int index, String value) {
                          System.out.printf("[%d] %s%n", index, value);
                      }
                  });
              }
          }
          

          结果

          [0] John
          [1] Paul
          [2] George
          [3] Ringo
          

          扩展思路

          映射、归约、过滤器

          我更进一步,基于这个概念创建了 map、reduce 和 filter 函数。

          Google 的 GuavaApache common-collections 依赖项都包含类似的功能。您可以随意查看。

          import java.util.ArrayList;
          import java.util.Arrays;
          import java.util.List;
          import java.util.ListIterator;
          
          public class ListUtils {
              // =========================================================================
              // Enumerate
              // =========================================================================
              public static abstract interface Enumerator<T> {
                  void execute(int index, T value, List<T> list);
              };
          
              public static final <T> void enumerate(final List<T> list,
                      final Enumerator<T> enumerator) {
                  for (ListIterator<T> it = list.listIterator(); it.hasNext();) {
                      enumerator.execute(it.nextIndex(), it.next(), list);
                  }
              }
          
              // =========================================================================
              // Map
              // =========================================================================
              public static interface Transformer<T, U> {
                  U execute(int index, T value, List<T> list);
              };
          
              public static final <T, U> List<U> transform(final List<T> list,
                      final Transformer<T, U> transformer) {
                  List<U> result = new ArrayList<U>();
                  for (ListIterator<T> it = list.listIterator(); it.hasNext();) {
                      result.add(transformer.execute(it.nextIndex(), it.next(), list));
                  }
                  return result;
              }
          
              // =========================================================================
              // Reduce
              // =========================================================================
              public static interface Reducer<T, U> {
                  U execute(int index, T value, U result, List<T> list);
              };
          
              public static final <T, U> U reduce(final List<T> list,
                      final Reducer<T, U> enumerator, U result) {
                  for (ListIterator<T> it = list.listIterator(); it.hasNext();) {
                      result = enumerator.execute(it.nextIndex(), it.next(), result, list);
                  }
                  return result;
              }
          
              // =========================================================================
              // Filter
              // =========================================================================
              public static interface Predicate<T> {
                  boolean execute(int index, T value, List<T> list);
              };
          
              public static final <T> List<T> filter(final List<T> list,
                      final Predicate<T> predicate) {
                  List<T> result = new ArrayList<T>();
                  for (ListIterator<T> it = list.listIterator(); it.hasNext();) {
                      int index = it.nextIndex();
                      T value = it.next();
                      if (predicate.execute(index, value, list)) {
                          result.add(value);
                      }
                  }
                  return result;
              }
          
              // =========================================================================
              // Predefined Methods
              // =========================================================================
              // Enumerate
              public static <T> String printTuples(List<T> list) {
                  StringBuffer buff = new StringBuffer();
          
                  enumerate(list, new Enumerator<T>() {
                      @Override
                      public void execute(int index, T value, List<T> list) {
                          buff.append('(').append(index).append(", ")
                              .append(value).append(')');
                          if (index < list.size() - 1) {
                              buff.append(", ");
                          }
                      }
                  });
          
                  return buff.toString();
              }
          
              // Map
              public static List<String> intToHex(List<Integer> list) {
                  return transform(list, new Transformer<Integer, String>() {
                      @Override
                      public String execute(int index, Integer value, List<Integer> list) {
                          return String.format("0x%02X", value);
                      }
                  });
              }
          
              // Reduce
              public static Integer sum(List<Integer> list) {
                  return reduce(list, new Reducer<Integer, Integer>() {
                      @Override
                      public Integer execute(int index, Integer value, Integer result,
                              List<Integer> list) {
                          return result + value;
                      }
                  }, 0);
              }
          
              // Filter
              public static List<Integer> evenNumbers(List<Integer> list) {
                  return filter(list, new Predicate<Integer>() {
                      @Override
                      public boolean execute(int index, Integer value, List<Integer> list) {
                          return value % 2 == 0;
                      }
                  });
              }
          
              // =========================================================================
              // Driver
              // =========================================================================
              public static void main(String[] args) {
                  List<Integer> numbers = Arrays.asList(8, 6, 7, 5, 3, 0, 9);
          
                  // Enumerate
                  System.out.printf("%-10s: %s%n", "Enumerate", printTuples(numbers));
          
                  // Map
                  System.out.printf("%-10s: %s%n", "Map", intToHex(numbers));
          
                  // Reduce
                  System.out.printf("%-10s: %d%n", "Reduce", sum(numbers));
          
                  // Filter
                  System.out.printf("%-10s: %s%n", "Filter", evenNumbers(numbers));
              }
          }
          

          【讨论】:

            【解决方案11】:

            使用 Java8 Streams 的语法几乎相同

                ArrayList<String> numbers = new ArrayList<String>();
                numbers.add("one");
                numbers.add("two");
                numbers.add("three");
            
                numbers.stream().forEach(num ->
                {
                    System.out.println(numbers.indexOf(num) + " " + num);
                });
            

            【讨论】:

            • 不是真的;这需要在每次迭代时遍历列表以查找当前元素的索引,并且如果列表中的项目重复则将不起作用。
            猜你喜欢
            • 2011-09-23
            • 2014-07-12
            • 2016-06-28
            • 2012-01-19
            • 2010-10-30
            • 2018-10-14
            • 2011-02-09
            • 2013-06-07
            • 2010-12-19
            相关资源
            最近更新 更多