Função JavaScript semelhante ao intervalo Python ()

103

Existe uma função em JavaScript semelhante à do Python range()?

Acho que deveria haver uma maneira melhor do que escrever as seguintes linhas todas as vezes:

array = new Array();
for (i = 0; i < specified_len; i++) {
    array[i] = i;
}
Clwen
fonte
1
@clwen: Infelizmente não há, mas dê uma olhada no meu código - escrevi uma função que visa emular a maneira como range()funciona em Python, para que você possa usá-la. Não existe tal função em JavaScript, mas existem alguns plug-ins para diferentes estruturas, como Rangeclasse para MooTools .
Tadeck

Respostas:

92

Não , não há nenhum, mas você pode fazer um .

Implementação de JavaScript de Python range()

Tentando emular como funciona em Python , eu criaria uma função semelhante a esta:

function range(start, stop, step) {
    if (typeof stop == 'undefined') {
        // one param defined
        stop = start;
        start = 0;
    }

    if (typeof step == 'undefined') {
        step = 1;
    }

    if ((step > 0 && start >= stop) || (step < 0 && start <= stop)) {
        return [];
    }

    var result = [];
    for (var i = start; step > 0 ? i < stop : i > stop; i += step) {
        result.push(i);
    }

    return result;
};

Veja este jsfiddle para uma prova.

Comparação entre range()em JavaScript e Python

Funciona da seguinte maneira:

  • range(4)retorna [0, 1, 2, 3],
  • range(3,6)retorna [3, 4, 5],
  • range(0,10,2)retorna [0, 2, 4, 6, 8],
  • range(10,0,-1)retorna [10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
  • range(8,2,-2)retorna [8, 6, 4],
  • range(8,2)retorna [],
  • range(8,2,2)retorna [],
  • range(1,5,-1)retorna [],
  • range(1,5,-2)retorna [],

e sua contraparte Python funciona exatamente da mesma maneira (pelo menos nos casos mencionados):

>>> range(4)
[0, 1, 2, 3]
>>> range(3,6)
[3, 4, 5]
>>> range(0,10,2)
[0, 2, 4, 6, 8]
>>> range(10,0,-1)
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
>>> range(8,2,-2)
[8, 6, 4]
>>> range(8,2)
[]
>>> range(8,2,2)
[]
>>> range(1,5,-1)
[]
>>> range(1,5,-2)
[]

Portanto, se você precisa de uma função para funcionar de forma semelhante à do Python range(), pode usar a solução mencionada acima.

Tadeck
fonte
4
talvez algumas verificações defensivas adicionais - certifique-se de que os argumentos passados ​​sejam todos coercíveis para números e certifique-se de que stopseja maior que start(e troque-os se não).
Russ Cam
1
@RussCam: Obrigado por apontar isso. Não adicionei verificações defensivas para tipos, etc., mas implementei a ordem reversa dos elementos - agora funciona exatamente da mesma forma que a contraparte Python, quando o último parâmetro é um inteiro negativo.
Tadeck
@RussCam: start >= stopconduzir a um array vazio é necessário se o objetivo for realmente emular o intervalo do Python. E eu diria que é mais intuitivo de qualquer maneira.
1
@delnan: Check pode ser mais complexo, pois simples start >= stopnão é suficiente para esta função se comportar como range()em Python. Eu atualizei minha resposta.
Tadeck
@delnan - Não estou familiarizado com a implementação do Python. Eu acho que se ele for usado apenas por pessoas familiarizadas com a implementação do Python, faz sentido emulá-lo :)
Russ Cam
130

Para um intervalo muito simples em ES6:

let range = n => Array.from(Array(n).keys())

A partir do comentário de bigOmega , isso pode ser reduzido usando a sintaxe Spread :

let range = n => [...Array(n).keys()]
Will Ediger
fonte
41
Uma versão mais simples disso élet range = n => [...Array(n).keys()]
bigOmega
2
@BharathRaja ótimo, obrigado! Por que não const? Vou tentar usar const sempre que puder :) como a
versão
Você está certo, tento usar consto máximo possível, mas aqui será apenas uma referência ao array, então o array ainda seria editável: 'D
bigOmega
1
@bigOmega The constse aplicaria à própria função, não ao seu valor de retorno. Você provavelmente não deseja modificar a função de forma alguma.
Solomon Ucko
7
Essa resposta não leva em consideração um índice inicial e a capacidade de aumentar o tamanho do passo.
Fluous
31

2018: esta resposta continua recebendo votos positivos, então aqui está uma atualização. O código abaixo está obsoleto, mas felizmente ES6 padronizou geradores e a yieldpalavra - chave, e eles são universalmente suportados em todas as plataformas. Um exemplo de range()uso preguiçoso yieldpode ser encontrado aqui .


Além do que já foi dito, o Javascript 1.7+ fornece suporte para iteradores e geradores que podem ser usados ​​para criar uma versão preguiçosa e com uso eficiente de memória range, semelhante ao xrangedo Python2:

function range(low, high) {  
    return {
        __iterator__: function() {
            return {  
                next: function() {
                    if (low > high)
                        throw StopIteration;  
                    return low++;
                }
            }
        }
    }
}

for (var i in range(3, 5))  
  console.log(i); // 3,4,5
georg
fonte
1
+1 Ótima ideia! Você poderia implementar também o stepargumento e testá-lo nos valores de minha resposta ? Sua resposta é ótima para os aplicativos para os quais temos navegadores muito específicos em mente (não funciona no Google Chrome, Safari e versão do IE anteriores à 9: stackoverflow.com/a/2209743/548696 ).
Tadeck,
@Tadeck: ironicamente, eu fiz uma pergunta muito semelhante recentemente, dê uma olhada - algumas boas respostas aí. A propósito, seu código não passou no meu teste; (
georg,
Você poderia compartilhar os dados do teste e os resultados esperados? Eu ficaria feliz em melhorá-lo, mas meus testes estão 100% aprovados. Você está dizendo que o código que forneci não foi analisado corretamente pelo script que você colocou nesta pergunta: stackoverflow.com/q/12173856/548696 ?
Tadeck,
@Tadeck: deixa pra lá. Eu testei fatias e seu código é para intervalos.
georg,
1
O Python rangetem o valor "último" excluído (portanto, é necessário usar em >=vez do >código acima)
Pac0
25

Combinando as respostas de @Tadeck e @georg , eu vim com o seguinte:

function* range(start, stop, step = 1) {
    if (stop == null) {
        // one param defined
        stop = start;
        start = 0;
    }

    for (let i = start; step > 0 ? i < stop : i > stop; i += step) {
        yield i;
    }
}

Para usá-lo em um loop for, você precisa do loop for-of ES6 / JS1.7:

for (let i of range(5)) {
    console.log(i);
}
// Outputs => 0 1 2 3 4

for (let i of range(0, 10, 2)) {
    console.log(i);
}
// Outputs => 0 2 4 6 8

for (let i of range(10, 0, -2)) {
    console.log(i);
}
// Outputs => 10 8 6 4 2
janka102
fonte
Por que não usar parâmetros padrão se você estiver usando ES6?
Amin NAIRI
@Gradiuss que funcionaria para o stepparâmetro, mas quando stopnão é passado, ambos starte stopprecisam ser alterados. Vou atualizá-lo
janka102
2
WTG, esta é a melhor implementação apresentada até agora. Ele usa geradores (bom, já que não há necessidade de armazenar toda a sequência na memória) e é muito sucinto.
Lucio Paiva
undefined é um ponteiro para um objeto como null. possível comparar com 3 sinais de igual como: if (stop === undefined) {3 sinais de igual é comparar sem conversão automática. compare como também é o tipo de comparação. 2 sinais de igual são comparados com fundição automática para outro tipo de lado.
Shimon Doodkin
21

Uma porta da rangefunção do Python 2 é fornecida pelas bibliotecas de utilitários underscore.js e lodash (junto com muitas outras ferramentas úteis). Exemplos copiados dos documentos de sublinhado:

_.range(10);
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
_.range(1, 11);
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
_.range(0, 30, 5);
=> [0, 5, 10, 15, 20, 25]
_.range(0, -10, -1);
=> [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
_.range(0);
=> []
Mark Amery
fonte
16

Pode ser alcançado anexando um iterador ao Numberprotótipo

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

[...5] // will result in [0,1,2,3,4,5]

Retirado do curso de Kyle Simpson Rethinking Asynchronous JavaScript

mcha
fonte
8
Isso é muito legal, mas substituir protótipos é muito frágil: '(
m0meni
9

Aqui está uma pequena extensão para uma das respostas, caso você precise especificar as posições inicial e final do intervalo:

let range = (start, end) => Array.from(Array(end + 1).keys()).slice(start);
Dmitrii Mikhailov
fonte
7

Aqui está.

Isso gravará (ou substituirá) o valor de cada índice com o número do índice.

Array.prototype.writeIndices = function( n ) {
    for( var i = 0; i < (n || this.length); ++i ) this[i] = i;
    return this;
};

Se você não fornecer um número, ele usará o comprimento atual do Array.

Use-o assim:

var array = [].writeIndices(10);  // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
RightSaidFred
fonte
5

Mais refinado com os parâmetros padrão do ES6.

let range = function*(start = 0, stop, step = 1) {
  let cur = (stop === undefined) ? 0 : start;
  let max = (stop === undefined) ? start : stop;
  for (let i = cur; step < 0 ? i > max : i < max; i += step)
    yield i
}
Volv
fonte
É assim que o intervalo realmente deve ser.
Konrad Linkowski
5

Para obter um array de tamanho x, aqui está um one-liner sem usar qualquer biblioteca

var range = n => Array(n + 1).join(1).split('').map((x, i) => i)

trabalha como

> range(4)
[0, 1, 2, 3]
bigOmega
fonte
2
var range = n => Array(n).fill().map((e, i) => i);
Valen
e por que dizer 'tamanho x' quando você realmente usou ncomo nome de parâmetro
Valen
5

O que se segue é uma adaptação natural da função range () do Python para o JavaScript:

// Generate range from start (inclusive) to stop (exclusive):
function* range(start, stop, step = 1) {
   if (stop === undefined) [start, stop] = [0, start];
   if (step > 0) while (start < stop) yield start, start += step;
   else if (step < 0) while (start > stop) yield start, start += step;
   else throw new RangeError('range() step argument invalid');
} 

// Examples:
console.log([...range(3)]);       // [0, 1, 2]
console.log([...range(0, 3)]);    // [0, 1, 2]
console.log([...range(0, 3, -1)]);// []
console.log([...range(0, 0)]);    // []
console.log([...range(-3)]);      // []
console.log([...range(-3, 0)]);   // [-3, -2, -1]

Ele suporta qualquer argumento que pode ser comparado a 0e stope pode ser incrementado por step. Ele se comporta de forma idêntica à versão Python quando usado com números que não excedem Number.MAX_SAFE_INTEGER.

Observe os seguintes casos:

[...range(0, 0, 0)];        // RangeError: range() step argument invalid
[...range(Number.MAX_SAFE_INTEGER + 1, Number.MAX_SAFE_INTEGER + 2)];  // []
[...range(Number.MAX_SAFE_INTEGER + 2, Number.MAX_SAFE_INTEGER + 3)];  // Infinite loop
[...range(0.7, 0.8, 0.1)];  // [0.7, 0.7999999999999999]
[...range('1', '11')];      // ['1']
[...range('2', '22')];      // Infinite loop

Em contraste com @ Tadeck de , @ Volv do e @ da janka102 resposta que retorno [], undefinedou entrar num ciclo infinito quando stepavalia a 0ou NaN, esta função de gerador gera uma exceção semelhante ao comportamento do Python.

le_m
fonte
Acordado. Embora as outras respostas sejam elegantes à sua maneira, essa abordagem e funcionalidade são muito mais pítônicas.
Travis Clarke
4

pythonicimita o rangecomportamento do Python da melhor maneira possível usando geradores JS ( yield), suportando os casos de uso range(stop)e range(start, stop, step). Além disso, pythonica rangefunção de retorna um Iteratorobjeto semelhante ao Python que suporta mape filter, portanto, pode-se fazer uma linha sofisticada como:

import {range} from 'pythonic';
// ...
const results = range(5).map(wouldBeInvokedFiveTimes);
// `results` is now an array containing elements from
// 5 calls to wouldBeInvokedFiveTimes

Instale usando npm:

npm install --save pythonic

Divulgação Sou autor e mantenedor do Pythonic

Keyvan
fonte
3

MDN recomenda esta abordagem: Gerador de sequência (alcance)

// Sequence generator function (commonly referred to as "range", e.g. Clojure, PHP etc)
const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step));

// Generate numbers range 0..4
console.log("range(0, 4, 1):", range(0, 4, 1));
// [0, 1, 2, 3, 4] 

// Generate numbers range 1..10 with step of 2 
console.log("\nrange(1, 10, 2):", range(1, 10, 2));
// [1, 3, 5, 7, 9]

// Generate the alphabet using Array.from making use of it being ordered as a sequence
console.log("\nrange('A'.charCodeAt(0), 'Z'.charCodeAt(0), 1).map(x => String.fromCharCode(x))", range('A'.charCodeAt(0), 'Z'.charCodeAt(0), 1).map(x => String.fromCharCode(x)));
// ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]

IliasT
fonte
2

Você pode usar a biblioteca de sublinhados . Ele contém dezenas de funções úteis para trabalhar com matrizes e muito mais.

Radagast
fonte
2

Existe uma função em JavaScript semelhante a range () do Python?

Todas as soluções aqui estão se referindo ao intervalo do Python 2 (provavelmente por causa do exemplo de código que você deu). No entanto, no Python 3, o método range () retorna um iterador. JavaScript também tem iteradores e são mais eficientes em termos de espaço do que gerar todo o array e armazená-lo na memória.

Portanto, a representação mais precisa da range(n)função do Python 3 é Array(n).keys().

Por exemplo:

for (let i of Array(n).keys()) {
  console.log(i) // 0, 1, 2, 3, ..., n
}

Mais um exemplo (que já foi abordado nas outras respostas). Convertendo o iterador em uma matriz (ES6):

let ary = [...Array(n).keys()];
// ary = [0, 1, 2, 3, ..., n]
MattCochrane
fonte
1

Ainda sem função embutida equivalente a range(), mas com a versão mais recente - ES2015 - você pode construir sua própria implementação. Aqui está uma versão limitada dele. Limitado porque não leva em consideração o parâmetro step. Apenas min, max.

const range = (min = null, max = null) =>
  Array.from({length:max ? max - min : min}, (v,k) => max ? k + min : k)

Isso é realizado pelo Array.frommétodo capaz de construir um array a partir de qualquer objeto que tenha uma lengthpropriedade. Portanto, passar um objeto simples com apenas a lengthpropriedade criará um ArrayIterator que produzirá um lengthnúmero de objetos.

Steve Brownlee
fonte
1

Esta é a minha forma preferida. Ele permite que você especifique uma ou duas entradas como no Python.

function range(start, end) {
  return Array.from(Array(end||start).keys()).slice(!!end*start)
}
Rob Kwasowski
fonte
0

Aqui está outra es6implementação do intervalo

// range :: (from, to, step?) -> [Number]
const range = (from, to, step = 1) => {
  //swap values if necesery
  [from, to] = from > to ? [to, from] : [from, to]
  //create range array
  return [...Array(Math.round((to - from) / step))]
    .map((_, index) => {
      const negative = from < 0 ? Math.abs(from) : 0
      return index < negative ? 
        from + index * step  :
        (index - negative + 1) * step
    })
}  

range(-20, 0, 5)
  .forEach(val => console.log(val))

for(const val of range(5, 1)){
   console.log(`value ${val}`)
}

mrFunkyWisdom
fonte
E se quisermos gerar um array de -20a -30?
Amin NAIRI
Hmm, este irá gerar um array de -30 a -20, mas pode ser facilmente modificado para incluir uma propriedade reversa, se desejar. Vou editar a resposta acima para incluí-la
mrFunkyWisdom
0

Não, não há nenhum, mas você pode fazer um.

Sou parcial para o comportamento de intervalo do Python3. Você encontrará a seguir a implementação do JavaScript do range () do Python:

function* range(start=0, end=undefined, step=1) {    
    if(arguments.length === 1) {end = start, start = 0}    
    
    [...arguments].forEach(arg => {    
        if( typeof arg !== 'number') {throw new TypeError("Invalid argument")}                               
    })    
    if(arguments.length === 0) {throw new TypeError("More arguments neede")}    
        
    if(start >= end) return                                                                                                                                     
    yield start    
    yield* range(start + step, end, step)    
}    
         
// Use Cases
console.log([...range(5)])

console.log([...range(2, 5)])

console.log([...range(2, 5, 2)])
console.log([...range(2,3)])
// You can, of course, iterate through the range instance.

elayira
fonte
0

Supondo que você precise de um intervalo simples com uma única etapa:

let range = (start, end)=> {
    if(start === end) return [start];
    return [start, ...range(start + 1, end)];
}

outro

let range = (start, end, step)=> {
    if(start === end) return [start];
    return [start, ...range(start + step, end)];
}

consulte aqui para obter mais informações.

N Djel Okoye
fonte
0

Existe uma função em JavaScript semelhante a range () do Python?

Como respondi antes: não , não há. Mas você pode fazer o seu próprio. Eu acredito que esta é uma abordagem interessante para ES6. Ele funciona de forma muito semelhante ao Python 2.7 range(), mas é muito mais dinâmico.

function range(start, stop, step = 1) 
{
    // This will make the function behave as range(stop)
    if(arguments.length === 1)
    {
        return [...Array(arguments[0]).keys()]
    }

    // Adjusts step to go towards the stop value
    if((start > stop && !(step < 0)) ||
       (start < stop && !(step > 0)))
    {
        step *= -1
    }

    let returnArray = []
    // Checks if i is in the interval between start and stop no matter if stop
    // is lower than start or vice-versa
    for(let i = start; (i-start)*(i-stop) <= 0; i += step)
    {
        returnArray.push(i)
    }
    return returnArray
}

Esta função pode se comportar de três maneiras diferentes (assim como range () do Python):

  1. range(stop)
  2. range(start, stop)
  3. range(start, stop, step)

Estes exemplos:

console.log(range(5))
console.log(range(-2, 2))
console.log(range(2, -2))
console.log(range(10, 20, 2))

Fornecerá a seguinte saída:

[ 0, 1, 2, 3, 4 ]
[ -2, -1, 0, 1, 2 ]
[ 2, 1, 0, -1, -2 ]
[ 10, 12, 14, 16, 18, 20 ]

Observe que, em vez de iterar sobre o array com o inoperador (como python), você deve usar of. Portanto, a ivariável assume o valor, e não o índice, do elemento da matriz.

for(let i of range(5))
{
    // do something with i...
}
sndmnn
fonte
0

Uma opção para NodeJs é usar um Buffer:

[...Buffer.alloc(5).keys()]
// [ 0, 1, 2, 3, 4 ]

O que é bom é que você pode iterar diretamente no buffer:

Buffer.alloc(5).forEach((_, index) => console.log(index))
// 0
// 1
// 2
// 3
// 4

Você não pode fazer isso com um Array não inicializado:

Array(5).forEach((_, index) => console.log(index))
// undefined

Mas, quem em sã consciência usa um Buffer para uma finalidade como esta;)

Otto
fonte
0
function range(start, stop) {
    if (typeof stop == 'undefined') {
        stop = start;
        start = 0;
    }
   
    result = [...Array(stop).keys()].slice(start, stop);
    return result;
}
MSA
fonte
-1

É assim que eu faço

let n = 5 
[...Array(n).keys()].map(x=>{console.log(x)})

resultado

0
1
2
3
4
Ricky
fonte
Isso não suporta etapas.
Mark Stosberg