ใน
บทที่แล้วได้แนะนำเรื่องอิเทอร์ไปแล้ว ส่วนในบทนี้จะแนะนำเกี่ยวกับสิ่งที่เรียกว่า
เจเนอเรเตอร์ (generator) ซึ่งเป็นวิธีการสร้างอิเทอเรเตอร์อย่างง่ายแบบหนึ่ง
การสร้างเจเนอเรเตอร์
เจเนอเรเตอร์ (generator) เป็นอิเทอเรเตอร์ชนิดหนึ่ง สามารถสร้างขึ้นได้อย่างง่ายโดยใช้คำสั่ง function
วิธีการสร้างเจเนอเรเตอร์นั้นทำคล้ายๆกับการสร้างฟังก์ชันธรรมดา แต่จะต่างกันตรงที่หน้าวงเล็บจะเติม * ลงไป และภายใน {} จะมีคำสั่ง yield
ออบเจ็กต์ที่ได้จากฟังก์ชันเจเนอเรเตอร์นี้จะเป็นอิเทอเรเตอร์ที่เมื่อใช้เมธอด .next หรือวนซ้ำด้วย for๛of แล้วก็จะได้ค่าออกมาเรื่อยๆ
โดยค่าที่ได้คือค่าที่อยู่ข้างหลังคำสั่ง yield คล้ายๆกับการใช้ retuen ในฟังก์ชันทั่วไป แต่คำสั่ง yield อาจมีหลายครั้ง ซึ่งแทนการวนซ้ำแต่ละครั้ง การวนซ้ำจะสิ้นสุดลงเมื่อสิ้นสุดฟังก์ชันไปโดยไม่เจอ yield อีกแล้ว
ตัวอย่างการสร้างและใช้เจเนอเรเตอร์
function *rezerogen(){
yield "เอมิเลีย";
yield "เรม";
yield "รัม";
}
let iter = rezerogen();
alert(iter.next().value); // ได้ เอมิเลีย
alert(iter.next().value); // ได้ เรม
alert(iter.next().value); // ได้ รัม
alert(iter.next().value); // ได้ undefined
let iter2 = rezerogen()
for (let x of iter2){
alert(x); // วน ๓ รอบ ได้ เอมิเลีย, เรม, รัม
}
ในที่นี้ฟังก์ชัน rezerogen() เป็นเจเนอเรเตอร์ซึ่งจะสร้างอิเทอเรเตอร์ที่ไล่คืนค่าออกมา ๓ ตัว จะนำมาใช้ .next() เพื่อไล่เอาค่าทีละตัว หรือนำมาวนซ้ำด้วย for๛of ก็ได้
สามารถใช้การวนซ้ำเพื่อให้ฟังก์ชันผ่าน yield หลายครั้งเพื่อจะได้เขียน yield แค่ครั้งเดียวแต่วนซ้ำได้หลายรอบ
function* gen(n) {
let i = 1;
while (i <= n) {
yield i * i
i++;
}
}
let iter = gen(4);
alert(iter.next().value); // ได้ 1
alert(iter.next().value); // ได้ 4
alert(iter.next().value); // ได้ 9
alert(iter.next().value); // ได้ 16
alert(iter.next().value); // ได้ undefined
นอกจากนี้ สามารถใช้ * ตามหลัง yield เพื่อรับค่าเป็นแถวลำดับได้ แบบนี้แต่ละรอบก็จะเอาค่าจากในนั้นไปทีละตัว
function* konosubagen() {
yield "คาซึมะ"
yield* ["อควา","เมงุมิน","ดักเนส"]
}
let iter = konosubagen();
alert(iter.next().value); // ได้ คาซึมะ
alert(iter.next().value); // ได้ อควา
alert(iter.next().value); // ได้ เมงุมิน
alert(iter.next().value); // ได้ ดักเนส
alert(iter.next().value); // ได้ undefined
สร้างคลาสของอิเทอเรเตอร์โดยใช้เจเนอเรเตอร์
สามารถนำเจเนอเรเตอร์มาสร้างเป็นคลาสของอิเทอเรเตอร์ขึ้นมาได้ โดยอาจสร้าง this[Symbol.iterator] ไว้ใน constructor
ตัวอย่างเช่น ลองสร้างคลาสที่เอาค่าในในแถวลำดับ ๒ อันมาต่อกันแล้วไล่ออกมาทีละตัว
class Itor {
constructor(ar1, ar2) {
this.ar1 = ar1;
this.ar2 = ar2;
this[Symbol.iterator] = function* () {
let i = 0, n = this.ar1.length;
while (i < n) {
yield "~" + this.ar1[i] + " " + this.ar2[i] + "~";
i++;
}
}
}
}
let myouji = ["โคโนะฮาตะ", "มานากะ", "โมริโนะ", "อิโนเสะ", "ซากุราอิ"];
let namae = ["มิระ", "อาโอะ", "มาริ", "ไม", "มิกาเงะ"];
let koias = new Itor(myouji, namae);
let s = "";
for (let k of koias) {
s += k;
}
alert(s); // ได้ ~โคโนะฮาตะ มิระ~~มานากะ อาโอะ~~โมริโนะ มาริ~~อิโนเสะ ไม~~ซากุราอิ มิกาเงะ~
ลองสร้างฟังก์ชัน range
เพื่อเป็นตัวอย่างการใช้งานจริง ลองใช้เจเนอเรเตอร์เพื่อสร้างเจเนอเรเตอร์เหมือนอย่าง range ในภาษาไพธอนขึ้นมาดู
range คือฟังก์ชันที่สร้างค่าที่วนซ้ำโดยไล่จากค่าต่ำสุด a ไปจนก่อนถึงค่าสูงสุด b โดยเว้นห่างทีละ c
แต่กรณีที่ใส่แค่ a กับ b ให้ถือว่าค่า c เป็น 1 คือข้ามทีละ 1
หรือถ้าใส่แค่ตัวเดียว จะถือว่าไล่จากค่า a=0 ไปยังก่อนถึงค่านั้น
ตัวอย่างการสร้าง
function* range(a, b, c = 1) {
if (!b) [a, b] = [0, a];
while (a < b) {
yield a;
a += c;
}
}
for (let x of range(4)) { alert(x); } // ได้ 0, 1, 2, 3
for (let x of range(5, 10)) { alert(x); } // ได้ 5, 6, 7, 8, 9
for (let x of range(2, 7, 2)) { alert(x); } // ได้ 2, 4, 6
ลองสร้างจำนวนฟีโบนัชชี
อีกตัวอย่างที่นิยมเขียนเพื่อฝึกคือ
จำนวนฟีโบนัชชี อาจลองเขียนโค้ดได้ดังนี้
function* fibo() {
let a = 0, b = 1, c
while (1) {
c = a + b;
a = b;
b = c;
yield a;
}
}
let s = "";
let i = 0;
for (let x of fibo()) {
if(x > 2000) break;
s += x + "; ";
i++;
}
alert(s) // ได้ 1; 1; 2; 3; 5; 8; 13; 21; 34; 55; 89; 144; 233; 377; 610; 987; 1597;
ลองสร้างฟังก์ชันไล่จำนวนเฉพาะ
อีกตัวอย่างหนึ่งที่อาจลองใช้เจเนอเรเตอร์สร้างได้คือฟังก์ชันที่ไล่
จำนวนเฉพาะออกมา โดยใช้วิธีการแบบตะแกรงของเอราโตสเทเนส (Ερατοσθένης) อาจสร้างได้ดังนี้
function* chamnuanchapho() {
let a = 1
while (1) {
let pen = true;
a++;
let i = 2;
while (i <= Math.floor(Math.sqrt(a))) {
if (a % i == 0) {
pen = false
break;
}
i++;
}
if (pen) yield a;
}
}
let s = "";
let i = 0;
for (let x of chamnuanchapho()) {
if (x > 100) break
s += x + "' ";
i++;
}
alert(s) // ได้ 2' 3' 5' 7' 11' 13' 17' 19' 23' 29' 31' 37' 41' 43' 47' 53' 59' 61' 67' 71' 73' 79' 83' 89' 97'