JavaScript มีวิธีเช่น "range ()" เพื่อสร้างช่วงภายในขอบเขตที่ให้มาหรือไม่?


870

ใน PHP คุณสามารถทำ ...

range(1, 3); // Array(1, 2, 3)
range("A", "C"); // Array("A", "B", "C")

นั่นคือมีฟังก์ชั่นที่ช่วยให้คุณได้รับช่วงของตัวเลขหรือตัวอักษรโดยผ่านขอบเขตบนและล่าง

มีอะไรในตัว JavaScript สำหรับกำเนิดนี้? ถ้าไม่ฉันจะใช้มันอย่างไร?


1
Prototype.js มี$Rฟังก์ชั่น แต่นอกเหนือจากนั้นฉันไม่คิดอย่างนั้น
Yi Jiang

คำถาม (ที่เกี่ยวข้อง) นี้มีคำตอบที่ยอดเยี่ยม: stackoverflow.com/questions/6299500/…
btk

Array.from("ABC") //['A', 'B', 'C']นี่คือสิ่งที่ใกล้เคียงที่สุดที่ฉันสามารถหาได้ในส่วนที่สองของคำถาม
Andrew_1510

@ Andrew_1510 คุณสามารถใช้ที่split("")นั่นได้ด้วย
alex

1
เมื่อคนรักถูก จำกัด อยู่ที่ศูนย์นี้:Array.apply(null, { length: 10 }).map(eval.call, Number)
csharpfolk

คำตอบ:


1498

เบอร์

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

ตัวละครซ้ำ

String.fromCharCode(...[...Array('D'.charCodeAt(0) - 'A'.charCodeAt(0) + 1).keys()].map(i => i + 'A'.charCodeAt(0)));
 => "ABCD"

การย้ำ

for (const x of Array(5).keys()) {
  console.log(x, String.fromCharCode('A'.charCodeAt(0) + x));
}
 => 0,"A" 1,"B" 2,"C" 3,"D" 4,"E"

เป็นฟังก์ชั่น

function range(size, startAt = 0) {
    return [...Array(size).keys()].map(i => i + startAt);
}

function characterRange(startChar, endChar) {
    return String.fromCharCode(...range(endChar.charCodeAt(0) -
            startChar.charCodeAt(0), startChar.charCodeAt(0)))
}

ฟังก์ชั่นที่พิมพ์

function range(size:number, startAt:number = 0):ReadonlyArray<number> {
    return [...Array(size).keys()].map(i => i + startAt);
}

function characterRange(startChar:string, endChar:string):ReadonlyArray<string> {
    return String.fromCharCode(...range(endChar.charCodeAt(0) -
            startChar.charCodeAt(0), startChar.charCodeAt(0)))
}

_.range()ฟังก์ชันlodash.js

_.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]
String.fromCharCode(..._.range('A'.charCodeAt(0), 'D'.charCodeAt(0) + 1));
 => "ABCD"

เบราว์เซอร์ที่ไม่ใช่ es6 รุ่นเก่าที่ไม่มีไลบรารี:

Array.apply(null, Array(5)).map(function (_, i) {return i;});
 => [0, 1, 2, 3, 4]

console.log([...Array(5).keys()]);

(เครดิต ES6 สำหรับ nils petersohn และผู้แสดงความคิดเห็นอื่น ๆ )


72
เพราะถ้ามันมีประโยชน์ทุกที่มันน่าจะมีประโยชน์ใน JS (JS สามารถทำสิ่งที่ประเภทการเขียนโปรแกรมการทำงานซึ่งจะได้รับประโยชน์จากช่วง (0 คำสั่งที่และเหตุผลอื่น ๆ อีกพันเหตุผลที่มันอาจจะมีประโยชน์ในบางกรณี semirare)
Lodewijk

5
ความคิดใดที่ว่าทำไมการใช้เพียงอย่างเดียว(new Array(5)).map(function (value, index) { return index; })ไม่ได้ผล? ผลตอบแทนนี้[undefined × 5]สำหรับฉันใน Chrome DevTools
ลูอิส

12
@Lewis เนื่องจากอาร์เรย์ที่กำหนดด้วยมีช่องว่างที่จะไม่วนซ้ำกับmap()หรือเป็นหนึ่งในเพื่อน
alex

65
Array.from (Array (5) .keys ())
nils petersohn

17
Array(5).fill()สามารถใช้แผนที่ได้เช่นกัน
nils petersohn

333

สำหรับตัวเลขที่คุณสามารถใช้ ES6 Array.from(), ที่ทำงานในทุกสิ่งที่วันนี้ยกเว้น IE:

รุ่นที่สั้นกว่า:

Array.from({length: 20}, (x,i) => i);

รุ่นที่ยาวกว่า:

Array.from(new Array(20), (x,i) => i)

ซึ่งสร้างอาร์เรย์จาก 0 ถึง 19 รวม สิ่งนี้สามารถย่อให้สั้นลงในรูปแบบใดรูปแบบหนึ่งต่อไปนี้:

Array.from(Array(20).keys())
// or
[...Array(20).keys()]

ขอบเขตล่างและบนสามารถระบุได้เช่นกัน:

Array.from(new Array(20), (x,i) => i + *lowerBound*)

บทความอธิบายรายละเอียดเพิ่มเติมนี้: http://www.2ality.com/2014/05/es6-array-methods.html


50
ตัวอย่างแรกสามารถทำให้ง่ายขึ้นเป็น [... Array (20) .keys ()]
Delapouite

27
รวบรัดมากกว่าArray.from()วิธีเล็กน้อยเล็กน้อยและเร็วกว่าทั้งคู่:Array(20).fill().map((_, i) => i)
Stu Cox

2
@Delapouite สุดยอด! คุณควรทำให้เป็นคำตอบที่แยกจากกันและฉันจะลงคะแนนให้! นอกจากนี้ยังเป็นคำตอบที่สมบูรณ์แบบสำหรับสำเนาที่ซ้ำกันนี้
jib

9
@Delapouite @jib และนี่ก็เช่นกัน:Array.from({length: end - start}, (v, k) => k + start)
Aditya Singh

1
@ icc97 ใช่ linters อาจบ่นแม้ว่าใน JavaScript จะละเว้นอาร์กิวเมนต์ฟังก์ชันที่กำหนดให้เหมือนกับการส่งผ่านundefinedดังนั้นfill()(โดยไม่มีอาร์กิวเมนต์) จึงไม่ผิดต่อ se ค่าการเติมไม่ได้ใช้ในโซลูชันนั้นดังนั้นหากคุณต้องการคุณสามารถใช้fill(0)เพื่อบันทึกอักขระสองสามตัว
Stu Cox

122

แบบฟอร์มใหม่ที่ฉันชอบ ( ES2015 )

Array(10).fill(1).map((x, y) => x + y)

และถ้าคุณต้องการฟังก์ชั่นที่มีstepพารามิเตอร์:

const range = (start, stop, step = 1) =>
  Array(Math.ceil((stop - start) / step)).fill(start).map((x, y) => x + y * step)

5
ให้ช่วง = (เริ่ม, หยุด, ขั้นตอน = 1) => อาร์เรย์ (หยุด - เริ่มต้น). เติม (เริ่มต้น). แผนที่ ((x, y) => x + y * ขั้นตอน)
rodfersou

4
@rodfersou FYI: ตัวอย่างของคุณผิด stopไม่ใช่ตำแหน่งหยุด / สิ้นสุด แต่นับ / ระยะทาง (ไม่มีความผิดเพียงเพื่อให้ผู้คนตระหนักถึงการพิมพ์ผิด)
F Lekschas

4
สำหรับความสับสน - เนื่องจากการแก้ไขของ rodfersou หลังจากความคิดเห็นของ F Lekschas รหัสของเขาถูกต้องแล้ว
eedrah

1
อาร์กิวเมนต์ที่คุณส่งผ่านArray(Math.ceil((stop - start) / step) + 1)ต้องการ+1ในตอนท้ายเพื่อเลียนแบบพฤติกรรม "รวม" ของ php จริงๆ
Johan Dettmar

3
นี่คือคำตอบอันดับต้นที่ตอบคำถามเต็มรูปแบบของฟังก์ชัน Javascript ที่ใช้rangeวิธีการอย่างเต็มที่ คนอื่น ๆ ทั้งหมดข้างต้นในขณะนี้ (ยกเว้นสำหรับ lodash's _.range) ใช้ตัวทำซ้ำขั้นพื้นฐานมากกว่าฟังก์ชั่นช่วงจริงกับการเริ่มต้นหยุดและขั้นตอน
icc97

99

นี่คือ 2 เซ็นต์ของฉัน:

function range(start, count) {
  return Array.apply(0, Array(count))
    .map((element, index) => index + start);
}

1
การใช้ฟังก์ชั่นการสั่งซื้อที่ยอดเยี่ยม
Farzad YZ

5
สิ่งนี้ผิดจริงเพราะคำถามกำลังขอค่าเริ่มต้นและค่าสิ้นสุด ไม่เริ่ม & นับ / ระยะทาง
James Robey

73

มันใช้งานได้กับตัวละครและตัวเลขก้าวไปข้างหน้าหรือข้างหลังด้วยขั้นตอนเสริม

var range = function(start, end, step) {
    var range = [];
    var typeofStart = typeof start;
    var typeofEnd = typeof end;

    if (step === 0) {
        throw TypeError("Step cannot be zero.");
    }

    if (typeofStart == "undefined" || typeofEnd == "undefined") {
        throw TypeError("Must pass start and end arguments.");
    } else if (typeofStart != typeofEnd) {
        throw TypeError("Start and end arguments must be of same type.");
    }

    typeof step == "undefined" && (step = 1);

    if (end < start) {
        step = -step;
    }

    if (typeofStart == "number") {

        while (step > 0 ? end >= start : end <= start) {
            range.push(start);
            start += step;
        }

    } else if (typeofStart == "string") {

        if (start.length != 1 || end.length != 1) {
            throw TypeError("Only strings with one character are supported.");
        }

        start = start.charCodeAt(0);
        end = end.charCodeAt(0);

        while (step > 0 ? end >= start : end <= start) {
            range.push(String.fromCharCode(start));
            start += step;
        }

    } else {
        throw TypeError("Only string and number types are supported");
    }

    return range;

}

jsFiddle

หาก augmenting Array.rangeประเภทพื้นเมืองคือสิ่งที่คุณแล้วกำหนดให้


53

ฟังก์ชันช่วงง่าย:

function range(start, stop, step) {
    var a = [start], b = start;
    while (b < stop) {
        a.push(b += step || 1);
    }
    return a;
}

ในการรวมประเภทข้อมูล BitInt ไว้ด้วยกันการตรวจสอบบางอย่างอาจรวมอยู่ด้วยทำให้มั่นใจว่าตัวแปรทั้งหมดเหมือนtypeof startกัน:

function range(start, stop, step) {
    var a = [start], b = start;
    if (typeof start == 'bigint') {
        stop = BigInt(stop)
        step = step? BigInt(step): 1n;
    } else
        step = step || 1;
    while (b < stop) {
        a.push(b += step);
    }
    return a;
}

หากต้องการลบค่าที่สูงกว่าที่กำหนดโดยstopเช่นrange(0,5,2)จะรวม6ซึ่งไม่ควรเป็น

function range(start, stop, step) {
    var a = [start], b = start;
    while (b < stop) {
        a.push(b += step || 1);
    }
    return (b > stop) ? a.slice(0,-1) : a;
}

3
PLUS UNO สำหรับการใช้งานและสามารถอ่านได้ ตัวอย่างรหัสที่ดีที่สุดที่ฉันเห็นมาเป็นเวลานาน
monsto

1
นี้จะไม่ทำงานเมื่อstep != 1ที่whileสภาพความต้องการที่จะstepเข้าบัญชี เวอร์ชันที่อัปเดตของฉันพร้อมค่าเริ่มต้นstep: ช่วงฟังก์ชั่น (เริ่ม, หยุด, ก้าว) 1 var a = [start], b = start; ในขณะที่ ((b + step) <stop) {console.log ("b:" + b + ". a:" + a + "."); B + = ขั้นตอน; a.push (ข); } คืน a; }
daveharris

@daveharris ฉันเพิ่มขั้นตอนเริ่มต้นด้านบน(step || 1)แล้ว
นาย Polywhirl

36
Array.range= function(a, b, step){
    var A= [];
    if(typeof a== 'number'){
        A[0]= a;
        step= step || 1;
        while(a+step<= b){
            A[A.length]= a+= step;
        }
    }
    else{
        var s= 'abcdefghijklmnopqrstuvwxyz';
        if(a=== a.toUpperCase()){
            b=b.toUpperCase();
            s= s.toUpperCase();
        }
        s= s.substring(s.indexOf(a), s.indexOf(b)+ 1);
        A= s.split('');        
    }
    return A;
}


    Array.range(0,10);
    // [0,1,2,3,4,5,6,7,8,9,10]

    Array.range(-100,100,20);
    // [-100,-80,-60,-40,-20,0,20,40,60,80,100]

    Array.range('A','F');
    // ['A','B','C','D','E','F')

    Array.range('m','r');
    // ['m','n','o','p','q','r']

คุณไม่ควรใช้วิธีการ jerry-rig ลงบนArrayต้นแบบ
connectyourcharger

วิธีนี้ใช้ได้กับจำนวนเต็มและตัวอักษรเท่านั้น ถ้าพารามิเตอร์เป็นโมฆะไม่ได้กำหนด NaN บูลีนอาร์เรย์วัตถุ ฯลฯ วิธีนี้จะส่งกลับข้อผิดพลาดต่อไปนี้: undefined method toUpperCase to etc!
Victor

`` `if (typeof from! == 'number' && typeof from! == 'string') {โยน TypeError ใหม่ ('พารามิเตอร์แรกควรเป็นตัวเลขหรือตัวอักษร')} ถ้า (typeof ถึง! == ' number '&& typeof to! ==' string ') {โยน TypeError ใหม่ (' พารามิเตอร์แรกควรเป็นตัวเลขหรือตัวอักษร ')} `` `
Victor

36

ตกลงใน JavaScript เราไม่มีrange()ฟังก์ชั่นอย่างPHPดังนั้นเราจำเป็นต้องสร้างฟังก์ชั่นที่ค่อนข้างง่ายฉันเขียนฟังก์ชั่นหนึ่งบรรทัดสำหรับคุณและแยกพวกมันออกเป็นNumbersและAlphabetsดังนี้

สำหรับตัวเลข :

function numberRange (start, end) {
  return new Array(end - start).fill().map((d, i) => i + start);
}

และเรียกว่าชอบ:

numberRange(5, 10); //[5, 6, 7, 8, 9]

สำหรับตัวอักษร :

function alphabetRange (start, end) {
  return new Array(end.charCodeAt(0) - start.charCodeAt(0)).fill().map((d, i) => String.fromCharCode(i + start.charCodeAt(0)));
}

และเรียกว่าชอบ:

alphabetRange('c', 'h'); //["c", "d", "e", "f", "g"]

2
ฉันคิดว่ามีข้อผิดพลาดในแต่ละหน้าที่ ควรจะเป็นและArray(end - start + 1) Array(end.charCodeAt(0) - start.charCodeAt(0) + 1)
earcanal

24

ฟังก์ชั่นที่มีประโยชน์ในการทำเคล็ดลับเรียกใช้ข้อมูลโค้ดด้านล่าง

function range(start, end, step, offset) {
  
  var len = (Math.abs(end - start) + ((offset || 0) * 2)) / (step || 1) + 1;
  var direction = start < end ? 1 : -1;
  var startingPoint = start - (direction * (offset || 0));
  var stepSize = direction * (step || 1);
  
  return Array(len).fill(0).map(function(_, index) {
    return startingPoint + (stepSize * index);
  });
  
}

console.log('range(1, 5)=> ' + range(1, 5));
console.log('range(5, 1)=> ' + range(5, 1));
console.log('range(5, 5)=> ' + range(5, 5));
console.log('range(-5, 5)=> ' + range(-5, 5));
console.log('range(-10, 5, 5)=> ' + range(-10, 5, 5));
console.log('range(1, 5, 1, 2)=> ' + range(1, 5, 1, 2));

นี่คือวิธีการใช้งาน

ช่วง (เริ่ม, สิ้นสุด, ขั้นตอนที่ = 1, ออฟเซ็ต = 0);

  • รวม - ไปข้างหน้า range(5,10) // [5, 6, 7, 8, 9, 10]
  • รวม - ย้อนหลัง range(10,5) // [10, 9, 8, 7, 6, 5]
  • ขั้นตอน - ย้อนหลัง range(10,2,2) // [10, 8, 6, 4, 2]
  • เอกสิทธิ์ - ไปข้างหน้า range(5,10,0,-1) // [6, 7, 8, 9] not 5,10 themselves
  • ชดเชย - ขยาย range(5,10,0,1) // [4, 5, 6, 7, 8, 9, 10, 11]
  • ชดเชย - หด range(5,10,0,-2) // [7, 8]
  • ขั้นตอน - ขยาย range(10,0,2,2) // [12, 10, 8, 6, 4, 2, 0, -2]

หวังว่าคุณจะพบว่ามีประโยชน์


และนี่คือวิธีการทำงาน

โดยทั่วไปฉันจะคำนวณความยาวของอาเรย์ที่เกิดขึ้นก่อนแล้วสร้างอาเรย์ที่เติมเต็มความยาวนั้นแล้วเติมด้วยค่าที่ต้องการ

  • (step || 1)=> และอื่น ๆ เช่นนี้หมายถึงใช้ค่าของstepและถ้ามันไม่ได้ให้ใช้1แทน
  • เราเริ่มต้นด้วยการคำนวณความยาวของอาร์เรย์ผลลัพธ์โดยใช้ (Math.abs(end - start) + ((offset || 0) * 2)) / (step || 1) + 1)เพื่อทำให้ง่ายขึ้น (ความแตกต่าง * ชดเชยในทั้งทิศทาง / ขั้นตอน)
  • หลังจากได้รับความยาวเราจะสร้างอาร์เรย์ว่างเปล่าด้วยค่าเริ่มต้นโดยใช้new Array(length).fill(0); เครื่องหมายที่นี่
  • ตอนนี้เรามีอาร์เรย์[0,0,0,..]ตามความยาวที่เราต้องการ เราจับคู่กับมันแล้วคืนค่าอาร์เรย์ใหม่ด้วยค่าที่เราต้องการโดยใช้Array.map(function() {})
  • var direction = start < end ? 1 : 0;เห็นได้ชัดว่าถ้าstartไม่เล็กกว่าที่endเราต้องการจะย้ายไปข้างหลัง ฉันหมายถึงไปจาก 0 ถึง 5 หรือในทางกลับกัน
  • ในทุกการทำซ้ำstartingPoint+ stepSize* indexจะให้คุณค่าที่เราต้องการ

8
มีประโยชน์แน่นอนที่สุด ง่าย? ฉันขอแตกต่าง ไม่ว่าคุณจะทำให้มันเป็นหนึ่งซับ มาจาก Python นี่มันน่าตกใจ
PascalVKooten

@PascalvKooten ใช่แน่นอนมันคงจะดีมากถ้ามีวิธีการในตัวสำหรับเช่น python ที่ฉันเดา แต่นี่เป็นวิธีที่ง่ายที่สุดที่ฉันสามารถทำได้ และได้พิสูจน์แล้วว่ามีประโยชน์ในโครงการของฉัน
azerafati

การโพสต์ตัวอย่างโค้ดที่ซับซ้อนอย่างเจ็บปวดโดยเฉพาะอย่างยิ่งในฐานะซับในและไม่มีคำอธิบายว่ามันทำงานอย่างไร ตัวอย่างที่ดีของคำตอบ SO ที่ดีโดยไม่คำนึงว่า "ทำงาน" หรือไม่
Madbreaks

1
@Madbreaks ใช่แล้วคุณพูดถูก ฉันไร้เดียงสาที่จะทำให้มันเป็นหนึ่งซับ แค่อยากให้ทุกคนแก้ปัญหาได้อย่างรวดเร็วและง่ายดาย
azerafati

22
var range = (l,r) => new Array(r - l).fill().map((_,k) => k + l);

@nikkwong, _เป็นเพียงชื่อของอาร์กิวเมนต์ในการเรียกกลับการแมป คุณรู้ไหมว่าในบางภาษาคุณจะใช้_ชื่อเพื่อชี้ให้เห็นว่าไม่ได้ใช้ตัวแปร
Klesun

นี่แม้ว่าจะไม่ได้ผ่านการขัดแย้งในการ_ rangeทำไมจะไม่ล่ะ?
nikk wong

2
เรียบร้อยมาก! แม้ว่าจะเป็นสิ่งสำคัญที่จะต้องทราบว่ามันไม่ทำงานบน IE หรือ Opera ใด ๆ
Rafael Xavier

4
คำตอบนี้ต้องการคำอธิบายเนื่องจากมันเหมาะสมสำหรับ SO
Madbreaks

@RafaelXavier จะทำงานกับ IE ด้วยArray.fill () polyfill
mwag

18

ใช้ Harmony spread โอเปอเรเตอร์และฟังก์ชั่น arrow:

var range = (start, end) => [...Array(end - start + 1)].map((_, i) => start + i);

ตัวอย่าง:

range(10, 15);
[ 10, 11, 12, 13, 14, 15 ]

นั่นคือคำตอบที่ดีที่สุด!
เฮนรี่เอช

1
ไม่ใช่ว่าเร็วที่สุด
mjwrazor

สัญลักษณ์ขีดล่าง '_' แสดงถึงอะไรในกรณีนี้
Oleh Berehovskyi

@OlehBerehovskyi หมายถึงพารามิเตอร์ฟังก์ชัน lambda ที่คุณไม่ต้องการใช้ linter ที่เตือนเกี่ยวกับตัวแปรที่ไม่ได้ใช้ควรละเว้น
คา Zoltu

18

--- ปรับปรุง (ขอบคุณ @lokhmakov สำหรับการทำให้เข้าใจง่าย) ---

รุ่นอื่นที่ใช้เครื่องกำเนิดไฟฟ้า ES6 (ดูคำตอบที่ดีPaolo Moretti กับเครื่องกำเนิด ES6 ):

const RANGE = (x,y) => Array.from((function*(){
  while (x <= y) yield x++;
})());

console.log(RANGE(3,7));  // [ 3, 4, 5, 6, 7 ]

หรือถ้าเราต้องการเพียง iterable แล้ว:

const RANGE_ITER = (x,y) => (function*(){
  while (x <= y) yield x++;
})();

for (let n of RANGE_ITER(3,7)){
  console.log(n);
}

// 3
// 4
// 5
// 6
// 7

--- รหัสเดิมคือ: ---

const RANGE = (a,b) => Array.from((function*(x,y){
  while (x <= y) yield x++;
})(a,b));

และ

const RANGE_ITER = (a,b) => (function*(x,y){
  while (x <= y) yield x++;
})(a,b);

1
เพียงแค่ const range = (x, y) => Array.from(function* () { while (x <= y) yield x++; }())
lokhmakov

@lokhmakov ใช่คุณพูดถูก ขอบคุณ! เพียงแค่ใช้รหัสของคุณในคำตอบของฉัน
ฮีโร่ Qu

15

ทำวิจัยเกี่ยวกับฟังก์ชั่นพิสัยที่หลากหลาย ชำระเงินเปรียบเทียบ jsperfของวิธีต่างๆในการทำหน้าที่เหล่านี้ ไม่แน่นอนรายการที่สมบูรณ์หรือครบถ้วนสมบูรณ์ แต่ควรช่วย :)

ผู้ชนะคือ...

function range(lowEnd,highEnd){
    var arr = [],
    c = highEnd - lowEnd + 1;
    while ( c-- ) {
        arr[c] = highEnd--
    }
    return arr;
}
range(0,31);

ในทางเทคนิคแล้วมันไม่ได้เร็วที่สุดสำหรับ firefox แต่ความแตกต่างของความเร็วที่บ้า (imho) บนโครเมียมนั้นทำขึ้นเพื่อมัน

การสังเกตที่น่าสนใจก็คือปริมาณโครเมี่ยมที่เร็วกว่าด้วยฟังก์ชันอาเรย์เหล่านี้มากกว่า firefox โครเมี่ยมอย่างน้อย 4 หรือ 5 ครั้งเร็ว


โปรดทราบว่าสิ่งนี้ถูกเปรียบเทียบกับฟังก์ชั่นช่วงที่รวมพารามิเตอร์ขนาดขั้นตอน
binaryfunt

15

Javascript มาตรฐานไม่มีฟังก์ชันในตัวในการสร้างช่วง เฟรมเวิร์ก javascript หลายตัวเพิ่มการสนับสนุนสำหรับคุณสมบัติดังกล่าวหรืออย่างที่คนอื่น ๆ ได้ชี้ให้เห็นว่าคุณสามารถม้วนตัวเองได้เสมอ

หากคุณต้องการที่จะตรวจสอบอีกครั้งทรัพยากรที่ชัดเจนเป็นECMA-262 มาตรฐาน


ในขณะที่ฉันแน่ใจว่าคำตอบที่ดีอย่างสมบูรณ์ในปี 2010 นี้ไม่ควรถือว่าเป็นวิธีที่ดีที่สุด คุณไม่ควรขยายประเภทที่สร้างขึ้นเช่น Prototype.js มีแนวโน้มที่จะทำ👍
Dana Woodman

@DanaWoodman ขอบคุณที่นำสิ่งนี้มา - ฉันได้อัปเดตคำตอบเพื่อนำไปอ้างอิงกับ Prototype.js เนื่องจากมันล้าสมัยไปแล้วในปี 2018
Mike Dinescu

21
อย่างนี้ก็ไม่ได้ช่วยอะไรเลย
Pithikos

@Pithikos ฉันเห็นคำถามนี้ได้รับการแก้ไขตั้งแต่แรกถามและ OP ต้องการทราบว่ามีฟังก์ชันช่วงดั้งเดิมใน JS หรือไม่
Mike Dinescu

13

คุณสามารถใช้lodashหรือUndescore.js range :

var range = require('lodash/range')
range(10)
// -> [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

หรือถ้าคุณต้องการเพียงจำนวนเต็มต่อเนื่องคุณสามารถทำสิ่งต่อไปนี้

Array.apply(undefined, { length: 10 }).map(Number.call, Number)
// -> [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

ใน ES6 rangeสามารถนำไปใช้กับเครื่องกำเนิดไฟฟ้า :

function* range(start=0, end=null, step=1) {
  if (end == null) {
    end = start;
    start = 0;
  }

  for (let i=start; i < end; i+=step) {
    yield i;
  }
}

การใช้งานนี้จะช่วยประหยัดหน่วยความจำเมื่อวนซ้ำลำดับขนาดใหญ่เพราะมันไม่จำเป็นต้องทำให้ค่าทั้งหมดเป็นจริง:

for (let i of range(1, oneZillion)) {
  console.log(i);
}

ตอนนี้ส่วน ES6 เป็นคำตอบที่ถูกต้องสำหรับคำถามนี้ ฉันขอแนะนำให้ลบส่วนอื่น ๆ ซึ่งครอบคลุมโดยคำตอบอื่น ๆ
joews

เครื่องกำเนิดไฟฟ้าค่อนข้างแปลกถ้าใช้นอกวงแม้ว่า: x = range (1, 10); // {} x; // {} // ดูเหมือนแผนที่ว่าง WTF!?! x.next (). value; // OK 1; x [3] // ไม่ได้กำหนดเฉพาะกับอาร์เรย์ที่แท้จริง
Anona112

@ Anona112 คุณสามารถใช้Array.fromในการแปลง generators เป็น array อินสแตนซ์และตรวจสอบผลลัพธ์
เปาโล Moretti

10

ความท้าทายที่น่าสนใจคือการเขียนฟังก์ชั่นที่สั้นที่สุดในการทำสิ่งนี้ การเรียกซ้ำเพื่อช่วยชีวิต!

function r(a,b){return a>b?[]:[a].concat(r(++a,b))}

มีแนวโน้มที่จะช้าในช่วงกว้าง แต่โชคดีที่คอมพิวเตอร์ควอนตัมอยู่ใกล้แค่เอื้อม

โบนัสเพิ่มเติมคือมันทำให้งงงวย เพราะเราทุกคนรู้ว่ามันสำคัญขนาดไหนที่จะซ่อนรหัสจากสายตาที่แอบสอดส่อง

ในการทำให้งงงวยฟังก์ชั่นอย่างแท้จริงและอย่างเต็มที่ทำสิ่งนี้:

function r(a,b){return (a<b?[a,b].concat(r(++a,--b)):a>b?[]:[a]).sort(function(a,b){return a-b})}

4
สั้น! = เรียบง่าย แต่ง่ายกว่าดีกว่า นี่คือเวอร์ชันที่อ่านง่ายกว่า: const range = (a, b) => (a>=b) ? [] : [a, ...range(a+1, b)]โดยใช้ไวยากรณ์ ES6
nafg

1
const range = (a, b, Δ = 1) => (a > b) ? [] : [a, ...range(a + Δ, b, Δ)];@nafg: นอกจากนี้ยังถอนคำตอบทั้งหมดสำหรับความคิดเห็น
7vujy0f0hy

10

นี่อาจไม่ใช่วิธีที่ดีที่สุด แต่หากคุณกำลังมองหาช่วงของตัวเลขในรหัสบรรทัดเดียว ตัวอย่างเช่น 10 - 50

Array(40).fill(undefined).map((n, i) => i + 10)

โดยที่ 40 คือ (สิ้นสุด - เริ่ม) และ 10 คือจุดเริ่มต้น สิ่งนี้ควรกลับ[10, 11, ... , 50]


9

ฉันจะรหัสสิ่งนี้:

function range(start, end) {
    return Array(end-start).join(0).split(0).map(function(val, id) {return id+start});
}  

range(-4,2);
// [-4,-3,-2,-1,0,1]

range(3,9);
// [3,4,5,6,7,8]

มันทำหน้าที่คล้ายกับช่วง Python:

>>> range(-4,2)
[-4, -3, -2, -1, 0, 1]

8

การใช้งานที่ค่อนข้างเรียบง่ายซึ่งใช้ ES6 อย่างหนักสามารถสร้างได้ดังต่อไปนี้โดยให้ความสนใจเป็นพิเศษกับArray.from()วิธีการแบบคงที่:

const getRange = (start, stop) => Array.from(
  new Array((stop - start) + 1),
  (_, i) => i + start
);

ในฐานะที่เป็นข้อความด้านข้างฉันได้สร้าง Gist ซึ่งฉันได้ทำgetRange()ฟังก์ชั่น"ปรับปรุง" แปลก ๆ โดยเฉพาะอย่างยิ่งฉันมุ่งเป้าไปที่การจับเคสขอบที่อาจไม่ได้รับการแก้ไขในตัวแปรกระดูกข้างต้น นอกจากนี้ฉันเพิ่มการสนับสนุนสำหรับช่วงตัวอักษรและตัวเลข กล่าวอีกนัยหนึ่งการเรียกด้วยอินพุตที่ให้มาสองรายการเช่น'C'และ'K'(ในลำดับนั้น) ส่งคืนอาร์เรย์ซึ่งค่าเป็นชุดลำดับของอักขระจากตัวอักษร 'C' (รวม) ผ่านตัวอักษร 'K' (พิเศษ):getRange('C', 'K'); // => ["C", "D", "E", "F", "G", "H", "I", "J"]
IsenrichO

คุณไม่ต้องการnewคำหลัก
Soldeplata Saketos

8

range(start,end,step): ด้วย ES6 Iterators

คุณขอขอบเขตบนและล่างเท่านั้น ที่นี่เราสร้างขั้นตอนเดียวด้วย

คุณสามารถสร้างrange()ฟังก์ชั่นเครื่องกำเนิดไฟฟ้าซึ่งสามารถทำหน้าที่เป็นตัววนซ้ำได้อย่างง่ายดาย ซึ่งหมายความว่าคุณไม่จำเป็นต้องสร้างอาร์เรย์ทั้งหมดล่วงหน้า

function * range ( start, end, step = 1 ) {
  let state = start;
  while ( state < end ) {
    yield state;
    state += step;
  }
  return;
};

ตอนนี้คุณอาจต้องการสร้างสิ่งที่สร้างอาร์เรย์ล่วงหน้าจากตัววนซ้ำและส่งคืนรายการ สิ่งนี้มีประโยชน์สำหรับฟังก์ชั่นที่ยอมรับอาร์เรย์ สำหรับสิ่งนี้เราสามารถใช้Array.from()

const generate_array = (start,end,step) =>
  Array.from( range(start,end,step) );

ตอนนี้คุณสามารถสร้างอาเรย์แบบคงที่ได้อย่างง่ายดาย

const array1 = generate_array(1,10,2);
const array1 = generate_array(1,7);

แต่เมื่อมีบางสิ่งที่ต้องการตัววนซ้ำ (หรือให้ทางเลือกแก่คุณในการใช้ตัววนซ้ำ) คุณก็สามารถสร้างตัววนซ้ำได้อย่างง่ายดายเช่นกัน

for ( const i of range(1, Number.MAX_SAFE_INTEGER, 7) ) {
  console.log(i)
}

หมายเหตุพิเศษ


7

แต่นี้ไม่ได้มาจากPHPแต่เลียนแบบrangeจากงูใหญ่

function range(start, end) {
    var total = [];

    if (!end) {
        end = start;
        start = 0;
    }

    for (var i = start; i < end; i += 1) {
        total.push(i);
    }

    return total;
}

console.log(range(10)); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 
console.log(range(0, 10)); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(range(5, 10)); // [5, 6, 7, 8, 9] 

+1 สำหรับสิ่งที่เร็วที่สุด ด้วยอาร์เรย์ของ -36768 - 36768, ใช้เวลา 3 มิลลิวินาที, อันดับที่ 2 คือ 13 ms และมีเส้นสีแดง IDE
mjwrazor

7

เท่าที่สร้างอาร์เรย์ที่เป็นตัวเลขสำหรับช่วงที่กำหนดฉันใช้สิ่งนี้:

function range(start, stop)
{
    var array = [];

    var length = stop - start; 

    for (var i = 0; i <= length; i++) { 
        array[i] = start;
        start++;
    }

    return array;
}

console.log(range(1, 7));  // [1,2,3,4,5,6,7]
console.log(range(5, 10)); // [5,6,7,8,9,10]
console.log(range(-2, 3)); // [-2,-1,0,1,2,3]

เห็นได้ชัดว่ามันจะไม่ทำงานสำหรับอาร์เรย์ตัวอักษร


การตั้งค่าarray = []ภายในลูปอาจไม่ให้สิ่งที่คุณต้องการ
alex

@alex ขอบคุณ คุณพูดถูกฉันยังลืมที่จะเพิ่มพารามิเตอร์ "เริ่มต้น" ในแต่ละรอบของวง ตอนนี้ได้รับการแก้ไขแล้ว
jhaskell

มันยังคงไม่สร้างเอาต์พุตที่ต้องการหากฉันต้องการช่วง 5-10 มันจะให้ฉัน[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]ฉันคาดหวังเพียงครึ่งแรกของอาร์เรย์นั้น
alex

@alex ขอบคุณอีกครั้งฉันไม่ได้พิจารณาข้อจำกัดความยาวตามอินพุต ดูรุ่นที่อัพเดท
jhaskell

6

การใช้เครื่องกำเนิดไฟฟ้าสามัคคี , การสนับสนุนจากเบราว์เซอร์ทั้งหมดยกเว้น IE11 :

var take = function (amount, generator) {
    var a = [];

    try {
        while (amount) {
            a.push(generator.next());
            amount -= 1;
        }
    } catch (e) {}

    return a;
};

var takeAll = function (gen) {
    var a = [],
        x;

    try {
        do {
            x = a.push(gen.next());
        } while (x);
    } catch (e) {}

    return a;
};

var range = (function (d) {
    var unlimited = (typeof d.to === "undefined");

    if (typeof d.from === "undefined") {
        d.from = 0;
    }

    if (typeof d.step === "undefined") {
        if (unlimited) {
            d.step = 1;
        }
    } else {
        if (typeof d.from !== "string") {
            if (d.from < d.to) {
                d.step = 1;
            } else {
                d.step = -1;
            }
        } else {
            if (d.from.charCodeAt(0) < d.to.charCodeAt(0)) {
                d.step = 1;
            } else {
                d.step = -1;
            }
        }
    }

    if (typeof d.from === "string") {
        for (let i = d.from.charCodeAt(0); (d.step > 0) ? (unlimited ? true : i <= d.to.charCodeAt(0)) : (i >= d.to.charCodeAt(0)); i += d.step) {
            yield String.fromCharCode(i);
        }
    } else {
        for (let i = d.from; (d.step > 0) ? (unlimited ? true : i <= d.to) : (i >= d.to); i += d.step) {
            yield i;
        }
    }
});

ตัวอย่าง

เอา

ตัวอย่างที่ 1

take ใช้เวลามากเท่าที่จะได้รับ

take(10, range( {from: 100, step: 5, to: 120} ) )

ผลตอบแทน

[100, 105, 110, 115, 120]

ตัวอย่างที่ 2

to ไม่จำเป็น

take(10, range( {from: 100, step: 5} ) )

ผลตอบแทน

[100, 105, 110, 115, 120, 125, 130, 135, 140, 145]

เอาทั้งหมด

ตัวอย่างที่ 3

from ไม่จำเป็น

takeAll( range( {to: 5} ) )

ผลตอบแทน

[0, 1, 2, 3, 4, 5]

ตัวอย่างที่ 4

takeAll( range( {to: 500, step: 100} ) )

ผลตอบแทน

[0, 100, 200, 300, 400, 500]

ตัวอย่างที่ 5

takeAll( range( {from: 'z', to: 'a'} ) )

ผลตอบแทน

["z", "y", "x", "w", "v", "u", "t", "s", "r", "q", "p", "o", "n", "m", "l", "k", "j", "i", "h", "g", "f", "e", "d", "c", "b", "a"]


แก้ไขด้วยคำแนะนำของฉัน :)
Xotic750

+1 สำหรับวิธีการ ถึงจุดของ @ alex การไม่มีการดำเนินการแบบไตรภาค (โดยเฉพาะอย่างยิ่งไม่ซ้อนกัน) ในforข้อจะปรับปรุงการอ่านที่นี่
Justin Johnson

5

... ช่วงมากขึ้นโดยใช้ฟังก์ชั่นเครื่องกำเนิดไฟฟ้า

function range(s, e, str){
  // create generator that handles numbers & strings.
  function *gen(s, e, str){
    while(s <= e){
      yield (!str) ? s : str[s]
      s++
    }
  }
  if (typeof s === 'string' && !str)
    str = 'abcdefghijklmnopqrstuvwxyz'
  const from = (!str) ? s : str.indexOf(s)
  const to = (!str) ? e : str.indexOf(e)
  // use the generator and return.
  return [...gen(from, to, str)]
}

// usage ...
console.log(range('l', 'w'))
//=> [ 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w' ]

console.log(range(7, 12))
//=> [ 7, 8, 9, 10, 11, 12 ]

// first 'o' to first 't' of passed in string.
console.log(range('o', 't', "ssshhhooooouuut!!!!"))
// => [ 'o', 'o', 'o', 'o', 'o', 'u', 'u', 'u', 't' ]

// only lowercase args allowed here, but ...
console.log(range('m', 'v').map(v=>v.toUpperCase()))
//=> [ 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V' ]

// => and decreasing range ...
console.log(range('m', 'v').map(v=>v.toUpperCase()).reverse())

// => ... and with a step
console.log(range('m', 'v')
          .map(v=>v.toUpperCase())
          .reverse()
          .reduce((acc, c, i) => (i % 2) ? acc.concat(c) : acc, []))

// ... etc, etc.

หวังว่านี่จะเป็นประโยชน์


5

เพื่อนร่วมงาน codegolfing ของฉันมาด้วยสิ่งนี้ (ES6) รวม:

(s,f)=>[...Array(f-s+1)].map((e,i)=>i+s)

ไม่รวม:

(s,f)=>[...Array(f-s)].map((e,i)=>i+s)


4

d3 ยังมีฟังก์ชั่นช่วงในตัว ดูhttps://github.com/mbostock/d3/wiki/Arrays#d3_range :

d3.range ([เริ่ม,] หยุด [, ขั้นตอน])

สร้างอาร์เรย์ที่มีการดำเนินการทางคณิตศาสตร์คล้ายกับช่วงของ Python ในตัว วิธีนี้มักจะใช้เพื่อวนซ้ำลำดับของค่าตัวเลขหรือจำนวนเต็มเช่นดัชนีในอาร์เรย์ ไม่เหมือนกับเวอร์ชัน Python อาร์กิวเมนต์ไม่จำเป็นต้องเป็นจำนวนเต็มถึงแม้ว่าผลลัพธ์จะสามารถคาดเดาได้มากขึ้นหากมีความแม่นยำเนื่องจากจุดลอยตัว หากข้ามขั้นตอนไปแล้วจะมีค่าเริ่มต้นเป็น 1

ตัวอย่าง:

d3.range(10)
// returns [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

ฉันไม่เคยรู้จัก D3 มาก่อน จะไม่ใช้วิธีการช่วง แต่จะใช้แพคเกจนี้
mjwrazor

ขอบคุณมาก. ฉันใช้ D3 และกำลังมองหาวิธี JS พื้นเมืองไม่ทราบว่าฉัน D3 ให้บริการแล้ว
cezar

4

การติดตั้ง ES6 ให้สมบูรณ์โดยใช้ช่วง ([เริ่ม,] หยุด [ขั้นตอน]) ลายเซ็น:

function range(start, stop, step=1){
  if(!stop){stop=start;start=0;}
  return Array.from(new Array(int((stop-start)/step)), (x,i) => start+ i*step)
}

หากคุณต้องการก้าวเชิงลบอัตโนมัติเพิ่ม

if(stop<start)step=-Math.abs(step)

หรือมากกว่านั้น:

range=(b, e, step=1)=>{
  if(!e){e=b;b=0}
  return Array.from(new Array(int((e-b)/step)), (_,i) => b<e? b+i*step : b-i*step)
}

หากคุณมีช่วงขนาดใหญ่ดูที่วิธีการกำเนิดของ Paolo Moretti


แทนที่!stopด้วยtypeof stop === 'undefined'แล้วแทนที่intด้วยMath.floorและเพิ่มการตรวจสอบif (start > stop && step > 0)(มิฉะนั้นrange(-3, -10)พ่นยกเว้นแทนการทำบางสิ่งบางอย่างมีสติ (ทั้งพลิกสัญญาณของขั้นตอนหรือกลับ[])) มิฉะนั้นดี!
Ahmed Fasih

4

มีโมดูลnpm bereichสำหรับสิ่งนั้น ("bereich" คือคำภาษาเยอรมันสำหรับ "range") มันใช้ประโยชน์จาก iterators ของ JavaScript ที่ทันสมัยดังนั้นคุณสามารถใช้มันได้หลายวิธีเช่น:

console.log(...bereich(1, 10));
// => 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

const numbers = Array.from(bereich(1, 10));
// => [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

for (const number of bereich(1, 10)) {
  // ...
}

นอกจากนี้ยังรองรับช่วงจากมากไปน้อย (โดยการแลกเปลี่ยนminและmax) และยังรองรับขั้นตอนอื่นนอกเหนือ1จาก

คำเตือน: ฉันเป็นผู้สร้างโมดูลนี้ดังนั้นโปรดตอบด้วยเกลือเม็ด


4

อันนี้ทำงานในสิ่งที่ตรงกันข้าม

const range = ( a , b ) => Array.from( new Array( b > a ? b - a : a - b ), ( x, i ) => b > a ? i + a : a - i );

range( -3, 2 ); // [ -3, -2, -1, 0, 1 ]
range( 1, -4 ); // [ 1, 0, -1, -2, -3 ]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.