Jak mogę wygenerować listę lub tablicę sekwencyjnych liczb całkowitych w Javie?

129

Czy istnieje krótki i przyjemny sposób generowania a List<Integer>, a może Integer[]lub lub int[]z sekwencyjnymi wartościami od jakiejś startwartości do endwartości?

To znaczy coś krótszego niż, ale równoważne 1 z następującego:

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

Używanie guawy jest w porządku.

Aktualizacja:

Analiza wydajności

Ponieważ to pytanie otrzymało kilka dobrych odpowiedzi, zarówno przy użyciu natywnych bibliotek Java 8, jak i bibliotek innych firm, pomyślałem, że przetestuję wydajność wszystkich rozwiązań.

Pierwszy test polega po prostu na przetestowaniu tworzenia listy 10 elementów [1..10]za pomocą następujących metod:

  • classicArrayList : kod podany powyżej w moim pytaniu (i zasadniczo taki sam jak odpowiedź adarshr).
  • eclipseCollections : kod podany w odpowiedzi Donalda poniżej przy użyciu Eclipse Collections 8.0.
  • guavaRange : kod podany w odpowiedzi daveb poniżej. Technicznie rzecz biorąc, nie tworzy to, List<Integer>ale raczej ContiguousSet<Integer>- ale ponieważ wdraża się Iterable<Integer>w kolejności, działa głównie do moich celów.
  • intStreamRange : kod podany w odpowiedzi Vladimira poniżej, który używa IntStream.rangeClosed()- który został wprowadzony w Javie 8.
  • streamIterate : kod podany w poniższej odpowiedzi Catalina, który również wykorzystuje IntStreamfunkcjonalność wprowadzoną w Javie 8.

Oto wyniki w kilo operacjach na sekundę (wyższe liczby są lepsze), dla wszystkich powyższych z listami o rozmiarze 10:

Przepustowość tworzenia list

... i jeszcze raz dla list o rozmiarze 10000:

wprowadź opis obrazu tutaj

Ten ostatni wykres jest poprawny - rozwiązania inne niż Eclipse i Guava są zbyt wolne, aby uzyskać nawet pojedynczy pasek pikseli! Szybkie rozwiązania są od 10 000 do 20 000 razy szybsze niż pozostałe.

Oczywiście chodzi tutaj o to, że rozwiązania z guawy i zaćmienia w rzeczywistości nie materializują żadnej listy 10 000 elementów - są po prostu opakowaniami o stałym rozmiarze wokół punktów początkowych i końcowych. Każdy element jest tworzony w razie potrzeby podczas iteracji. Ponieważ w rzeczywistości nie wykonujemy iteracji w tym teście, koszt jest odroczony. Wszystkie inne rozwiązania faktycznie materializują pełną listę w pamięci i płacą wysoką cenę w benchmarku tylko do tworzenia.

Zróbmy coś bardziej realistycznego, a także powtórzmy wszystkie liczby całkowite, sumując je. Czyli w przypadku IntStream.rangeClosedwariantu benchmark wygląda następująco:

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

Tutaj obraz bardzo się zmienia, choć wciąż najszybsze są niematerialne rozwiązania. Oto długość = 10:

Lista <Integer> Iteracja (długość = 10)

... i długość = 10000:

Lista <Integer> Iteracja (długość = 10000)

Długa iteracja wielu elementów bardzo wyrównuje sytuację, ale zaćmienie i guawa pozostają ponad dwukrotnie szybsze nawet w teście 10000 elementów.

Więc jeśli naprawdę chcesz List<Integer>, kolekcje zaćmienia wydają się najlepszym wyborem - ale oczywiście, jeśli używasz strumieni w bardziej natywny sposób (np. Zapominając .boxed()i redukując prymitywną domenę), prawdopodobnie skończysz szybciej niż wszystkie te warianty.


1 Być może z wyjątkiem obsługi błędów, np. If end< begin, lub jeśli rozmiar przekracza pewne limity implementacji lub JVM (np. Tablice większe niż 2^31-1.

BeeOnRope
źródło
Dla apache commons, stackoverflow.com/a/5744861/560302
MoveFast

Odpowiedzi:

185

Dzięki Java 8 jest to tak proste, że nie wymaga już nawet oddzielnej metody:

List<Integer> range = IntStream.rangeClosed(start, end)
    .boxed().collect(Collectors.toList());
Vladimir Matveev
źródło
2
Dodałem wyniki wydajności dla tej odpowiedzi powyżej z etykietą intStreamRange .
BeeOnRope
1
Wymaga API 24+
gcantoni
28

Cóż, ten jeden liniowiec może się kwalifikować (używa zakresów guawy )

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

Nie tworzy to List<Integer>, ale ContiguousSetoferuje taką samą funkcjonalność, w szczególności implementację, Iterable<Integer>która umożliwia foreachimplementację w taki sam sposób, jak List<Integer>.

W starszych wersjach (gdzieś przed Guava 14) możesz użyć tego:

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

Oba produkują:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
daveb
źródło
7
Nie użyłbym asList()tam, chyba że naprawdę potrzebujesz List... ContiguousSetwyprodukowany przez asSetjest lekki (potrzebuje tylko zakresu i domeny), ale asList()utworzę listę, która faktycznie przechowuje wszystkie elementy w pamięci (obecnie).
ColinD
1
Zgoda. OP prosił jednak o listę lub tablicę, w przeciwnym razie bym to zostawił
daveb
1
Wierzę, że 18.0 Rangeistnieje, ale nie ma Rangesi oni zlikwidowali tę asSetmetodę. W mojej starszej wersji asSetjest przestarzały i wygląda na to, że go usunęli. Zakresy najwyraźniej mają być używane tylko do ciągłych kolekcji i wymusili to, chociaż uwielbiam to rozwiązanie.
demongolem
API wymaga teraz kodu podobnego do tego: ContiguousSet.create (Range.closed (1, count), DiscreteDomain.integers ()
Ben
Dodałem powyżej wyniki wydajności dla tej odpowiedzi z etykietą guavaRange .
BeeOnRope
11

Następująca jednoliniowa wersja Java 8 wygeneruje [1, 2, 3 ... 10]. Pierwszy argument iteratejest pierwszym numerem w sekwencji, a pierwszym argumentem limitjest ostatnia liczba.

List<Integer> numbers = Stream.iterate(1, n -> n + 1)
                              .limit(10)
                              .collect(Collectors.toList());
Catalin Ciurea
źródło
Dodałem wyniki wydajności dla tej odpowiedzi powyżej z etykietą streamIterate .
BeeOnRope
1
Dla wyjaśnienia, argument limitu nie jest ostatnią liczbą, jest to liczba liczb całkowitych na liście.
neilireson
7

Możesz użyć Intervalklasy z kolekcji Eclipse .

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

IntervalKlasa jest leniwy, więc nie przechowuje wszystkie wartości.

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

Twoja metoda mogłaby zostać zaimplementowana w następujący sposób:

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

Jeśli chcesz uniknąć oznaczania liczb całkowitych jako liczb całkowitych, ale nadal chciałbyś mieć strukturę listy, możesz użyć IntListz IntIntervalkolekcjami Eclipse.

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

IntListma metody sum(), min(), minIfEmpty(), max(), maxIfEmpty(), average()i median()dostępne w interfejsie.

Aktualizacja dla przejrzystości: 27.11.2017

An Intervaljest List<Integer>, ale jest leniwy i niezmienny. Jest to niezwykle przydatne do generowania danych testowych, zwłaszcza jeśli masz do czynienia ze zbiorami. Jeśli chcesz, możesz łatwo skopiować interwał Do List, Setlub Bagw sposób następujący:

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

Jest IntIntervalto, ImmutableIntListco się rozciąga IntList. Posiada również metody konwertera.

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

An Intervali IntIntervalnie mają tej samej equalsumowy.

Aktualizacja kolekcji Eclipse 9.0

Możesz teraz tworzyć pierwotne kolekcje z pierwotnych strumieni. Istnieją withAlli ofAllmetody w zależności od twoich preferencji. Jeśli jesteś ciekawy, wyjaśnię, dlaczego mamy tutaj oba . Te metody istnieją dla zmiennych i niezmiennych list Int / Long / Double, zestawów, worków i stosów.

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

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

Uwaga: jestem promotorem Eclipse Collections

Donald Raab
źródło
Dodałem wyniki wydajności dla tej odpowiedzi powyżej z etykietą eclipseCollections .
BeeOnRope
Schludny. Zaktualizowałem swoją odpowiedź o dodatkową prymitywną wersję, która powinna unikać jakiegokolwiek boksu.
Donald Raab
6

To jest najkrótszy, jaki mogłem uzyskać używając 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;  
}
adarshr
źródło
3
Możesz zgolić jeszcze kilka postaci, zmieniając tę ​​pętlę na for(int i = begin; i <= end; ret.add(i++));:)
vaughandroid
Nie jestem pewien, czy przeniesienie ret.add(i)części do inkrementacji pętli for sprawia, że ​​jest to „krótsze”. Myślę, że zgodnie z tą logiką, gdybym napisał to wszystko w jednym wierszu, byłby krótszy :)
BeeOnRope
@BeeOnRope Tak, zdecydowanie nie najkrótsza, ale na pewno krótsza o dwie linie :) Jak powiedziałem, jest to najbliższe skrócenie tego w Core Java.
adarshr
Dodałem wyniki wydajności dla tej odpowiedzi powyżej z etykietą classicArrayList .
BeeOnRope
3

Możesz użyć zakresów guawy

Możesz uzyskać SortedSetza pomocą

ImmutableSortedSet<Integer> set = Ranges.open(1, 5).asSet(DiscreteDomains.integers());
// set contains [2, 3, 4]
jmruc
źródło
0

To jest najkrótszy, jaki udało mi się znaleźć.

Wersja listy

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

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

    return ret;
}

Wersja tablicowa

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;
}
Kod za
źródło
-2

Ten może działać dla Ciebie ....

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;  
}
Shehzad
źródło
Używanie AtomicInteger jest bardzo ciężkie dla zasobów, około dziesięć razy wolniej w moim teście. Ale jest bezpieczny dla wielowątkowości. koniec <początek niezweryfikowany
cl-r
1
Użycie AtomicInteger nie ma sensu wewnątrz metody. Wszystkie zdania w wywołaniu metody są uruchamiane sekwencyjnie przez wątek, który wywołał metodę, więc nie dostajesz nic z AtomicInteger poza spowolnieniem i irytującymi wywołaniami getAndIncrement ().
Igor Rodriguez,