Làm cách nào để tạo danh sách hoặc mảng các số nguyên tuần tự trong Java?


130

Có cách nào ngắn gọn và thú vị để tạo một List<Integer>, hoặc có thể là Integer[]hoặc int[], với các giá trị tuần tự từ một số startgiá trị đến mộtend giá trị nào đó không?

Đó là, một cái gì đó ngắn hơn, nhưng tương đương với 1 cái sau:

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

Việc sử dụng ổi cũng được.

Cập nhật:

Phân tích hiệu suất

Vì câu hỏi này đã nhận được một số câu trả lời tốt, cả hai đều sử dụng Java 8 bản địa và các thư viện của bên thứ ba, tôi nghĩ tôi sẽ kiểm tra hiệu suất của tất cả các giải pháp.

Bài kiểm tra đầu tiên chỉ đơn giản là kiểm tra việc tạo danh sách 10 phần tử [1..10]bằng các phương pháp sau:

  • classicArrayList : mã được đưa ra ở trên trong câu hỏi của tôi (và về cơ bản giống với câu trả lời của adarshr).
  • eclipseCollections : mã được đưa ra trong câu trả lời của Donald bên dưới sử dụng Eclipse Collections 8.0.
  • ổiRange : mã được đưa ra trong câu trả lời của daveb bên dưới. Về mặt kỹ thuật, điều này không tạo ra List<Integer>mà là một ContiguousSet<Integer>- nhưng vì nó thực hiện Iterable<Integer>theo thứ tự, nó hầu hết hoạt động cho các mục đích của tôi.
  • intStreamRange : mã được đưa ra trong câu trả lời của Vladimir bên dưới, sử dụngIntStream.rangeClosed() - được giới thiệu trong Java 8.
  • streamIterate : mã được đưa ra trong câu trả lời của Catalin bên dưới, mã này cũng sử dụng IntStreamchức năng được giới thiệu trong Java 8.

Dưới đây là kết quả tính bằng kilo-thao tác trên giây (số càng cao càng tốt), cho tất cả những điều trên với danh sách kích thước 10:

Thông lượng tạo danh sách

... và một lần nữa đối với danh sách có kích thước 10.000:

nhập mô tả hình ảnh ở đây

Biểu đồ cuối cùng đó là chính xác - các giải pháp khác ngoài Eclipse và Guava quá chậm để có được một thanh pixel duy nhất! Các giải pháp nhanh nhanh hơn 10.000 đến 20.000 lần so với các giải pháp còn lại.

Tất nhiên, điều đang xảy ra ở đây là các giải pháp ổi và eclipse không thực sự hiện thực hóa bất kỳ loại danh sách 10.000 phần tử nào - chúng chỉ đơn giản là các trình bao bọc có kích thước cố định xung quanh điểm đầu và điểm cuối. Mỗi phần tử được tạo khi cần thiết trong quá trình lặp. Vì chúng tôi không thực sự lặp lại trong thử nghiệm này, nên chi phí được hoãn lại. Tất cả các giải pháp khác thực sự hiện thực hóa danh sách đầy đủ trong bộ nhớ và phải trả giá đắt trong một tiêu chuẩn chỉ dành cho sáng tạo.

Hãy làm điều gì đó thực tế hơn một chút và cũng lặp lại trên tất cả các số nguyên, tính tổng chúng. Vì vậy, trong trường hợp của IntStream.rangeClosedbiến thể, điểm chuẩn trông giống như:

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

Ở đây, các bức tranh thay đổi rất nhiều, mặc dù các giải pháp không hiện thực hóa vẫn là nhanh nhất. Đây là độ dài = 10:

Lặp lại danh sách <Integer> (length = 10)

... và length = 10.000:

Lặp lại danh sách <Integer> (length = 10.000)

Quá trình lặp lại lâu trên nhiều phần tử làm mọi thứ trở nên hoàn thiện hơn, nhưng nhật thực và ổi vẫn nhanh hơn gấp đôi ngay cả trong bài kiểm tra 10.000 phần tử.

Vì vậy, nếu bạn thực sự muốn List<Integer>, các bộ sưu tập nhật thực có vẻ là lựa chọn tốt nhất - nhưng tất nhiên nếu bạn sử dụng các luồng theo cách nguyên bản hơn (ví dụ: quên .boxed()và giảm miền nguyên thủy) có thể bạn sẽ kết thúc nhanh hơn tất cả những các biến thể.


1 Có lẽ ngoại trừ việc xử lý lỗi, ví dụ: nếu end< begin, hoặc nếu kích thước vượt quá một số giới hạn triển khai hoặc JVM (ví dụ: mảng lớn hơn 2^31-1.


Đối với
dấu phẩy của

Câu trả lời:


185

Với Java 8, nó rất đơn giản nên nó thậm chí không cần phương thức riêng biệt nữa:

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

2
Tôi đã thêm kết quả hiệu suất cho câu trả lời này ở trên với nhãn intStreamRange .
BeeOnRope

Yêu cầu API 24+
gcantoni Ngày

28

Chà, một lớp lót này có thể đủ điều kiện (sử dụng Dãy Ổi )

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

Điều này không tạo ra List<Integer>, nhưng ContiguousSetcung cấp nhiều chức năng tương tự, cụ thể là triển khai Iterable<Integer>cho phép foreachtriển khai theo cách tương tự nhưList<Integer> .

Trong các phiên bản cũ hơn (đâu đó trước Guava 14), bạn có thể sử dụng điều này:

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

Cả hai đều sản xuất:

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

7
Tôi sẽ không sử dụng asList()ở đó trừ khi bạn thực sự cần một List... ContiguousSetsản phẩm được sản xuất asSetnhẹ (nó chỉ cần phạm vi và miền), nhưng asList()sẽ tạo một danh sách thực sự lưu trữ tất cả các phần tử trong bộ nhớ (hiện tại).
ColinD

1
Đã đồng ý. OP đã được yêu cầu cho một danh sách hoặc mảng tuy nhiên, nếu không tôi sẽ rời nó ra
daveb

1
Tôi tin rằng đối với 18.0, Rangetồn tại nhưng không Rangesvà họ đã loại bỏ asSetphương pháp này. Trong phiên bản cũ hơn của tôi, asSetkhông được dùng nữa và có vẻ như họ đã xóa nó. Phạm vi dường như chỉ được sử dụng cho các bộ sưu tập liền kề và họ đã thực thi nó mặc dù tôi rất thích giải pháp này.
demongolem

API hiện yêu cầu mã tương tự như sau: ContiguousSet.create (Range.closed (1, đếm), DiscreteDomain.integers ()
Ben

Tôi đã thêm kết quả hiệu suất cho câu trả lời này ở trên với nhãn ổiRange .
BeeOnRope

11

Phiên bản Java 8 một lớp sau sẽ tạo [1, 2, 3 ... 10]. Đối số đầu tiên iteratelà nr đầu tiên trong dãy, và đối số đầu tiên limitlà số cuối cùng.

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

Tôi đã thêm kết quả hiệu suất cho câu trả lời này ở trên với nhãn streamIterate .
BeeOnRope

1
Như một điểm cần làm rõ, đối số giới hạn không phải là số cuối cùng, mà là số Số nguyên trong danh sách.
neilireson

7

Bạn có thể sử dụng Intervallớp từ Bộ sưu tập Eclipse .

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

Các Intervallớp học là lười biếng, vì vậy không lưu trữ tất cả các giá trị.

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

Phương pháp của bạn sẽ có thể được triển khai như sau:

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

Nếu bạn muốn tránh các int quyền anh dưới dạng Số nguyên, nhưng kết quả là vẫn muốn có cấu trúc danh sách, thì bạn có thể sử dụng IntListvới IntIntervaltừ Bộ sưu tập Eclipse.

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

IntListcó phương pháp sum(), min(), minIfEmpty(), max(), maxIfEmpty(), average()median() có sẵn trên giao diện.

Cập nhật cho rõ ràng: 27/11/2017

An Intervallà a List<Integer>, nhưng lười và bất di bất dịch. Nó cực kỳ hữu ích để tạo dữ liệu thử nghiệm, đặc biệt nếu bạn xử lý nhiều với các bộ sưu tập. Nếu bạn muốn, bạn có thể dễ dàng sao chép một khoảng thời gian để một List, Sethay Bagnhư sau:

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

An IntIntervallà một ImmutableIntListmở rộng IntList. Nó cũng có các phương thức chuyển đổi.

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

An Intervalvà an IntIntervalkhông có cùng một equalshợp đồng.

Cập nhật cho Bộ sưu tập Eclipse 9.0

Bây giờ bạn có thể tạo các bộ sưu tập nguyên thủy từ các dòng nguyên thủy. Có withAllofAllphương pháp tùy thuộc vào sở thích của bạn. Nếu bạn tò mò, tôi giải thích tại sao chúng ta có cả hai ở đây . Các phương thức này tồn tại cho Danh sách Int / Dài / Đôi, Bộ, Túi và Ngăn xếp có thể thay đổi và bất biến.

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

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

Lưu ý: Tôi là người cam kết cho Bộ sưu tập Eclipse


Tôi đã thêm kết quả hiệu suất cho câu trả lời này ở trên với nhãn eclipseCollections .
BeeOnRope

Khéo léo. Tôi đã cập nhật câu trả lời của mình bằng một phiên bản nguyên thủy bổ sung để tránh mọi trận quyền anh.
Donald Raab

6

Đây là bản ngắn nhất mà tôi có thể nhận được khi sử dụng 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;  
}

3
Bạn có thể cạo ký tự một vài chi tiết bằng cách thay đổi đó vòng lặp để for(int i = begin; i <= end; ret.add(i++));:)
vaughandroid

Tôi không thực sự chắc chắn rằng việc di chuyển ret.add(i)phần này vào khoảng tăng vòng lặp for làm cho điều này "ngắn hơn". Tôi đoán bằng logic rằng nếu tôi đã viết nó trên cùng một dòng nó sẽ ngắn hơn :)
BeeOnRope

@BeeOnRope Có, chắc chắn không phải là ngắn nhất, nhưng chắc chắn là ngắn hơn hai dòng :) Như tôi đã nói, đây là cách gần nhất chúng ta có thể rút ngắn nó trong Core Java.
adarshr

Tôi đã thêm kết quả hiệu suất cho câu trả lời này ở trên với nhãn classicArrayList .
BeeOnRope

3

Bạn đã có thể sử dụng Dãy Ổi

Bạn có thể nhận được SortedSetbằng cách sử dụng

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

0

Đây là ngắn nhất tôi có thể tìm thấy.

Phiên bản danh sách

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

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

    return ret;
}

Phiên bản mảng

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

-2

Cái này có thể phù hợp với bạn ....

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

Sử dụng AtomicInteger rất nặng đối với các nguồn tải lại, chậm hơn khoảng mười lần trong thử nghiệm của tôi. Nhưng nó an toàn cho đa luồng. cuối <bắt đầu không xác nhận
cl-r

1
Việc sử dụng AtomicInteger không có ý nghĩa bên trong một phương thức. Tất cả các câu trong một cuộc gọi phương thức được chạy tuần tự bởi chuỗi gọi phương thức, vì vậy bạn không nhận được gì từ AtomicInteger ngoài các lời gọi getAndIncrement () làm chậm và khó chịu.
Igor Rodriguez
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.