Come posso generare un elenco o un array di numeri interi sequenziali in Java?


130

C'è un modo breve e dolce per generare un List<Integer>, o forse un Integer[]o int[], con valori sequenziali da un startvalore a un endvalore?

Cioè, qualcosa di più breve di, ma equivalente a 1, quanto segue:

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

L'uso della guava va bene.

Aggiornare:

Analisi di performance

Poiché questa domanda ha ricevuto diverse buone risposte, sia utilizzando Java 8 nativo che librerie di terze parti, ho pensato di testare le prestazioni di tutte le soluzioni.

Il primo test verifica semplicemente la creazione di un elenco di 10 elementi [1..10]utilizzando i seguenti metodi:

  • classicArrayList : il codice indicato sopra nella mia domanda (ed essenzialmente lo stesso della risposta di adarshr).
  • eclipseCollections : il codice fornito nella risposta di Donald di seguito utilizzando Eclipse Collections 8.0.
  • guavaRange : il codice fornito nella risposta di daveb di seguito. Tecnicamente, questo non crea un List<Integer>ma piuttosto un ContiguousSet<Integer>- ma poiché implementa Iterable<Integer>in ordine, funziona principalmente per i miei scopi.
  • intStreamRange : il codice fornito nella risposta di Vladimir di seguito, che utilizza IntStream.rangeClosed()- che è stato introdotto in Java 8.
  • streamIterate : il codice fornito nella risposta di Catalin di seguito che utilizza anche le IntStreamfunzionalità introdotte in Java 8.

Ecco i risultati in chilogrammi al secondo (numeri più alti sono migliori), per tutto quanto sopra con elenchi di dimensione 10:

Velocità effettiva di creazione di elenchi

... e ancora per elenchi di dimensioni 10.000:

inserisci qui la descrizione dell'immagine

Quest'ultimo grafico è corretto: le soluzioni diverse da Eclipse e Guava sono troppo lente per ottenere anche una singola barra di pixel! Le soluzioni rapide sono da 10.000 a 20.000 volte più veloci delle altre.

Quello che sta succedendo qui, ovviamente, è che le soluzioni guava ed eclipse in realtà non materializzano alcun tipo di elenco di 10.000 elementi: sono semplicemente wrapper di dimensioni fisse attorno all'inizio e agli endpoint. Ogni elemento viene creato secondo necessità durante l'iterazione. Poiché in realtà non iteriamo in questo test, il costo viene differito. Tutte le altre soluzioni materializzano effettivamente l'elenco completo in memoria e pagano un prezzo elevato in un benchmark di sola creazione.

Facciamo qualcosa di un po 'più realistico e ripetiamo anche tutti i numeri interi, sommandoli. Quindi, nel caso della IntStream.rangeClosedvariante, il benchmark assomiglia a:

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

Qui le immagini cambiano molto, anche se le soluzioni non materializzanti sono comunque le più veloci. Ecco la lunghezza = 10:

Elenco <Integer> Iterazione (lunghezza = 10)

... e lunghezza = 10.000:

Elenco <Integer> Iterazione (lunghezza = 10.000)

La lunga iterazione su molti elementi uniforma le cose molto, ma eclipse e guava rimangono più del doppio più veloci anche nel test di 10.000 elementi.

Quindi, se vuoi davvero una List<Integer>, le raccolte di eclipse sembrano la scelta migliore, ma ovviamente se usi i flussi in un modo più nativo (ad esempio, dimenticando .boxed()e riducendo il dominio primitivo) probabilmente finirai più veloce di tutti questi varianti.


1 Forse con l'eccezione della gestione degli errori, ad esempio, se end< begin, o se la dimensione supera alcuni limiti di implementazione o JVM (ad esempio, array più grandi di 2^31-1.


Risposte:


185

Con Java 8 è così semplice che non è più necessario nemmeno un metodo separato:

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

2
Ho aggiunto i risultati delle prestazioni per questa risposta sopra con l'etichetta intStreamRange .
BeeOnRope

Richiede API 24+
gcantoni

28

Bene, questa linea potrebbe qualificarsi (usa Guava Ranges )

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

Questo non crea un List<Integer>, ma ContiguousSetoffre più o meno la stessa funzionalità, in particolare l'implementazione Iterable<Integer>che consente l' foreachimplementazione allo stesso modo di List<Integer>.

Nelle versioni precedenti (da qualche parte prima di Guava 14) potresti usare questo:

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

Entrambi producono:

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

7
Non lo asList()userei a meno che tu non abbia davvero bisogno di un List... il ContiguousSetprodotto da asSetè leggero (ha solo bisogno dell'intervallo e del dominio), ma asList()creerà un elenco che memorizza effettivamente tutti gli elementi in memoria (attualmente).
ColinD

1
Concordato. L'OP richiedeva una lista o un array, altrimenti l'avrei lasciato fuori
daveb

1
Credo per 18.0, Rangeesiste ma non Rangese hanno eliminato il asSetmetodo. Nella mia versione precedente, asSetè deprecato e sembra che lo abbiano rimosso. Apparentemente gli intervalli devono essere utilizzati solo per raccolte contigue e l'hanno applicato anche se adoro questa soluzione.
demongolem

L'API ora richiede un codice simile a questo: ContiguousSet.create (Range.closed (1, count), DiscreteDomain.integers ()
Ben

Ho aggiunto i risultati delle prestazioni per questa risposta sopra con l'etichetta guavaRange .
BeeOnRope

11

La seguente versione one-liner di Java 8 genererà [1, 2, 3 ... 10]. Il primo argomento di iterateè il primo nr della sequenza e il primo argomento di limitè l'ultimo numero.

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

Ho aggiunto i risultati delle prestazioni per questa risposta sopra con l'etichetta streamIterate .
BeeOnRope

1
Come punto di chiarimento, il limite arg non è l'ultimo numero, è il numero di numeri interi nell'elenco.
neilireson

7

È possibile utilizzare la Intervalclasse dalle collezioni Eclipse .

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

La Intervalclasse è pigra, quindi non memorizza tutti i valori.

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

Il tuo metodo potrebbe essere implementato come segue:

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

Se desideri evitare di inscatolare gli interi come numeri interi, ma come risultato desideri comunque una struttura a elenco, puoi utilizzarli IntListcon IntIntervalda Eclipse Collections.

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

IntListha i metodi sum(), min(), minIfEmpty(), max(), maxIfEmpty(), average()e median()disponibili sull'interfaccia.

Aggiornamento per chiarezza: 27/11/2017

An Intervalè a List<Integer>, ma è pigro e immutabile. È estremamente utile per generare dati di test, soprattutto se ti occupi molto di raccolte. Se lo desideri, puoi facilmente copiare un intervallo in a List, Seto Bagcome segue:

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

An IntIntervalè un ImmutableIntListche si estende IntList. Ha anche metodi di conversione.

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

An Intervale an IntIntervalnon hanno lo stesso equalscontratto.

Aggiornamento per le collezioni Eclipse 9.0

È ora possibile creare raccolte primitive da flussi primitivi. Ci sono withAlle ofAllmetodi a seconda delle tue preferenze. Se sei curioso, ti spiego perché abbiamo entrambi qui . Questi metodi esistono per elenchi, set, borse e pile Int / Long / Double mutabili e immutabili.

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

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

Nota: sono un committer per le collezioni Eclipse


Ho aggiunto i risultati delle prestazioni per questa risposta sopra con l'etichetta eclipseCollections .
BeeOnRope

Neat. Ho aggiornato la mia risposta con una versione primitiva aggiuntiva che dovrebbe evitare qualsiasi boxe.
Donald Raab

6

Questo è il più breve che potrei ottenere usando 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
Puoi radere un paio di personaggi in più cambiando quel ciclo in for(int i = begin; i <= end; ret.add(i++));:)
vaughandroid

Non sono proprio sicuro che lo spostamento della ret.add(i)parte nell'incremento del ciclo for lo renda "più corto". Immagino che con quella logica se avessi scritto tutto su una riga sarebbe più breve :)
BeeOnRope

@BeeOnRope Sì, sicuramente non il più breve, ma sicuramente più corto di due righe :) Come ho detto, questo è il più vicino possibile ad accorciarlo in Core Java.
adarshr

Ho aggiunto i risultati delle prestazioni per questa risposta sopra con l'etichetta classicArrayList .
BeeOnRope

3

Potresti usare Guava Ranges

Puoi ottenere un SortedSetutilizzando

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

0

Questo è il più corto che ho trovato.

Versione elenco

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

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

    return ret;
}

Versione array

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

Questo potrebbe funzionare per te ...

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

Usare AtomicInteger è molto pesante per le risorse, circa dieci volte più lento nel mio test. Ma è sicuro per il multithread. fine <inizio non verificato
cl-r

1
L'uso di AtomicInteger non ha senso all'interno di un metodo. Tutte le frasi in una chiamata di metodo vengono eseguite in sequenza dal thread che ha chiamato il metodo, quindi non ottieni nulla da AtomicInteger ma rallentamenti e fastidiose chiamate getAndIncrement ().
Igor Rodriguez
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.