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

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

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

16 ответов

Решение

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

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

или же

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

Array.from() синтаксис необходим при работе с TypeScript

Я также нашел еще один интуитивно понятный способ использования 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);

РЕДАКТИРОВАТЬ В соответствии с предложением @marco6, вы можете использовать это как статический метод, если он подходит для вашего случая использования.

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

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

Array.range(3, 9)

С дельтой

Для 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, 2, 0, -2, -4, -6, -8, -10, -12, -14]

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

Для номеров от 0 до 5

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

Многие из этих решений основаны на создании экземпляров реальных объектов 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 }

Таким образом, в этом случае было бы хорошо, если бы объект 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.

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

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

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

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

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

Диапазон с шагом 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]

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

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, Государство - это смешанное благословение.

Как насчет простого отображения....

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

Поддержать дельту

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

Еще несколько способов сделать

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

const integerRange = (start, end, n = start, arr = []) =>
  (n === end) ? [...arr, n]
    : integerRange(start, end, start < end ? n + 1 : n - 1, [...arr, n]);

$> intSequence(1, 1)
<- Array [ 1 ]

$> intSequence(1, 3)
<- Array(3) [ 1, 2, 3 ]

$> intSequence(3, -3)
<- Array(7) [ 3, 2, 1, 0, -1, -2, -3 ]

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

((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],

const keys = Array(n).keys();
[...Array.from(keys)].forEach(callback);

в машинописи

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

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

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

Хотя в вопросе конкретно говорится о 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.

Другие вопросы по тегам