Funkcja JavaScript podobna do zakresu Pythona ()

103

Czy w JavaScript jest funkcja podobna do tej w Pythonie range()?

Myślę, że powinien być lepszy sposób niż pisanie za każdym razem następujących wierszy:

array = new Array();
for (i = 0; i < specified_len; i++) {
    array[i] = i;
}
clwen
źródło
1
@clwen: Niestety nie ma, ale spójrz na mój kod - napisałem funkcję, która ma na celu emulowanie sposobu range()działania w Pythonie, więc możesz z niej korzystać. W JavaScript nie ma takiej funkcji, ale jest kilka wtyczek dla różnych frameworków, takich jak Rangeklasa dla MooTools .
Tadeck

Odpowiedzi:

92

Nie , nie ma, ale możesz to zrobić .

Implementacja języka Python w JavaScript range()

Próbując emulować jak to działa w Pythonie , stworzyłbym funkcję podobną do tej:

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

Zobacz to jsfiddle, aby uzyskać dowód.

Porównanie między range()JavaScript i Python

Działa to w następujący sposób:

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

a jego odpowiednik w Pythonie działa dokładnie w ten sam sposób (przynajmniej we wspomnianych przypadkach):

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

Jeśli więc potrzebujesz funkcji działającej podobnie do Pythona range(), możesz skorzystać z powyższego rozwiązania.

Tadeck
źródło
4
może kilka dodatkowych testów obronnych - upewnij się, że wszystkie przekazywane argumenty są przymusowe do liczb i upewnij się, że stopjest większe niż start(i zamień je, jeśli nie).
Russ Cam
1
@RussCam: Dzięki za wskazanie tego. Nie dodałem sprawdzania obronnego dla typów itp., Ale zaimplementowałem odwrotną kolejność elementów - teraz działa dokładnie tak samo jak odpowiednik w Pythonie, gdy ostatni parametr jest ujemną liczbą całkowitą.
Tadeck,
@RussCam: start >= stopprowadzenie do pustej tablicy jest konieczne, jeśli celem jest rzeczywiście emulacja zakresu Pythona. I uważam, że i tak jest bardziej intuicyjny.
1
@delnan: Sprawdzanie może być bardziej złożone, ponieważ proste start >= stopnie wystarczy, aby ta funkcja zachowywała się jak range()w Pythonie. Zaktualizowałem moją odpowiedź.
Tadeck,
@delnan - nie znam implementacji Pythona. Myślę, że jeśli to będzie tylko być wykorzystywane przez ćwierkania znających realizacji Python, że to ma sens, aby go naśladować :)
Russ Cam
130

Dla bardzo prostego zakresu w ES6:

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

Z komentarza bigOmega można to skrócić za pomocą składni Spread :

let range = n => [...Array(n).keys()]
Will Ediger
źródło
41
Prostsza wersja tolet range = n => [...Array(n).keys()]
bigOmega
2
@BharathRaja świetnie, dzięki! Dlaczego nie const? Postaram się użyć const gdzie mogę :) Podoba końcowego odpowiednikiem Java;)
Kjellski
Masz rację, staram się używać constjak najwięcej, ale tutaj będzie to tylko odniesienie do tablicy, więc tablica nadal byłaby edytowalna: 'D
bigOmega
1
@bigOmega Dotyczy constto samej funkcji, a nie jej wartości zwracanej. Prawdopodobnie nie chcesz w żaden sposób modyfikować funkcji.
Solomon Ucko
7
Ta odpowiedź nie uwzględnia indeksu początkowego i możliwości zwiększenia rozmiaru kroku.
Fluous
31

2018: ta odpowiedź wciąż zyskuje na popularności, więc oto aktualizacja. Poniższy kod jest przestarzały, ale na szczęście standardowe generatory ES6 i yieldsłowo kluczowe są powszechnie obsługiwane na różnych platformach. Przykład leniwego range()używania yieldmożna znaleźć tutaj .


Oprócz tego, co już zostało powiedziane, Javascript 1.7+ zapewnia obsługę iteratorów i generatorów, które mogą być używane do tworzenia leniwej, wydajnej pamięciowo wersji programu podobnego rangedo xrangew Pythonie2:

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
źródło
1
+1 Świetny pomysł! Czy mógłbyś również zaimplementować stepargument i przetestować go na wartościach z mojej odpowiedzi ? Twoja odpowiedź jest świetna w przypadku aplikacji, dla których mamy na myśli bardzo specyficzne przeglądarki (nie będzie działać w Google Chrome, Safari i IE w wersjach wcześniejszych niż 9: stackoverflow.com/a/2209743/548696 ).
Tadeck,
@Tadeck: Jak na ironię, ostatnio zadałem bardzo podobne pytanie , sprawdź - kilka dobrych odpowiedzi. BTW, twój kod nie przechodzi mojego testu; (
georg
Czy mógłbyś podzielić się danymi testowymi i oczekiwanymi wynikami? Chętnie bym go poprawił, ale moje testy są w 100% zdane. Czy twierdzisz, że kod, który podałem, nie jest poprawnie przeanalizowany przez skrypt, który umieściłeś w tym pytaniu: stackoverflow.com/q/12173856/548696 ?
Tadeck
@Tadeck: nevermind. Przetestowałem plasterki, a Twój kod dotyczy zakresów.
georg
1
Python rangema wykluczoną „ostatnią” wartość (więc należy użyć jej >=zamiast >w powyższym kodzie)
Pac0
25

Łącząc obie odpowiedzi z @Tadeck i @georg , wymyśliłem to:

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

Aby użyć go w pętli for, potrzebujesz pętli 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
źródło
Dlaczego nie używać parametrów domyślnych, jeśli używasz ES6?
Amin NAIRI
@Gradiuss, który działałby dla stepparametru, ale gdy stopnie jest przekazywany, oba starti stopmuszą zostać zmienione. Zaktualizuję to
janka102
2
WTG, to najlepsza dotychczas prezentowana realizacja. Używa generatorów (dobrze, bo nie ma potrzeby przechowywania całej sekwencji w pamięci) i jest bardzo zwięzła.
Lucio Paiva
undefined to wskaźnik do obiektu takiego jak null. można porównać z 3 znakami równości, takimi jak: if (stop === undefined) {3 znaki równości są porównywane bez automatycznego rzutowania. porównaj tak samo, jak również typ porównania. 2 znaki równości porównuje się z automatycznym rzutem na inny typ strony.
Shimon Doodkin,
21

Port rangefunkcji z Pythona 2 jest udostępniany przez biblioteki narzędziowe underscore.js i lodash (wraz z wieloma innymi przydatnymi narzędziami). Przykłady skopiowane z podkreślonych dokumentów:

_.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
źródło
16

Można to osiągnąć, dołączając iterator do Numberprototypu

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

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

Zaczerpnięte z kursu Kyle'a Simpsona Rethinking Asynchronous JavaScript

mcha
źródło
8
To jest naprawdę fajne, ale zastępowanie prototypów jest po prostu zbyt delikatne: '(
m0meni
9

Oto małe rozszerzenie jednej z odpowiedzi na wypadek, gdybyś musiał określić zarówno początkową, jak i końcową pozycję zakresu:

let range = (start, end) => Array.from(Array(end + 1).keys()).slice(start);
Dmitrij Michajłow
źródło
7

Proszę bardzo.

Spowoduje to zapisanie (lub nadpisanie) wartości każdego indeksu wraz z numerem indeksu.

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

Jeśli nie podasz liczby, użyje bieżącej długości tablicy.

Użyj tego w ten sposób:

var array = [].writeIndices(10);  // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
RightSaidFred
źródło
5

Dalsze udoskonalenie dzięki domyślnym parametrom 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
źródło
Tak naprawdę powinna wyglądać seria.
Konrad Linkowski
5

Aby uzyskać tablicę rozmiarów x, oto jedna linijka bez użycia żadnej biblioteki

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

pracuje jako

> range(4)
[0, 1, 2, 3]
bigOmega
źródło
2
var range = n => Array(n).fill().map((e, i) => i);
Valen
i po co mówić „rozmiar x”, kiedy faktycznie nużywałeś nazwy parametru
Valen
5

Poniżej przedstawiono naturalną adaptację funkcji range () języka Python do 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]

Obsługuje żadnego argumentu, który można porównać do 0a stopi może być zwiększana o step. Zachowuje się identycznie jak wersja Pythona, gdy jest używana z liczbami nieprzekraczającymiNumber.MAX_SAFE_INTEGER .

Zwróć uwagę na następujące przypadki narożne:

[...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

W przeciwieństwie do @ Tadeck użytkownika , @ Volv użytkownika i użytkownika @ janka102 odpowiedź których powrót [], undefinedalbo wejść w nieskończoną pętlę, gdy stepocenia się 0czy NaNfunkcja ta generator zgłasza wyjątek podobny do zachowania Pythona.

le_m
źródło
Zgoda. Chociaż inne odpowiedzi są eleganckie na swój sposób, to podejście i funkcjonalność są znacznie bardziej pytoniczne.
Travis Clarke
4

pythonicnaśladuje rangezachowanie Pythona najlepiej, jak potrafi, używając generatorów JS ( yield), obsługujących zarówno przypadki użycia , jak range(stop)i range(start, stop, step). Ponadto, pythonicjest rangefunkcja zwraca Iteratorobiekt podobny do Pythona, który obsługuje mapi filtertak można zrobić fantazyjny jednej wkładki, takich jak:

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

Zainstaluj za pomocą npm:

npm install --save pythonic

Ujawnienie Jestem autorem i opiekunem Pythonic

Keyvan
źródło
3

MDN zaleca takie podejście: Generator sekwencji (zakres)

// 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
źródło
2

Możesz użyć biblioteki podkreślenia . Zawiera dziesiątki przydatnych funkcji do pracy z tablicami i wiele innych.

Radagast
źródło
2

Czy w JavaScript jest funkcja podobna do range () w Pythonie?

Wszystkie przedstawione tutaj rozwiązania odnoszą się do zakresu Pythona 2 (prawdopodobnie z powodu podanego przykładu kodu). Jednak w Pythonie 3 metoda range () zwraca iterator. JavaScript ma również iteratory i są one bardziej wydajne pod względem miejsca niż generowanie całej tablicy i przechowywanie jej w pamięci.

Tak więc dokładniejsza reprezentacja funkcji Pythona 3 range(n)to Array(n).keys().

Na przykład:

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

Jeszcze jeden przykład (który został już omówiony w innych odpowiedziach). Konwersja iteratora na tablicę (ES6):

let ary = [...Array(n).keys()];
// ary = [0, 1, 2, 3, ..., n]
MattCochrane
źródło
1

Wciąż nie ma wbudowanej funkcji, która range()byłaby równoważna , ale w najnowszej wersji - ES2015 - możesz zbudować własną implementację. Oto jego ograniczona wersja. Ograniczone, ponieważ nie uwzględnia parametru kroku. Tylko min, max.

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

Osiąga się to za pomocą Array.frommetody zdolnej do zbudowania tablicy z dowolnego obiektu, który ma lengthwłaściwość. Zatem przekazanie prostego obiektu tylko z tą lengthwłaściwością utworzy ArrayIterator, który dostarczy lengthliczbę obiektów.

Steve Brownlee
źródło
1

To jest mój ulubiony sposób. Umożliwia określenie jednego lub dwóch danych wejściowych, jak w Pythonie.

function range(start, end) {
  return Array.from(Array(end||start).keys()).slice(!!end*start)
}
Rob Kwasowski
źródło
0

Oto kolejna es6realizacja tej serii

// 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
źródło
A co, jeśli chcemy wygenerować tablicę od -20do -30?
Amin NAIRI,
Hmm, ten wygeneruje tablicę od -30 do -20, ale można go łatwo zmodyfikować, aby zawierał właściwość reverse, jeśli chcesz.
Zmienię
0

Nie, nie ma, ale możesz to zrobić.

Jestem stronniczy w zachowaniu range w Pythonie3. Poniżej znajdziesz implementację zakresu Pythona () w JavaScript:

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
źródło
0

Zakładając, że potrzebujesz prostego zakresu z jednym krokiem:

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

jeszcze

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

zajrzyj tutaj po więcej.

N Djel Okoye
źródło
0

Czy w JavaScript jest funkcja podobna do range () w Pythonie?

Jak odpowiedziałem wcześniej: nie , nie ma. Ale możesz zrobić własne. Uważam, że jest to interesujące podejście do ES6. Działa bardzo podobnie do Pythona 2.7 range(), ale jest znacznie bardziej dynamiczny.

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
}

Ta funkcja może działać na trzy różne sposoby (podobnie jak zakres () w Pythonie):

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

Te przykłady:

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

Da ci następujące dane wyjściowe:

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

Zauważ, że zamiast iterować po tablicy za pomocą inoperatora (jak python), musisz użyć of. W ten sposób izmienna przyjmuje wartość, a nie indeks, elementu tablicy.

for(let i of range(5))
{
    // do something with i...
}
sndmnn
źródło
0

Opcją dla NodeJs jest użycie bufora:

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

Fajne jest to, że możesz iterować bezpośrednio w buforze:

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

Nie możesz tego zrobić z niezainicjowaną tablicą:

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

Ale kto przy zdrowych zmysłach używa bufora do takiego celu;)

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

Oto jak to robię

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

wynik

0
1
2
3
4
Ricky
źródło
To nie obsługuje kroków.
Mark Stosberg,