【问题标题】:How to generate range of numbers from 0 to n in ES2015 only?如何仅在 ES2015 中生成从 0 到 n 的数字范围?
【发布时间】:2016-08-25 04:13:48
【问题描述】:

我总是发现 JavaScript 中缺少 range 函数,因为它在 python 和其他中可用?在 ES2015 中是否有任何简洁的方法来生成数字范围?

编辑:我的问题与提到的重复项不同,因为它特定于 ES2015 而不是 ECMASCRIPT-5。此外,我需要范围从 0 开始,而不是特定的起始数字(如果有的话会很好)

【问题讨论】:

标签: javascript arrays ecmascript-6


【解决方案1】:

您可以在新创建的数组的键上使用扩展运算符。

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

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

如果使用 TypeScript,Array.from() 语法是必需的

【讨论】:

  • 甜蜜:function range (start, end) { return [...Array(1+end-start).keys()].map(v => start+v) }
  • 这在 typescript 中不起作用,因为 keys() 返回一个数组迭代器而不是一个数组。查看 aditya-singh 的答案以获得更通用的方法。
  • …… 或 Array.from(Array(n).keys()).
  • @DavidGonzalezShannon 你知道为什么[...Array(n).keys()] 在 Typescript 中不起作用吗?是故意偏离其他 JS 实现吗?
  • 嘿@StuCox 我不知道为什么,但它会将其转换为Array(5).keys().slice(),并且 slice 不是数组迭代器的方法。这是一个不工作的例子typescriptlang.org/play/…
【解决方案2】:

我还发现了一种更直观的使用Array.from的方法:

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

现在这个range函数将返回从0到n-1的所有数字

支持startend 的范围的修改版本是:

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)

【讨论】:

  • 不错的一个!为什么我们不用它扩展 Array 静态接口呢?在 typescript 中很好用:interface ArrayConstructor { range(n: number): number[]; } Array.range = n => Array.from({length: n}, (value, key) => key); 然后无处不在 Array.range(x)...
  • [ts] Property 'range' does not exist on type 'ArrayConstructor'。千分之几?
  • 现在在 javascript 中重写内置函数被认为是不好的做法。
【解决方案3】:

有 Delta/Step

最小的单线
[...Array(N)].map((_, i) => from + i * step);

示例和其他替代方法

[...Array(10)].map((_, i) => 4 + i * 2);
//=> [4, 6, 8, 10, 12, 14, 16, 18, 20, 22]

Array.from(Array(10)).map((_, i) => 4 + i * 2);
//=> [4, 6, 8, 10, 12, 14, 16, 18, 20, 22]

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((_, i) => 4 + i * 2);
//=> [4, 6, 8, 10, 12, 14, 16, 18, 20, 22]

Array(10).fill().map((_, i) => 4 + i * -2);
//=> [4, 2, 0, -2, -4, -6, -8, -10, -12, -14]
范围函数
const range = (from, to, step) =>
  [...Array(Math.floor((to - from) / step) + 1)].map((_, i) => from + i * step);

range(0, 9, 2);
//=> [0, 2, 4, 6, 8]

// can also assign range function as static method in Array class (but not recommended )
Array.range = (from, to, step) =>
  [...Array(Math.floor((to - from) / step) + 1)].map((_, i) => from + i * 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
仅作为发电机
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

// Lazy loaded way
const number0toInf = Range(Infinity);
number0toInf.next().value;
//=> 0
number0toInf.next().value;
//=> 1
// ...

From-To with steps/delta

使用迭代器
class Range2 {
  constructor(to = 0, step = 1, from = 0) {
    this[Symbol.iterator] = function* () {
      let i = 0,
        length = Math.floor((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]
使用生成器
const Range2 = function* (to = 0, step = 1, from = 0) {
  let i = 0,
    length = Math.floor((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(Math.floor((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(Math.floor((to - from) / step) + 1)).map(
      (v, k) => from + k * step
    );
  }
}
_Array.range(0, 9, 1);

【讨论】:

  • 您更新的 TypeScript 版本不起作用。它创建一个具有指定大小的空数组。您需要将 Array.from 与 A​​rray.keys 与 TypeScript 一起使用。 Array.from(Array(~~((to - from) / step) + 1).keys())
【解决方案4】:

对于数字 0 到 5

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

【讨论】:

    【解决方案5】:

    很多这些解决方案都建立在实例化真正的 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 }
    

    【讨论】:

      【解决方案6】:

      因此,在这种情况下,如果 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/

      干杯。

      【讨论】:

        【解决方案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 都会隐式更改状态。国家是喜忧参半。

        【讨论】:

          【解决方案8】:

          范围与步骤 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]
          

          【讨论】:

          • 很好地补充了这个问题!这帮助我在 Angular 8 html *ngFor 循环模板中获得了更清晰的代码。
          【解决方案9】:

          支持增量

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

          【讨论】:

            【解决方案10】:

            只是映射怎么样....

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

            【讨论】:

              【解决方案11】:

              您也可以使用带有阶梯支撑的单衬管,例如:

              ((from, to, step) =&gt; ((add, arr, v) =&gt; add(arr, v, add))((arr, v, add) =&gt; v &lt; to ? add(arr.concat([v]), v + step, add) : arr, [], from))(0, 10, 1)

              结果是[0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9]

              【讨论】:

              • 这是 Y 组合器吗?
              • 它遵循Y-combinator的思想。
              【解决方案12】:

              这个函数将返回一个整数序列。

              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 ]
              

              【讨论】:

                【解决方案13】:
                const keys = Array(n).keys();
                [...Array.from(keys)].forEach(callback);
                

                在打字稿中

                【讨论】:

                • 没有理由同时使用Array.from 和传播语法。然后它与现有答案完全相同。
                • 只是想指出 [...Array(n).keys()] 在 Typescript 中不起作用。
                • 然后使用Array.from(Array(n).keys())。我很确定它应该可以工作,带有扩展语法的文字转换成什么?
                【解决方案14】:

                这是另一个不使用Array 的变体。

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

                【讨论】:

                  【解决方案15】:

                  生成器现在允许您懒惰地生成数字序列,并为大范围使用更少的内存。

                  虽然问题具体说明了 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 中提供更多信息完成建议。

                  【讨论】:

                  • Aaaa 你可以看出我在发布之前没有阅读所有现有答案:-/
                  猜你喜欢
                  • 1970-01-01
                  • 2010-10-20
                  • 1970-01-01
                  • 2011-10-20
                  • 2010-11-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多