如何在 Java 中生成顺序整数的列表或数组?

Posted

技术标签:

【中文标题】如何在 Java 中生成顺序整数的列表或数组?【英文标题】:How can I generate a list or array of sequential integers in Java? 【发布时间】:2012-05-01 19:29:06 【问题描述】:

是否有一种简短而有效的方法来生成List<Integer>,或者可能是Integer[]int[],其顺序值从一些start 值到end 值?

也就是说,比1短但等价于以下内容:

void List<Integer> makeSequence(int begin, int end) 
  List<Integer> ret = new ArrayList<>(end - begin + 1);
  for (int i=begin; i<=end; i++) 
    ret.add(i);
  
  return ret;  

使用番石榴就好了。

更新:

性能分析

由于这个问题已经收到了几个很好的答案,无论是使用原生 Java 8 还是第三方库,我想我会测试所有解决方案的性能。

第一个测试简单地测试使用以下方法创建一个包含 10 个元素的列表 [1..10]

classicArrayList:上面在我的问题中给出的代码(与 adarshr 的答案基本相同)。 eclipseCollections:下面Donald's answer 中给出的代码使用Eclipse Collections 8.0。 guavaRange:下面daveb's answer给出的代码。从技术上讲,这不会创建 List&lt;Integer&gt; 而是创建 ContiguousSet&lt;Integer&gt; - 但由于它按顺序实现 Iterable&lt;Integer&gt;,因此它主要适用于我的目的。 intStreamRange:下面Vladimir's answer 中给出的代码,它使用了IntStream.rangeClosed() - 这是Java 8 中引入的。 streamIterate:下面Catalin's answer 中给出的代码,它还使用了Java 8 中引入的IntStream 功能。

以下是每秒千次操作的结果(数字越大越好),对于以上所有大小为 10 的列表:

...对于大小为 10,000 的列表:

最后一张图表是正确的 - 除了 Eclipse 和 Guava 之外的解决方案都太慢了,甚至无法获得单个像素条!快速解决方案比其他解决方案快 10,000 到 20,000

当然,这里发生的事情是 guava 和 eclipse 解决方案实际上并没有具体化任何类型的 10,000 个元素列表 - 它们只是围绕起点和终点的固定大小的包装器。每个元素都是在迭代期间根据需要创建的。由于我们实际上并没有在这个测试中进行迭代,因此成本被推迟了。所有其他解决方案实际上都在内存中实现了完整列表,并在仅创建基准测试中付出了沉重的代价。

让我们做一些更现实的事情,并遍历所有整数,对它们求和。所以在IntStream.rangeClosed 变体的情况下,基准看起来像:

@Benchmark
public int intStreamRange() 
    List<Integer> ret = IntStream.rangeClosed(begin, end).boxed().collect(Collectors.toList());  

    int total = 0;
    for (int i : ret) 
        total += i;
    
    return total;  

这里,图片变化很大,虽然非物化解决方案仍然是最快的。这里的长度=10:

... 并且长度 = 10,000:

对许多元素的长时间迭代使事情变得更加平衡,但是即使在 10,000 个元素的测试中,eclipse 和番石榴的速度仍然是两倍以上。

所以如果你真的想要List&lt;Integer&gt;,eclipse 集合似乎是最好的选择——当然,如果你以更原生的方式使用流(例如,忘记.boxed() 并做一个原始域的减少)你可能会比所有这些变体更快。


1 也许除了错误处理之外,例如,如果end begin,或者如果大小超过某些实现或JVM 限制(例如,数组大于2^31-1

【问题讨论】:

对于 apache commons,***.com/a/5744861/560302 【参考方案1】:

使用 Java 8 非常简单,因此甚至不再需要单独的方法:

List<Integer> range = IntStream.rangeClosed(start, end)
    .boxed().collect(Collectors.toList());

【讨论】:

我为上面的答案添加了性能结果,标签为 intStreamRange 需要 API 24+ 如何将这些数字转换成字符串? @FelixJassler IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList()),例如。【参考方案2】:

嗯,这一个班轮可能符合条件(使用Guava Ranges)

ContiguousSet<Integer> integerList = ContiguousSet.create(Range.closedOpen(0, 10), DiscreteDomain.integers());
System.out.println(integerList);

这不会创建List&lt;Integer&gt;,但ContiguousSet 提供了大致相同的功能,特别是实现Iterable&lt;Integer&gt;,它允许以与List&lt;Integer&gt; 相同的方式实现foreach

在旧版本(Guava 14 之前的某个地方)中,您可以使用这个:

ImmutableList<Integer> integerList = Ranges.closedOpen(0, 10).asSet(DiscreteDomains.integers()).asList();
System.out.println(integerList);

两者都产生:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

【讨论】:

除非你真的需要List,否则我不会在那里使用asList()...asSet 生成的ContiguousSet 是轻量级的(它只需要范围和域),但是asList() 将创建一个列表,该列表实际上将所有元素存储在内存中(当前)。 同意。不过,OP 要求的是 List 或数组,否则我会忽略它 我相信对于 18.0,Range 存在但不存在 Ranges 并且他们已经取消了 asSet 方法。在我的旧版本中,asSet 已被弃用,似乎他们已将其删除。范围显然只用于连续的集合,尽管我确实喜欢这个解决方案,但他们已经强制执行了。 API 现在需要类似这样的代码: ContiguousSet.create(Range.closed(1, count), DiscreteDomain.integers() 我用标签 guavaRange 为上面这个答案添加了性能结果。【参考方案3】:

以下单行 Java 8 版本将生成 [ 1, 2 ,3 ... 10 ]。 iterate 的第一个 arg 是序列中的第一个 nr,limit 的第一个 arg 是最后一个数字。

List<Integer> numbers = Stream.iterate(1, n -> n + 1)
                              .limit(10)
                              .collect(Collectors.toList());

【讨论】:

我为上面的这个答案添加了性能结果,标签为 streamIterate 澄清一下,限制 arg 不是最后一个数字,而是列表中的整数个数。【参考方案4】:

您可以使用Eclipse Collections 中的Interval 类。

List<Integer> range = Interval.oneTo(10);
range.forEach(System.out::print);  // prints 12345678910

Interval 类是惰性的,因此不会存储所有值。

LazyIterable<Integer> range = Interval.oneTo(10);
System.out.println(range.makeString(",")); // prints 1,2,3,4,5,6,7,8,9,10

您的方法可以实现如下:

public List<Integer> makeSequence(int begin, int end) 
    return Interval.fromTo(begin, end);

如果您希望避免将整数装箱为整数,但仍希望使用列表结构,那么您可以使用 Eclipse Collections 中的 IntListIntInterval

public IntList makeSequence(int begin, int end) 
    return IntInterval.fromTo(begin, end);

IntList 在接口上有 sum()min()minIfEmpty()max()maxIfEmpty()average()median() 方法。

为清晰起见更新:2017 年 11 月 27 日

IntervalList&lt;Integer&gt;,但它是惰性且不可变的。它对于生成测试数据非常有用,尤其是在您处理大量集合时。如果需要,您可以轻松地将间隔复制到 ListSetBag,如下所示:

Interval integers = Interval.oneTo(10);
Set<Integer> set = integers.toSet();
List<Integer> list = integers.toList();
Bag<Integer> bag = integers.toBag();

IntInterval 是一个ImmutableIntList,它扩展了IntList。它也有转换器方法。

IntInterval ints = IntInterval.oneTo(10);
IntSet set = ints.toSet();
IntList list = ints.toList();
IntBag bag = ints.toBag();

IntervalIntInterval 没有相同的 equals 合约。

Eclipse Collections 9.0的更新

您现在可以从原始流创建原始集合。根据您的喜好,有withAllofAll 方法。如果你好奇,我会解释为什么我们都有here。这些方法适用于可变和不可变的 Int/Long/Double Lists、Sets、Bags 和 Stacks。

Assert.assertEquals(
        IntInterval.oneTo(10),
        IntLists.mutable.withAll(IntStream.rangeClosed(1, 10)));

Assert.assertEquals(
        IntInterval.oneTo(10),
        IntLists.immutable.withAll(IntStream.rangeClosed(1, 10)));

注意:我是 Eclipse Collections 的提交者

【讨论】:

我为上面的这个答案添加了性能结果,标签为 eclipseCollections 整洁。我用一个额外的原始版本更新了我的答案,应该避免任何拳击。【参考方案5】:

这是我使用 Core Java 能得到的最短时间。

List<Integer> makeSequence(int begin, int end) 
  List<Integer> ret = new ArrayList(end - begin + 1);

  for(int i = begin; i <= end; i++, ret.add(i));

  return ret;  

【讨论】:

您可以通过将循环更改为 for(int i = begin; i &lt;= end; ret.add(i++)); 来减少更多字符:) 我不太确定将 ret.add(i) 部分移动到 for 循环增量中是否会使其“更短”。我想按照这个逻辑,如果我把它全部写在一行上,它会更短:) @BeeOnRope 是的,绝对不是最短的,但肯定短了两行 :) 正如我所说,这是我们可以在 Core Java 中缩短它的最接近的方法。 我为上面的答案添加了性能结果,标签为 classicArrayList【参考方案6】:

你可以使用Guava Ranges

您可以通过使用获得SortedSet

ImmutableSortedSet<Integer> set = Ranges.open(1, 5).asSet(DiscreteDomains.integers());
// set contains [2, 3, 4]

【讨论】:

【参考方案7】:
int[] arr = IntStream.rangeClosed(2, 5).toArray();
System.out.println(Arrays.toString(arr));
// [2, 3, 4, 5]

Integer[] boxedArr = IntStream.rangeClosed(2, 5)
  .boxed().toArray(Integer[]::new);
System.out.println(Arrays.toString(boxedArr));

// Since Java 16
List<Integer> list1 = IntStream.rangeClosed(2, 5)
  .boxed().toList();
System.out.println(list1);

List<Integer> list2 = IntStream.rangeClosed(2, 5)
  .boxed().collect(Collectors.toList());
System.out.println(list2);

List<Integer> list3 = Arrays.asList(boxedArr);
System.out.println(list3);

List<Integer> list4 = new ArrayList<>();
IntStream.rangeClosed(2, 5).forEachOrdered(list4::add);
System.out.println(list4);

【讨论】:

【参考方案8】:

这是我能找到的最短的。

列表版本

public List<Integer> makeSequence(int begin, int end)

    List<Integer> ret = new ArrayList<Integer>(++end - begin);

    for (; begin < end; )
        ret.add(begin++);

    return ret;

数组版本

public int[] makeSequence(int begin, int end)

    if(end < begin)
        return null;

    int[] ret = new int[++end - begin];
    for (int i=0; begin < end; )
        ret[i++] = begin++;
    return ret;

【讨论】:

for (; begin &lt; end; ) 写得更好while (begin &lt; end)【参考方案9】:

这个可能对你有用....

void List<Integer> makeSequence(int begin, int end) 

  AtomicInteger ai=new AtomicInteger(begin);
  List<Integer> ret = new ArrayList(end-begin+1);

  while ( end-->begin) 

    ret.add(ai.getAndIncrement());

  
  return ret;  

【讨论】:

使用 AtomicInteger 对资源来说非常繁重,在我的测试中慢了大约十倍。但它对于多线程是安全的。 结束未验证 在方法内部使用 AtomicInteger 没有意义。方法调用中的所有语句都由调用该方法的线程按顺序运行,因此您从 AtomicInteger 中什么也得不到,但会减慢和烦人的 getAndIncrement() 调用。

以上是关于如何在 Java 中生成顺序整数的列表或数组?的主要内容,如果未能解决你的问题,请参考以下文章

java 在java中生成一定长度的整数数组

如何在 Java 中生成特定范围内的随机整数?

如何在 Java 中生成特定范围内的随机整数?

编译器显示错误消息“从不强制转换的整数中生成指针”(反转数组)。我该如何解决这个问题? [关闭]

如何在数组中生成随机数,加起来达到定义的总数?

循环,从数组中生成带有函数的按钮