Comment générer une plage de nombres de 0 à n dans ES2015 uniquement?


122

J'ai toujours trouvé la rangefonction manquante dans JavaScript car elle est disponible en python et autres? Existe-t-il un moyen concis de générer une plage de nombres dans ES2015?

EDIT: Ma question est différente du duplicata mentionné car elle est spécifique à ES2015 et non à ECMASCRIPT-5. J'ai également besoin que la plage commence à 0 et non à un numéro de départ spécifique (bien que ce serait bien si c'était là)


La réponse est la même pour ES5 et ES6.
loganfsmyth

1
Mais vous pouvez toujours utiliser certains des nouveaux concepts tels que les générateurs, les nouvelles méthodes de tableau, etc. dans ES2015. Cela vous donne un ensemble supplémentaire d'outils pour accomplir la tâche
Aditya Singh

7
Je pense que @Delapouite a la réponse parfaite à cela dans les commentaires à une réponse à la question dupliquée : [...Array(n).keys()].
foc


2
[...Array(5)].map((_,i) => i+1)
nick indiessance

Réponses:


244

Vous pouvez utiliser l'opérateur de diffusion sur les clés d'un tableau fraîchement créé.

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

ou

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

La Array.from()syntaxe est nécessaire si vous travaillez avec TypeScript


38
Sweet:function range (start, end) { return [...Array(1+end-start).keys()].map(v => start+v) }
conny

2
Cela ne fonctionne pas dans le typographie car keys () renvoie un Array Iterator au lieu d'un Array. Découvrez la réponse d'aditya-singh pour une approche plus universelle.
David Domingo

3
…… ou Array.from(Array(n).keys()).
Константин Ван

2
@DavidGonzalezShannon Savez-vous pourquoi [...Array(n).keys()]ne fonctionne pas dans Typescript? S'agit-il d'un écart intentionnel par rapport aux autres implémentations JS?
Stu Cox

Hey @StuCox Je ne sais pas pourquoi mais il le transpile Array(5).keys().slice()et le slice n'est pas une méthode d'itérateur de tableau. Voici un exemple de cela ne fonctionne pas typescriptlang.org/play/…
David Domingo

98

J'ai également trouvé un moyen plus intuitif d'utiliser Array.from:

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

Maintenant, cette rangefonction retournera tous les nombres de 0 à n-1

Une version modifiée de la gamme à prendre en charge startet endest:

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

EDIT Comme suggéré par @ marco6, vous pouvez mettre cela comme une méthode statique si cela convient à votre cas d'utilisation

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

et utilisez-le comme

Array.range(3, 9)

1
Joli! Pourquoi ne pas étendre l'interface statique d'Array avec lui? En dactylographié fonctionne très bien avec: interface ArrayConstructor { range(n: number): number[]; } Array.range = n => Array.from({length: n}, (value, key) => key); Et puis partoutArray.range(x)...
marco6

[ts] Property 'range' does not exist on type 'ArrayConstructor'. des thouths?
kuncevic.dev

1
Remplacer les éléments intégrés est maintenant considéré comme une mauvaise pratique en javascript.
jhohlfeld

16

Avec Delta

Pour 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

Pour Typescript

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

Mettre à jour

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

Éditer

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

Votre version de TypeScript mise à jour ne fonctionne pas. Il crée un tableau vide avec la taille indiquée. Vous devez utiliser Array.from avec Array.keys avec TypeScript. Array.from(Array(~~((to - from) / step) + 1).keys())
David Domingo

13

Pour les nombres 0 à 5

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

10

Un grand nombre de ces solutions reposent sur l'instanciation d'objets Array réels, ce qui peut faire le travail dans de nombreux cas, mais ne peut pas prendre en charge des cas comme range(Infinity). Vous pouvez utiliser un simple générateur pour éviter ces problèmes et prendre en charge des séquences infinies:

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

Exemples:

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 }

8

Donc, dans ce cas, ce serait bien si l' objet Number se comporterait comme un objet Array avec l'opérateur de propagation.

Par exemple, un objet Array utilisé avec l'opérateur de diffusion:

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

Cela fonctionne comme ceci car l'objet Array a un itérateur intégré.
Dans notre cas, nous avons besoin d'un objet Number pour avoir une fonctionnalité similaire:

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

Pour ce faire, nous pouvons simplement créer un itérateur numérique à cet effet.

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

Il est maintenant possible de créer des plages de 0 à N avec l'opérateur d'étalement.

[... N] // renvoie maintenant 0 ... N tableau

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

À votre santé.


3

Vous pouvez utiliser une fonction de générateur, qui crée la plage paresseusement uniquement lorsque cela est nécessaire:

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()
);

Vous pouvez utiliser une fonction de générateur d'ordre supérieur pour mapper sur le rangegénérateur:

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]
);

Si vous n'avez pas peur, vous pouvez même généraliser l'approche du générateur pour aborder un éventail beaucoup plus large (jeu de mots):

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]
);

Gardez à l'esprit que les générateurs / itérateurs sont intrinsèquement avec état, c'est-à-dire qu'il y a un changement d'état implicite à chaque invocation de next. L'État est une bénédiction mitigée.


3

Plage avec l'étape ES6, qui fonctionne de manière similaire à python 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)
}

Exemples:

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]

1
Bel ajout à la question! Cela m'a aidé à obtenir un code beaucoup plus propre dans mes modèles de boucle Angular 8 html * ngFor.
Sam

2

Pour prendre en charge delta

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

1

Vous pouvez également le faire avec une doublure avec support de marche comme celui-ci:

((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)

Le résultat est [0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9].


2
Est-ce le combinateur Y?
TheChetan

1
Il suit l'idée de Y-combinator.
Marcin Król

1

Cette fonction renverra une séquence d'entiers.

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 ]

0
const keys = Array(n).keys();
[...Array.from(keys)].forEach(callback);

dans Typographie


Il n'y a aucune raison d'utiliser les deux Array.fromet de diffuser la syntaxe. Et puis c'est exactement la même chose que la réponse existante.
Bergi

Je veux juste souligner [...Array(n).keys()]ne fonctionne pas dans Typescript.
PeiSong

3
Ensuite, utilisez Array.from(Array(n).keys()). Je suis à peu près sûr que cela devrait fonctionner, à quoi transite le littéral avec la syntaxe de propagation?
Bergi

0

Voici une autre variante qui n'utilise pas Array.

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

0

Les générateurs vous permettent désormais de générer la séquence de nombres paresseusement et en utilisant moins de mémoire pour les grandes plages.

Bien que la question mentionne spécifiquement ES2015, je m'attends à ce que beaucoup d'utilisateurs de Typescript se retrouvent ici et la conversion en ES est simple ...

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

Les deux premières déclarations de fonction sont juste pour fournir des suggestions de complétion plus informatives dans votre IDE.


Aaaaand vous pouvez dire que je n'ai pas lu toutes les réponses existantes avant de poster: - /
Dave

0

Que diriez-vous simplement de la cartographie ...

Array (n) .map ((value, index) ....) est à 80% du chemin. Mais pour une raison étrange, cela ne fonctionne pas. Mais il existe une solution de contournement.

Array(n).map((v,i) => i) // does not work
Array(n).fill().map((v,i) => i) // does dork

Pour une gamme

Array(end-start+1).fill().map((v,i) => i + start) // gives you a range

Bizarre, ces deux itérateurs renvoient le même résultat: Array(end-start+1).entries()etArray(end-start+1).fill().entries()

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.