Wie kann ein Zahlenbereich von 0 bis n nur in ES2015 generiert werden?

121

Ich habe immer festgestellt, dass die rangeFunktion in JavaScript fehlt, da sie in Python und anderen verfügbar ist. Gibt es eine präzise Möglichkeit, in ES2015 einen Zahlenbereich zu generieren?

EDIT: MEINE Frage unterscheidet sich von dem erwähnten Duplikat, da sie spezifisch für ES2015 und nicht für ECMASCRIPT-5 ist. Außerdem muss der Bereich bei 0 beginnen und nicht bei einer bestimmten Startnummer (obwohl es gut wäre, wenn das vorhanden wäre).

Aditya Singh
quelle
Die Antwort ist für ES5 und ES6 gleich.
Loganfsmyth
1
Sie können jedoch immer einige der neuen Konzepte wie Generatoren, neue Array-Methoden usw. in ES2015 verwenden. Das gibt Ihnen zusätzliche Werkzeuge, um die Aufgabe zu erfüllen
Aditya Singh
7
Ich denke, @Delapouite hat die perfekte Antwort darauf in Kommentaren zu einer Antwort auf die doppelte Frage : [...Array(n).keys()].
Fock
2
[...Array(5)].map((_,i) => i+1)
Nick Indiessance

Antworten:

242

Sie können den Spread-Operator für die Schlüssel eines frisch erstellten Arrays verwenden.

[...Array(n).keys()]

oder

Array.from(Array(n).keys())

Die Array.from()Syntax ist erforderlich, wenn Sie mit TypeScript arbeiten

Delapouite
quelle
38
Süß:function range (start, end) { return [...Array(1+end-start).keys()].map(v => start+v) }
Conny
2
Dies funktioniert in Typoskript nicht, da keys () einen Array-Iterator anstelle eines Arrays zurückgibt. Kasse aditya-singhs Antwort für einen universelleren Ansatz.
David Domingo
3
…… oder Array.from(Array(n).keys()).
4онстантин Ван
2
@ DavidGonzalezShannon Wissen Sie, warum [...Array(n).keys()]in Typescript nicht funktioniert? Ist es eine absichtliche Abweichung von anderen JS-Implementierungen?
Stu Cox
Hey @StuCox Ich habe keine Ahnung , warum , aber es transpiles es Array(5).keys().slice()und in Scheiben schneiden ist keine Methode der Array - Iterator. Hier ist ein Beispiel dafür, dass es nicht funktioniert typescriptlang.org/play/…
David Domingo
97

Ich habe auch einen intuitiveren Weg gefunden, indem ich Array.from:

const range = n => Array.from({length: n}, (value, key) => key)

Jetzt gibt diese rangeFunktion alle Zahlen von 0 bis n-1 zurück

Eine modifizierte Version des Bereichs zu unterstützen startund endist:

const range = (start, end) => Array.from({length: (end - start)}, (v, k) => k + start);

BEARBEITEN Wie von @ marco6 vorgeschlagen, können Sie dies als statische Methode verwenden, wenn es Ihrem Anwendungsfall entspricht

Array.range = (start, end) => Array.from({length: (end - start)}, (v, k) => k + start);

und benutze es als

Array.range(3, 9)
Aditya Singh
quelle
1
Schön! Warum erweitern wir die statische Array-Schnittstelle nicht damit? In Typoskript funktioniert gut mit: interface ArrayConstructor { range(n: number): number[]; } Array.range = n => Array.from({length: n}, (value, key) => key); Und dann überallArray.range(x)...
marco6
[ts] Property 'range' does not exist on type 'ArrayConstructor'. Thouths?
Kuncevic.dev
Das Überschreiben von integrierten Funktionen wird derzeit in Javascript als schlechte Praxis angesehen.
Jhohlfeld
16

Mit Delta

Für Javascript

Array.from(Array(10).keys()).map(i => 4 + i * 2);
//=> [4, 6, 8, 10, 12, 14, 16, 18, 20, 22]

[...Array(10).keys()].map(i => 4 + i * -2);
//=> [4, 2, 0, -2, -4, -6, -8, -10, -12, -14]

Array(10).fill(0).map((v, i) => 4 + i * 2);
//=> [4, 6, 8, 10, 12, 14, 16, 18, 20, 22]

Array(10).fill().map((v, i) => 4 + i * -2);
//=> [4, 2, 0, -2, -4, -6, -8, -10, -12, -14]

[...Array(10)].map((v, i) => 4 + i * 2);
//=> [4, 6, 8, 10, 12, 14, 16, 18, 20, 22]

const range = (from, to, step) =>
  Array(~~((to - from) / step) + 1) // '~~' is Alternative for Math.floor()
  .fill().map((v, i) => from + i * step);

range(0, 9, 2);
//=> [0, 2, 4, 6, 8]

Array.range = (from, to, step) => Array.from({
    length: ~~((to - from) / step) + 1
  },
  (v, k) => from + k * step
);

Array.range = (from, to, step) => [...Array(~~((to - from) / step) + 1)].map(
  (v, k) => from + k * step
)
Array.range(2, 10, 2);
//=> [2, 4, 6, 8, 10]

Array.range(0, 10, 1);
//=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Array.range(2, 10, -1);
//=> []

Array.range(3, 0, -1);
//=> [3, 2, 1, 0]


class Range {
  constructor(total = 0, step = 1, from = 0) {
    this[Symbol.iterator] = function*() {
      for (let i = 0; i < total; yield from + i++ * step) {}
    };
  }
}

[...new Range(5)]; // Five Elements
//=> [0, 1, 2, 3, 4]
[...new Range(5, 2)]; // Five Elements With Step 2
//=> [0, 2, 4, 6, 8]
[...new Range(5, -2, 10)]; // Five Elements With Step -2 From 10
//=>[10, 8, 6, 4, 2]
[...new Range(5, -2, -10)]; // Five Elements With Step -2 From -10
//=> [-10, -12, -14, -16, -18]

// Also works with for..of loop
for (i of new Range(5, -2, 10)) console.log(i);
// 10 8 6 4 2

// Or
const Range = function*(total = 0, step = 1, from = 0){
  for (let i = 0; i < total; yield from + i++ * step) {}
};

Array.from(Range(5, -2, -10));
//=> [-10, -12, -14, -16, -18]
[...Range(5, -2, -10)]; // Five Elements With Step -2 From -10
//=> [-10, -12, -14, -16, -18]

// Also works with for..of loop
for (i of Range(5, -2, 10)) console.log(i);
// 10 8 6 4 2

class Range2 {
  constructor(to = 0, step = 1, from = 0) {
    this[Symbol.iterator] = function*() {
      let i = 0,
        length = ~~((to - from) / step) + 1;
      while (i < length) yield from + i++ * step;
    };
  }
}
[...new Range2(5)]; // First 5 Whole Numbers
//=> [0, 1, 2, 3, 4, 5]

[...new Range2(5, 2)]; // From 0 to 5 with step 2
//=> [0, 2, 4]

[...new Range2(5, -2, 10)]; // From 10 to 5 with step -2
//=> [10, 8, 6]

// Or 
const Range2 = function*(to = 0, step = 1, from = 0) {
    let i = 0, length = ~~((to - from) / step) + 1;
    while (i < length) yield from + i++ * step;
};


[...Range2(5, -2, 10)]; // From 10 to 5 with step -2
//=> [10, 8, 6]

let even4to10 = Range2(10, 2, 4);
even4to10.next().value
//=> 4
even4to10.next().value
//=> 6
even4to10.next().value
//=> 8
even4to10.next().value
//=> 10
even4to10.next().value
//=> undefined

Für Typoskript

interface _Iterable extends Iterable < {} > {
  length: number;
}

class _Array < T > extends Array < T > {
  static range(from: number, to: number, step: number): number[] {
    return Array.from(
      ( < _Iterable > { length: Math.floor((to - from) / step) + 1 }),
      (v, k) => from + k * step
    );
  }
}
_Array.range(0, 9, 1);
//=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

Aktualisieren

class _Array<T> extends Array<T> {
    static range(from: number, to: number, step: number): number[] {
        return [...Array(~~((to - from) / step) + 1)].map(
            (v, k) => from + k * step
        );
    }
}
_Array.range(0, 9, 1);

Bearbeiten

class _Array<T> extends Array<T> {
    static range(from: number, to: number, step: number): number[] {
        return Array.from(Array(~~((to - from) / step) + 1)).map(
            (v, k) => from + k * step
        );
    }
}
_Array.range(0, 9, 1);
nkitku
quelle
Ihre aktualisierte TypeScript-Version funktioniert nicht. Es wird ein leeres Array mit der angegebenen Größe erstellt. Sie müssen Array.from mit Array.keys mit TypeScript verwenden. Array.from(Array(~~((to - from) / step) + 1).keys())
David Domingo
13

Für die Nummern 0 bis 5

[...Array(5).keys()];
=> [0, 1, 2, 3, 4]
Ben
quelle
10

Viele dieser Lösungen bauen auf der Instanziierung realer Array-Objekte auf, die die Arbeit für viele Fälle erledigen können, aber Fälle wie nicht unterstützen können range(Infinity). Sie können einen einfachen Generator verwenden, um diese Probleme zu vermeiden und unendliche Sequenzen zu unterstützen:

function* range( start, end, step = 1 ){
  if( end === undefined ) [end, start] = [start, 0];
  for( let n = start; n < end; n += step ) yield n;
}

Beispiele:

Array.from(range(10));     // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Array.from(range(10, 20)); // [ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ]

i = range(10, Infinity);
i.next(); // { value: 10, done: false }
i.next(); // { value: 11, done: false }
i.next(); // { value: 12, done: false }
i.next(); // { value: 13, done: false }
i.next(); // { value: 14, done: false }
Eisenretter
quelle
8

In diesem Fall wäre es also schön, wenn sich das Number- Objekt mit dem Spread-Operator wie ein Array-Objekt verhalten würde.

Zum Beispiel Array- Objekt, das mit dem Spread-Operator verwendet wird:

let foo = [0,1,2,3];
console.log(...foo) // returns 0 1 2 3

Dies funktioniert folgendermaßen, da das Array-Objekt über einen integrierten Iterator verfügt.
In unserem Fall benötigen wir ein Number- Objekt, um eine ähnliche Funktionalität zu haben:

[...3] //should return [0,1,2,3]

Zu diesem Zweck können wir einfach einen Zahleniterator für diesen Zweck erstellen.

Number.prototype[Symbol.iterator] = function *() {
   for(let i = 0; i <= this; i++)
       yield i;
}

Jetzt ist es möglich, mit dem Spread-Operator Bereiche von 0 bis N zu erstellen.

[... N] // gibt jetzt 0 ... N Array zurück

http://jsfiddle.net/01e4xdv5/4/

Prost.

Getriax
quelle
3

Sie können eine Generatorfunktion verwenden, die den Bereich nur bei Bedarf träge erstellt:

function* range(x, y) {
  while (true) {
    if (x <= y)
      yield x++;

    else
      return null;
  }
}

const infiniteRange = x =>
  range(x, Infinity);
  
console.log(
  Array.from(range(1, 10)) // [1,2,3,4,5,6,7,8,9,10]
);

console.log(
  infiniteRange(1000000).next()
);

Sie können eine Generatorfunktion höherer Ordnung verwenden, um den rangeGenerator abzubilden :

function* range(x, y) {
  while (true) {
    if (x <= y)
      yield x++;

    else
      return null;
  }
}

const genMap = f => gx => function* (...args) {
  for (const x of gx(...args))
    yield f(x);
};

const dbl = n => n * 2;

console.log(
  Array.from(
    genMap(dbl) (range) (1, 10)) // [2,4,6,8,10,12,14,16,18,20]
);

Wenn Sie furchtlos sind, können Sie den Generatoransatz sogar verallgemeinern, um einen viel größeren Bereich anzusprechen (Wortspiel beabsichtigt):

const rangeBy = (p, f) => function* rangeBy(x) {
  while (true) {
    if (p(x)) {
      yield x;
      x = f(x);
    }

    else
      return null;
  }
};

const lte = y => x => x <= y;

const inc = n => n + 1;

const dbl = n => n * 2;

console.log(
  Array.from(rangeBy(lte(10), inc) (1)) // [1,2,3,4,5,6,7,8,9,10]
);

console.log(
  Array.from(rangeBy(lte(256), dbl) (2)) // [2,4,8,16,32,64,128,256]
);

Beachten Sie, dass Generatoren / Iteratoren von Natur aus statusbehaftet sind, dh, dass bei jedem Aufruf von eine implizite Statusänderung auftritt next. Staat ist ein gemischter Segen.


quelle
3

Bereich mit Schritt ES6, der ähnlich wie Python funktioniert list(range(start, stop[, step])):

const range = (start, stop, step = 1) => {
  return [...Array(stop - start).keys()]
    .filter(i => !(i % Math.round(step)))
    .map(v => start + v)
}

Beispiele:

range(0, 8) // [0, 1, 2, 3, 4, 5, 6, 7]
range(4, 9) // [4, 5, 6, 7, 8]
range(4, 9, 2) // [4, 6, 8] 
range(4, 9, 3) // [4, 7]
Juanesarango
quelle
1
Schön zur Frage hinzufügen! Dies hat mir geholfen, viel saubereren Code in meinen Angular 8 html * ngFor-Schleifenvorlagen zu erhalten.
Sam
2

Delta unterstützen

const range = (start, end, delta) => {
  return Array.from(
    {length: (end - start) / delta}, (v, k) => (k * delta) + start
  )
};
user3500066
quelle
1

Sie können dies auch mit einem Einzeiler mit Stufenunterstützung wie dieser tun:

((from, to, step) => ((add, arr, v) => add(arr, v, add))((arr, v, add) => v < to ? add(arr.concat([v]), v + step, add) : arr, [], from))(0, 10, 1)

Das Ergebnis ist [0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9].

Marcin Król
quelle
2
Ist das der Y-Kombinator?
TheChetan
1
Es folgt der Idee des Y-Kombinators.
Marcin Król
1

Diese Funktion gibt eine ganzzahlige Sequenz zurück.

const integerRange = (start, end, n = start, arr = []) =>
  (n === end) ? [...arr, n]
    : integerRange(start, end, start < end ? n + 1 : n - 1, [...arr, n]);

$> integerRange(1, 1)
<- Array [ 1 ]

$> integerRange(1, 3)
<- Array(3) [ 1, 2, 3 ]

$> integerRange(3, -3)
<- Array(7) [ 3, 2, 1, 0, -1, -2, -3 ]
Zack
quelle
0
const keys = Array(n).keys();
[...Array.from(keys)].forEach(callback);

in Typoskript

PeiSong
quelle
Es gibt keinen Grund, beides zu verwenden Array.fromund die Syntax zu verbreiten. Und dann ist es genau das gleiche wie die vorhandene Antwort.
Bergi
Ich möchte nur darauf hinweisen, dass [...Array(n).keys()]dies in Typescript nicht funktioniert.
PeiSong
3
Dann verwenden Array.from(Array(n).keys()). Ich bin mir ziemlich sicher, dass es funktionieren sollte. Worauf überträgt sich das Literal mit der Spread-Syntax?
Bergi
0

Hier ist eine andere Variante, die nicht verwendet wird Array.

let range = (n, l=[], delta=1) => {
  if (n < 0) { 
    return l 
  }
  else {
    l.unshift(n)
    return range(n - delta, l) 
  }
}
Han Lazarus
quelle
0

Mit Generatoren können Sie jetzt die Zahlenfolge träge generieren und für große Bereiche weniger Speicher verwenden.

Während die Frage speziell ES2015 angibt, gehe ich davon aus, dass viele Typescript-Benutzer hier landen werden und die Konvertierung in ES unkompliziert ist ...

function range(end: number): IterableIterator<number>;
// tslint:disable-next-line:unified-signatures
function range(begin: number, end: number): IterableIterator<number>;

function *range(begin: number, end: number = NaN): IterableIterator<number> {
    let num = 0;
    if (isNaN(end)) {
        end = begin;
    } else {
        num = begin;
    }
    while (num < end) {
        yield num++;
    }
}

Die ersten beiden Funktionsdeklarationen dienen lediglich dazu, informativere Vorschläge zur Vervollständigung in Ihrer IDE bereitzustellen.

Dave
quelle
Aaaaund Sie können sagen, dass ich nicht alle vorhandenen Antworten vor dem Posten gelesen habe: - /
Dave