【问题标题】:get the index of the min size list in an arrayList using java stream使用java流获取arrayList中最小大小列表的索引
【发布时间】:2018-11-28 17:44:46
【问题描述】:

我有一个整数列表,例如 list1=(1,2,3) 和 list2 = (0,1)。

我的列表包含 list1 和 list2。它可以包含更多,但对于示例,我只列出了两个列表。

问题是使用java流获取最小大小的列表的索引。

这是我的程序,它只使用 for 循环方法。

import java.util.ArrayList;

public class Example {

     public static void main( String[] args ) {


         ArrayList<Integer> list1 = new ArrayList<>();
         list1.add(1);list1.add(2);list1.add(3);

         ArrayList<Integer> list2 = new ArrayList<>();
         list2.add(0);list2.add(1);

         ArrayList<ArrayList<Integer>> listOLists = new ArrayList<>();
         listOLists.add(list1);
         listOLists.add(list2);
         printTheIndexOfTheListWithTheMinSize(listOLists);
     }

    private static void printTheIndexOfTheListWithTheMinSize( ArrayList<ArrayList<Integer>> listOLists ) {
        int minSize  = listOLists.get(0).size();
        int minIndex = 0;
        int i=0;
        for ( ArrayList<Integer> list:  listOLists ) {

            if (list.size()<minSize)
            {
                minSize = list.size();
                minIndex=i;
            }
            i++;

        }
        System.out.println(minIndex);
    }
}

您能否给我一个提示,如何使用 Java 流 API 做到这一点。

请注意,我在繁重的计算中多次调用此方法,因此 awnser 应考虑到这一点。

【问题讨论】:

  • list2 被声明和初始化之后,没有任何元素被添加到其中。在下一行中,01 被添加到 list1。错字?

标签: java collections java-stream


【解决方案1】:

不是很优雅,因为它需要装箱和拆箱,但是...

Optional<Integer> minIndex = 
    IntStream.range(0, list.size())
             .boxed()
             .min(Comparator.comparingInt(i -> list.get(i).size()));

【讨论】:

  • 这个装箱开销比其他答案的indexOf操作开销要小得多……
  • 或者,不装箱IntStream.range(0, list.size()) .reduce((a, b) -&gt; list.get(a).size() &lt;= list.get(b).size()? a: b)
【解决方案2】:

远离使用indexOf 的解决方案。虽然他们可能允许编写相当短的代码,但此 indexOf 操作具有基于内容的线性搜索操作,在列表元素上调用 equals 直到找到匹配项。

虽然这可能看起来微不足道,因为所有子列表的大小都不同,除了匹配元素,Java 8 的大多数 List 实现不使用大小来简化比较。

为了说明问题,

使用以下帮助类

class Counter {
  int count;
  @Override
  public boolean equals(Object obj) {
    count++;
    return super.equals(obj);
  }
  @Override
  public int hashCode() {
    return super.hashCode();
  }
  @Override
  public String toString() {
    return "equals invoked "+count+" times";
  }
}

Counter c = new Counter();
List<List<Counter>> list = Arrays.asList(
  new ArrayList<>(Collections.nCopies(10, c)),
  new ArrayList<>(Collections.nCopies(15, c)),
  new ArrayList<>(Collections.nCopies(7, c)),
  new ArrayList<>(Collections.nCopies(10, c))
  );

Comparator<List<?>> cmp = Comparator.comparingInt(List::size);

System.out.println("using IntStream.range(0, list.size()).boxed()\r\n" + 
  "               .min(Comparator.comparing(list::get, cmp))");
int minIndex = 
  IntStream.range(0, list.size()).boxed()
           .min(Comparator.comparing(list::get, cmp)).orElse(-1);
System.out.println("result "+minIndex+", "+c);
c.count = 0;

System.out.println("\nusing list.indexOf(Collections.min(list, cmp))");
minIndex = list.indexOf(Collections.min(list, cmp));
System.out.println("result "+minIndex+", "+c);
c.count = 0;

System.out.println("\nusing list.indexOf(list.stream().min(cmp).get())");
minIndex = list.indexOf(list.stream().min(cmp).get());
System.out.println("result "+minIndex+", "+c);

它会打印出来

using IntStream.range(0, list.size()).boxed()
               .min(Comparator.comparing(list::get, cmp))
result 2, equals invoked 0 times

using list.indexOf(Collections.min(list, cmp))
result 2, equals invoked 14 times

using list.indexOf(list.stream().min(cmp).get())
result 2, equals invoked 14 times

在 Java 8 中,表明对任何包含的元素调用 equals 是不必要的操作(参见第一个变体,派生自 this answer),但对其他变体执行了多次。现在想象一下,如果我们使用更大的列表和/或更多的列表,并且有一个元素类型进行了相当昂贵的相等性测试,会发生什么。

请注意,对于 ArrayList,这已在 JDK 11 中解决,但仍有列表实现,如 Collections.nCopiesArrays.asList 返回的那些不会短路,因此通常最好不要做一个完全过时的基于内容的线性搜索操作。

【讨论】:

  • 一如既往的好答案。 equals 实现仅在 JDK11 中添加到 ArrayList 类中。但我不太了解短路部分。 AbstractList 中的equals (或者换句话说等于 Java 11 之前的 ArrayList)中的 equals 不是也很短吗?
  • 我想我明白了。当另一个对象是 ArrayList 时,它们具有不同/特殊的方法 (equalsArrayList),如果大小不相等,它们甚至不会遍历两个列表。我说的对吗?
  • @user7 完全一样,区别在于大小检查。 AbstractList.equals 不检查大小;由于它不知道实际的实现逻辑,确定大小可能是一项昂贵的操作,所以它不使用它。因此,所有具有廉价大小操作的列表实现(几乎全部)都应该插入这样的检查,因为不同的大小会排除相等性。
【解决方案3】:

一种可能的方法是将indexOfCollections.mincomparator 一起使用:

int minIndex = listOLists.indexOf(Collections.min(listOLists, 
                                   Comparator.comparingInt(List::size))); 

【讨论】:

    【解决方案4】:

    这是一种解决方法:

    int index = listOLists.indexOf(listOLists.stream()
                    .min(Comparator.comparingInt(List::size))
                    .orElseGet(ArrayList::new));
    

    或者如果您想避免在源为空时创建ArrayList,那么您可以这样做:

    int index = listOLists.isEmpty() ? -1 : listOLists.indexOf(listOLists.stream()
                      .min(Comparator.comparingInt(List::size)).get());
    

    【讨论】:

    • 当传递一个空列表时,这将返回 -1,这比 OP 的当前代码更好,后者会因 NPE 而失败
    • 这个解决方案在性能问题上是否比 for 循环方式更好
    • @zakzak 你需要测量它(注意:流有一些开销)
    • @zakzak 它可能比循环等价物差得多,因为这个解决方案(完全像the accept answer)首先搜索最小元素,然后通过@987654325 对其索引执行额外的昂贵线性搜索@ 涉及比较列表的所有元素(您会感到惊讶,当大小不同或与自身比较时,有多少 List 实现不会捷径)。这种内容比较比按大小比较列表要贵几个数量级。
    【解决方案5】:

    创建索引/大小数组并按大小查找最小值的替代方法:

    IntStream.range(0, listOLists.size())
             .mapToObj(i -> new int[] { i, listOLists.get(i).size() })
             .min(Comparator.comparingInt(arr -> arr[1]))
             .map(arr -> arr[0])
             .ifPresent(System.out::println);
    

    这将打印listOLists中的最小列表索引

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-10-28
      • 1970-01-01
      • 1970-01-01
      • 2011-01-29
      • 2017-03-03
      • 2019-06-06
      • 1970-01-01
      • 2014-07-09
      相关资源
      最近更新 更多