¿Cómo generar un rango de números de 0 an solo en ES2015?

122

¿Siempre he encontrado la rangefunción que falta en JavaScript, ya que está disponible en Python y otros? ¿Existe alguna forma concisa de generar un rango de números en ES2015?

EDITAR: MI pregunta es diferente del duplicado mencionado, ya que es específico de ES2015 y no ECMASCRIPT-5. También necesito que el rango comience desde 0 y no un número de inicio específico (aunque sería bueno si eso estuviera allí)

Aditya Singh
fuente
La respuesta es la misma para ES5 y ES6.
loganfsmyth
1
Pero siempre puede utilizar algunos conceptos nuevos como generadores, nuevos métodos de matriz, etc. en ES2015. Eso le brinda un conjunto adicional de herramientas para lograr la tarea
Aditya Singh
7
Creo @Delapouite tiene la respuesta perfecta a esto en los comentarios de una respuesta a la pregunta duplicada : [...Array(n).keys()].
foque
2
[...Array(5)].map((_,i) => i+1)
nick indiessance

Respuestas:

243

Puede utilizar el operador de extensión en las claves de una matriz recién creada.

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

o

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

La Array.from()sintaxis es necesaria si se trabaja con TypeScript

Delapouite
fuente
38
Dulce:function range (start, end) { return [...Array(1+end-start).keys()].map(v => start+v) }
conny
2
Esto no funciona en mecanografiado porque keys () devuelve un iterador de matriz en lugar de una matriz. Consulte la respuesta de aditya-singh para un enfoque más universal.
David Domingo
3
…… o Array.from(Array(n).keys()).
Константин Ван
2
@DavidGonzalezShannon ¿Sabes por qué [...Array(n).keys()]no funciona en TypeScript? ¿Es una desviación intencional de otras implementaciones de JS?
Stu Cox
Hola @StuCox, no tengo idea de por qué, pero lo transpila Array(5).keys().slice()y el segmento no es un método de iterador de matriz. Aquí hay un ejemplo de que no funciona typecriptlang.org/play/…
David Domingo
98

También encontré una forma más intuitiva de usar Array.from:

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

Ahora esta rangefunción devolverá todos los números comenzando desde 0 hasta n-1

Una versión modificada de la gama para admitir starty endes:

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

EDITAR Como lo sugiere @ marco6, puede poner esto como un método estático si se adapta a su caso de uso

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

y usarlo como

Array.range(3, 9)
Aditya Singh
fuente
1
¡Buena esa! ¿Por qué no ampliamos la interfaz estática Array con él? En mecanografiado funciona muy bien con: interface ArrayConstructor { range(n: number): number[]; } Array.range = n => Array.from({length: n}, (value, key) => key); Y luego en todas partesArray.range(x)...
marco6
[ts] Property 'range' does not exist on type 'ArrayConstructor'. bocas?
kuncevic.dev
Anular los elementos integrados se considera una mala práctica en JavaScript ahora.
jhohlfeld
16

Con Delta

Para 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

Para mecanografiado

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

Actualizar

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

Editar

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
fuente
Su versión actualizada de TypeScript no funciona. Crea una matriz vacía con el tamaño indicado. Necesita usar Array.from con Array.keys con TypeScript. Array.from(Array(~~((to - from) / step) + 1).keys())
David Domingo
13

Para números del 0 al 5

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

Muchas de estas soluciones se basan en la creación de instancias de objetos Array reales, que pueden hacer el trabajo en muchos casos, pero no admiten casos como range(Infinity). Podría usar un generador simple para evitar estos problemas y admitir secuencias infinitas:

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

Ejemplos:

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 }
Salvador de hierro
fuente
8

Entonces, en este caso, sería bueno si el objeto Number se comportara como un objeto Array con el operador de propagación.

Por ejemplo, el objeto Array utilizado con el operador de propagación:

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

Funciona así porque el objeto Array tiene un iterador incorporado.
En nuestro caso, necesitamos que un objeto Number tenga una funcionalidad similar:

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

Para hacer eso, simplemente podemos crear un iterador numérico para ese propósito.

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

Ahora es posible crear rangos de 0 a N con el operador de propagación.

[... N] // ahora devuelve 0 ... N matriz

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

Salud.

Getriax
fuente
3

Puede usar una función de generador, que crea el rango de manera perezosa solo cuando es necesario:

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

Puede utilizar una función de generador de orden superior para mapear el rangegenerador:

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 no tiene miedo, incluso puede generalizar el enfoque del generador para abordar un rango mucho más amplio (juego de palabras):

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

Tenga en cuenta que los generadores / iteradores son inherentemente con estado, es decir, hay un cambio de estado implícito con cada invocación de next. El estado es una bendición mixta.


fuente
3

Rango con el paso ES6, que funciona de manera similar a 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)
}

Ejemplos:

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
fuente
1
¡Agradable adición a la pregunta! Esto me ayudó a obtener un código mucho más limpio en mis plantillas de bucle Angular 8 html * ngFor.
Sam
2

Para apoyar delta

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

También puedes hacerlo con un trazador de líneas con soporte escalonado como este:

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

El resultado es [0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9].

Marcin Król
fuente
2
¿Es este el combinador Y?
TheChetan
1
Sigue la idea del combinador en Y.
Marcin Król
1

Esta función devolverá una secuencia entera.

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
fuente
0
const keys = Array(n).keys();
[...Array.from(keys)].forEach(callback);

en TypeScript

PeiSong
fuente
No hay razón para usar ambos Array.fromy extender la sintaxis. Y luego es exactamente lo mismo que la respuesta existente.
Bergi
Solo quiero señalar [...Array(n).keys()]que no funciona en TypeScript.
PeiSong
3
Entonces usa Array.from(Array(n).keys()). Sin embargo, estoy bastante seguro de que debería funcionar, ¿a qué se transpila el literal con sintaxis extendida?
Bergi
0

Aquí hay otra variación que no se usa Array.

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

Los generadores ahora le permiten generar la secuencia numérica de manera perezosa y usando menos memoria para rangos grandes.

Si bien la pregunta establece específicamente ES2015, espero que muchos usuarios de Typecript terminen aquí y la conversión a ES sea sencilla ...

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

Las dos primeras declaraciones de función son solo para proporcionar sugerencias de finalización más informativas en su IDE.

Dave
fuente
Aaaaand se puede decir que no leí todas las respuestas existentes antes de publicar: - /
Dave
0

¿Qué tal simplemente mapear ...

Array (n) .map ((valor, índice) ....) es el 80% del camino. Pero por alguna extraña razón no funciona. Pero hay una solución.

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

Para una gama

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

Extraño, estos dos iteradores devuelven el mismo resultado: Array(end-start+1).entries()yArray(end-start+1).fill().entries()

Arturo Hernandez
fuente