Как сгенерировать диапазон чисел от 0 до n только в ES2015?


122

Я всегда обнаруживал, что rangeфункция отсутствует в JavaScript, поскольку она доступна в Python и других? Есть ли краткий способ сгенерировать диапазон чисел в ES2015?

РЕДАКТИРОВАТЬ: МОЙ вопрос отличается от упомянутого дубликата, поскольку он относится к ES2015, а не к ECMASCRIPT-5. Также мне нужно, чтобы диапазон начинался с 0, а не с конкретного начального номера (хотя было бы хорошо, если бы он был там)


Ответ одинаков для ES5 и ES6.
loganfsmyth

1
Но вы всегда можете использовать некоторые новые концепции, такие как генераторы, новые методы массива и т. Д. В ES2015. Это дает вам дополнительный набор инструментов для выполнения задачи
Адитья Сингх

7
Я думаю , что @Delapouite имеет идеальный ответ на это в комментариях к ответу на вопрос дублируется : [...Array(n).keys()].
jib


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

Ответы:


244

Вы можете использовать оператор распространения для ключей только что созданного массива.

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

или

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

Array.from()Синтаксис необходим при работе с машинописью


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

2
Это не работает в машинописном тексте, потому что keys () возвращает итератор массива вместо массива. Ознакомьтесь с ответом Адитья-Сингха для более универсального подхода.
Дэвид Доминго

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

2
@DavidGonzalezShannon Знаете, почему [...Array(n).keys()]не работает в Typescript? Является ли это преднамеренным отклонением от других реализаций JS?
Stu Cox

Привет, @StuCox, я понятия не имею, почему, но он переносит его, Array(5).keys().slice()а slice не является методом итератора массива. Вот пример того, что это не работает typescriptlang.org/play/…
Дэвид Доминго

98

Я также нашел еще один интуитивно понятный способ Array.from:

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

Теперь эта rangeфункция вернет все числа от 0 до n-1.

Модифицированная версия диапазона для поддержки startи endявляется:

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

EDIT Как было предложено @ marco6, вы можете поместить это как статический метод , если он подходит для вашего случая использования

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

и использовать его как

Array.range(3, 9)

1
Хороший! Почему бы нам не расширить с его помощью статический интерфейс Array? В машинописном тексте отлично работает с: interface ArrayConstructor { range(n: number): number[]; } Array.range = n => Array.from({length: n}, (value, key) => key); А потом вездеArray.range(x)...
marco6 03

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

1
Переопределение встроенных модулей сейчас считается плохой практикой в ​​javascript.
jhohlfeld

16

С Дельтой

Для 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

Для машинописного текста

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

Обновить

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

редактировать

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

Ваша обновленная версия TypeScript не работает. Создает пустой массив указанного размера. Вам нужно использовать Array.from с Array.keys с TypeScript. Array.from(Array(~~((to - from) / step) + 1).keys())
Дэвид Доминго


10

Многие из этих решений основаны на создании экземпляров реальных объектов Array, которые могут выполнять работу во многих случаях, но не могут поддерживать такие случаи, как range(Infinity). Вы можете использовать простой генератор, чтобы избежать этих проблем и поддерживать бесконечные последовательности:

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

Примеры:

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

Итак, в этом случае было бы неплохо, если бы объект Number вел себя как объект Array с оператором распространения.

Например, объект Array, используемый с оператором распространения:

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

Это работает так, потому что объект Array имеет встроенный итератор.
В нашем случае нам нужен объект Number для аналогичной функциональности:

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

Для этого мы можем просто создать для этой цели числовой итератор.

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

Теперь с помощью оператора распространения можно создавать диапазоны от 0 до N.

[... N] // теперь возвращает массив 0 ... N

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

Приветствия.


3

Вы можете использовать функцию генератора, которая лениво создает диапазон только при необходимости:

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

Вы можете использовать функцию генератора более высокого порядка для сопоставления с rangeгенератором:

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

Если вы бесстрашны, вы можете даже обобщить подход генератора для решения гораздо более широкого круга задач (каламбур):

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

Имейте в виду, что генераторы / итераторы по своей сути сохраняют состояние, то есть неявное изменение состояния происходит при каждом вызове next. Состояние - неоднозначное благо.


3

Диапазон с шагом ES6, который работает аналогично 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)
}

Примеры:

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
Хорошее добавление к вопросу! Это помогло мне получить намного более чистый код в моих шаблонах циклов Angular 8 html * ngFor.
Сэм

2

Для поддержки дельты

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

1

Вы также можете сделать это с одинарным вкладышем со ступенчатой ​​опорой, например:

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

Результат есть [0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9].


2
Это Y-комбинатор?
TheChetan

1
Он следует идее Y-комбинатора.
Marcin Król

1

Эта функция вернет целочисленную последовательность.

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

в машинописном тексте


Нет причин использовать оба Array.fromи распространять синтаксис. И тогда это точно так же, как существующий ответ.
Берги

Просто хочу отметить, что [...Array(n).keys()]это не работает в Typescript.
PeiSong

3
Тогда используйте Array.from(Array(n).keys()). Я почти уверен, что это должно сработать, к чему переносится литерал с синтаксисом распространения?
Берги

0

Вот еще один вариант, который не используется Array.

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

0

Генераторы теперь позволяют вам лениво генерировать числовые последовательности и использовать меньше памяти для больших диапазонов.

Хотя в вопросе конкретно говорится о ES2015, я ожидаю, что здесь окажется много пользователей Typescript, и переход на ES прост ...

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

Первые два объявления функций предназначены только для предоставления более информативных предложений завершения в вашей среде IDE.


Аааа, и вы можете сказать, что я не прочитал все существующие ответы перед публикацией: - /
Дэйв

0

Как насчет картографирования ....

Array (n) .map ((value, index) ....) находится на 80% пути туда. Но по какой-то непонятной причине не работает. Но есть обходной путь.

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

Для диапазона

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

Как ни странно, эти два итератора возвращают один и тот же результат: Array(end-start+1).entries()иArray(end-start+1).fill().entries()

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.