จะสร้างช่วงของตัวเลขจาก 0 ถึง n ใน ES2015 เท่านั้นได้อย่างไร


121

ฉันพบว่าrangeฟังก์ชันที่ขาดหายไปจาก JavaScript เสมอเนื่องจากมีใน python และอื่น ๆ ? มีวิธีที่กระชับในการสร้างช่วงของตัวเลขใน ES2015 หรือไม่?

แก้ไข: คำถามของฉันแตกต่างจากที่กล่าวถึงซ้ำเนื่องจากเป็นคำถามเฉพาะสำหรับ ES2015 และไม่ใช่ ECMASCRIPT-5 นอกจากนี้ฉันต้องการให้ช่วงเริ่มต้นจาก 0 และไม่ใช่หมายเลขเริ่มต้นที่เฉพาะเจาะจง (แม้ว่าจะดีถ้ามี)


คำตอบเหมือนกันสำหรับ ES5 และ ES6
loganfsmyth

1
แต่คุณสามารถใช้แนวคิดใหม่ ๆ เช่นเครื่องกำเนิดไฟฟ้าวิธีอาร์เรย์ใหม่เป็นต้นใน ES2015 นั่นทำให้คุณมีชุดเครื่องมือพิเศษเพื่อให้บรรลุภารกิจ
Aditya Singh

7
ผมคิดว่า @Delapouite มีคำตอบที่สมบูรณ์แบบนี้ในความคิดเห็นในคำตอบของคำถามที่ซ้ำกัน[...Array(n).keys()] :
jib


2
[...Array(5)].map((_,i) => i+1)
ชื่อเล่นว่า

คำตอบ:


243

คุณสามารถใช้ตัวดำเนินการกระจายบนคีย์ของอาร์เรย์ที่สร้างขึ้นใหม่ได้

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

หรือ

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

Array.from()ไวยากรณ์เป็นสิ่งจำเป็นหากทำงานกับ typescript


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

2
สิ่งนี้ใช้ไม่ได้ใน typescript เนื่องจาก keys () ส่งคืน Array Iterator แทน Array ชำระเงินคำตอบของ aditya-singh สำหรับแนวทางที่เป็นสากลมากขึ้น
David Domingo

3
……หรือArray.from(Array(n).keys()).
КонстантинВан

2
@DavidGonzalezShannon คุณรู้ไหมทำไม[...Array(n).keys()]ไม่ทำงานใน typescript? เป็นการเบี่ยงเบนโดยเจตนาจากการใช้งาน JS อื่น ๆ หรือไม่?
Stu Cox

เฮ้ @StuCox ฉันไม่รู้ว่าทำไม แต่มันเปลี่ยนเป็นArray(5).keys().slice()และสไลซ์ไม่ใช่วิธีการวนซ้ำอาร์เรย์ นี่คือตัวอย่างของมันไม่ทำงานtypescriptlang.org/play/…
David Domingo

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

แก้ไข ตามที่แนะนำโดย @ marco6 คุณสามารถวางวิธีนี้เป็นวิธีการคงที่ได้หากเหมาะสมกับกรณีการใช้งานของคุณ

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

และใช้เป็น

Array.range(3, 9)

1
ทำได้ดีนี่! ทำไมเราไม่ขยาย Array static interface ด้วยล่ะ? ใน typescript ใช้งานได้ดีกับ: interface ArrayConstructor { range(n: number): number[]; } Array.range = n => Array.from({length: n}, (value, key) => key); แล้วก็ทุกที่Array.range(x)...
marco6

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

1
การลบล้างบิวด์อินถือเป็นแนวทางปฏิบัติที่ไม่ดีในจาวาสคริปต์
jhohlfeld

16

ด้วย Delta

สำหรับ 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

สำหรับ typescript

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


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 object จะทำงานเหมือน Array object ที่มีตัวดำเนินการกระจาย

ตัวอย่างเช่นวัตถุ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 iterator เพื่อจุดประสงค์นั้นได้

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

ตอนนี้คุณสามารถสร้างช่วงตั้งแต่ 0 ถึง N ด้วยตัวดำเนินการกระจาย

[... N] // คืนค่า 0 ... N array

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 loop
แซม

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-combinator ใช่ไหม
TheChetan

1
เป็นไปตามแนวคิดของ Y-combinator
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);

ใน typescript


ไม่มีเหตุผลที่จะใช้ทั้งสองArray.fromและแพร่กระจายไวยากรณ์ แล้วมันก็เหมือนกับคำตอบที่มีอยู่
Bergi

แค่อยากจะชี้ให้เห็นว่าใช้[...Array(n).keys()]ไม่ได้ใน typescript
PeiSong

3
Array.from(Array(n).keys())การใช้งานแล้ว ฉันค่อนข้างแน่ใจว่ามันควรจะใช้งานได้ แต่ลิเทอรัลที่มีไวยากรณ์การแพร่กระจายจะเปลี่ยนไปเป็นอย่างไร
Bergi


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 ของคุณ


และคุณสามารถบอกได้ว่าฉันไม่ได้อ่านคำตอบที่มีอยู่ทั้งหมดก่อนที่จะโพสต์: - /
Dave

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.