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

Aditya Singh
fonte
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

Delapouite
fonte
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)
Aditya Singh
fonte
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);
nkitku
fonte
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]
Ben
fonte
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 }
Salvador de ferro
fonte
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.

Getriax
fonte
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.


fonte
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]
Juanesarango
fonte
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
  )
};
user3500066
fonte
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].

Marcin Król
fonte
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 ]
Zack
fonte
0
const keys = Array(n).keys();
[...Array.from(keys)].forEach(callback);

no texto datilografado

PeiSong
fonte
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) 
  }
}
Han Lazarus
fonte
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.

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

Arturo Hernandez
fonte