Como gerar um intervalo de números de 0 a n apenas no ES2015?


122

Sempre achei a rangefunção ausente no JavaScript, pois está disponível em python e outros? Existe alguma maneira concisa de gerar um intervalo de números no ES2015?

EDITAR: MINHA pergunta é diferente da duplicata mencionada, pois é específica para ES2015 e não ECMASCRIPT-5. Também preciso que o intervalo comece em 0 e não um número inicial específico (embora seja bom se estiver lá)


A resposta é a mesma para ES5 e ES6.
loganfsmyth

1
Mas você sempre pode usar alguns dos novos conceitos, como geradores, novos métodos de array, etc. no ES2015. Isso dá a você um conjunto extra de ferramentas para realizar a tarefa
Aditya Singh

7
Acho @Delapouite tem a resposta perfeita para isso em comentários para uma resposta para a pergunta duplicada : [...Array(n).keys()].
jib


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

Respostas:


243

Você pode usar o operador spread nas chaves de um array recém-criado.

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

ou

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

A Array.from()sintaxe é necessária se estiver trabalhando com TypeScript


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

2
Isso não funciona em texto digitado porque keys () retorna um Array Iterator em vez de um Array. Verifique a resposta de aditya-singh para uma abordagem mais universal.
David Domingo

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

2
@DavidGonzalezShannon Você sabe por [...Array(n).keys()]que não funciona no Typescript? É um desvio intencional de outras implementações JS?
Stu Cox

Ei @StuCox, não tenho ideia do porquê, mas ele transpila para Array(5).keys().slice()e o slice não é um método de iterador de array. Aqui está um exemplo de que ele não está funcionando typescriptlang.org/play/…
David Domingo

97

Também encontrei uma maneira mais intuitiva de usar Array.from:

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

Agora esta rangefunção irá retornar todos os números começando de 0 a n-1

Uma versão modificada do intervalo para oferecer suporte starte endé:

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

EDITAR Como sugerido por @ marco6, você pode colocar isso como um método estático se for adequado ao seu caso de uso

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

e usá-lo como

Array.range(3, 9)

1
Agradável! Por que não estendemos a interface estática Array com ele? Em datilografado funciona muito bem com: interface ArrayConstructor { range(n: number): number[]; } Array.range = n => Array.from({length: n}, (value, key) => key); E em qualquer lugarArray.range(x)...
marco6

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

Substituir built-ins é considerado uma prática ruim em javascript agora.
jhohlfeld de

16

Com 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 texto datilografado

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

Atualizar

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

Sua versão atualizada do TypeScript não funciona. Ele cria um array vazio com o tamanho indicado. Você precisa usar Array.from com Array.keys com TypeScript. Array.from(Array(~~((to - from) / step) + 1).keys())
David Domingo,

13

Para números de 0 a 5

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

10

Muitas dessas soluções se baseiam na instanciação de objetos Array reais, que podem realizar o trabalho em muitos casos, mas não podem suportar casos como o range(Infinity). Você pode usar um gerador simples para evitar esses problemas e suportar sequências infinitas:

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

Exemplos:

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

Portanto, neste caso, seria bom se o objeto Number se comportasse como um objeto Array com o operador spread.

Por exemplo, objeto Array usado com o operador spread:

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

Funciona assim porque o objeto Array tem um iterador embutido.
Em nosso caso, precisamos de um objeto Number para ter uma funcionalidade semelhante:

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

Para fazer isso, podemos simplesmente criar o iterador de número para esse propósito.

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

Agora é possível criar intervalos de 0 a N com o operador spread.

[... N] // agora retorna 0 ... N array

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

Felicidades.


3

Você pode usar uma função de gerador, que cria o intervalo lentamente apenas quando necessário:

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

Você pode usar uma função de gerador de ordem superior para mapear o rangegerador:

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

Se você não tem medo, pode até generalizar a abordagem do gerador para abordar uma gama muito mais ampla (trocadilho intencional):

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

Lembre-se de que os geradores / iteradores são inerentemente com estado, ou seja, há uma mudança de estado implícita com cada invocação de next. O estado é uma bênção mista.


3

Intervalo com a etapa ES6, que funciona de forma semelhante ao 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)
}

Exemplos:

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
Bom adicionar à pergunta! Isso me ajudou a obter um código muito mais limpo em meus modelos de loop Angular 8 html * ngFor.
Sam

2

Para apoiar delta

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

1

Você também pode fazer isso com um revestimento único com suporte de degrau 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)

O resultado é [0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9].


2
Este é o combinador Y?
TheChetan

1
Segue a ideia do combinador Y.
Marcin Król

1

Esta função retornará uma sequência inteira.

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

no texto datilografado


Não há razão para usar a Array.fromsintaxe de propagação e ambas . E então é exatamente igual à resposta existente.
Bergi

Só quero ressaltar [...Array(n).keys()]que não funciona no texto datilografado.
PeiSong de

3
Então use Array.from(Array(n).keys()). Tenho certeza de que deve funcionar, para o que a sintaxe literal com propagação se transpila?
Bergi

0

Aqui está outra variação que não usa Array.

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

0

Os geradores agora permitem que você gere a sequência numérica preguiçosamente e usando menos memória para grandes intervalos.

Embora a pergunta indique especificamente ES2015, espero que muitos usuários do Typescript acabem aqui e a conversão para ES seja direta ...

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

As duas primeiras declarações de função são apenas para fornecer sugestões de conclusão mais informativas em seu IDE.


Aaaaand você pode dizer que eu não li todas as respostas existentes antes de postar: - /
Dave

0

Que tal apenas mapear ...

Array (n) .map ((valor, índice) ....) é 80% do caminho até lá. Mas, por algum motivo estranho, não funciona. Mas há uma solução alternativa.

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

Para um intervalo

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

Estranho, esses dois iteradores retornam o mesmo resultado: Array(end-start+1).entries()eArray(end-start+1).fill().entries()

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.