φυβλαςのβλογ
บล็อกของ phyblas



javascript เบื้องต้น บทที่ ๒๗: เมธอดสำหรับไล่จัดการสมาชิกในแถวลำดับ
เขียนเมื่อ 2019/08/09 09:17


เมธอดของแถวลำดับที่เพิ่มมาใน ES5

จาวาสคริปต์ใน ES5 นั้นได้เพิ่มเมธอดสำหรับทำการวนซ้ำเพื่อไล่จัดการสมาชิกต่างๆในแถวลำดับมาหลายตัว

เดิมทีใน ES3 นั้นปกติจะใช้ while หรือ for เพื่อทำการวนซ้ำ ดังที่เขียนถึงในบทที่ ๑๑ แต่เมื่อใช้เมธอดที่เพิ่มมาใน ES5 จะทำให้สะดวกขึ้นมาก

ได้แก่

.forEach(f(x)) ไล่ทำฟังก์ชัน f ซ้ำกับสมาชิกในแถวลำดับทีละตัว
.map(f(x)) ไล่ทำฟังก์ชัน f ซ้ำกับสมาชิกในแถวลำดับทีละตัว แล้วสร้างเป็นแถวลำดับของค่าที่ฟังก์ชัน f คืนมา
.filter(f(x)) ไล่ทำฟังก์ชัน f ซ้ำกับสมาชิกในแถวลำดับทีละตัว แล้วคัดเหลือเฉพาะที่คืนค่า true
.every(f(x)) ไล่ทำฟังก์ชัน f ซ้ำกับสมาชิกในแถวลำดับทีละตัว แล้วดูค่าที่คืนมา ถ้าทุกตัวเป็น true ก็ได้ true ถ้ามี false แม้แต่ตัวเดียวก็จะได้ false
.some(f(x)) ไล่ทำฟังก์ชัน f ซ้ำกับสมาชิกในแถวลำดับทีละตัว แล้วดูค่าที่คืนมา ถ้าทุกตัวเป็น false ก็ได้ false ถ้ามี true แม้แต่ตัวเดียวก็จะได้ true
.reduce(f(a,b)) ไล่ทำฟังก์ชัน f ซ้ำกับสมาชิกในแถวลำดับทีละตัว ค่าที่คืนมาจะแทนในพารามิเตอร์ตัวแรกของรอบถัดไป สุดท้ายจะได้ค่าที่คืนจากรอบสุดท้าย
.reduceRight(f(a,b)) เหมือน .redece แต่ไล่จากตัวสุดท้ายมา



เมธอด forEach

วิธีใช้
แถวลำดับ.forEach(ฟังก์ชันที่ต้องการให้จัดการกับสมาชิกทุกตัว)

ตัวอย่าง
var ahan = ["ผัดไทย", "ราดหน้า", "แกงส้ม", "โจ๊กคนอร์"];
var saikhai = function(x) {
  raikan_ahan += x + "ใส่ไข่\n";
};

var raikan_ahan = "";
ahan.forEach(saikhai);
alert(raikan_ahan);

ได้
ผัดไทยใส่ไข่
ราดหน้าใส่ไข่
แกงส้มใส่ไข่
โจ๊กคนอร์ใส่ไข่

แต่โดยทั่วไปแล้ววิธีการที่ใช้บ่อยก็คือ สร้างฟังก์ชันขึ้นมาเดี๋ยวนั้นภายในวงเล็บหลัง forEach เลย คือ
แถวลำดับ.forEach(function(พารามิเตอร์) {
  สิ่งที่จะทำซ้ำกับทุกตัว
});

เช่นตัวอย่างที่แล้วเขียนใหม่ให้ได้ผลเหมือนเดิมเป็นแบบนี้
var ahan = ["ผัดไทย", "ราดหน้า", "แกงส้ม", "โจ๊กคนอร์"];

var raikan_ahan = "";
ahan.forEach(function(x) {
  raikan_ahan += x + "ใส่ไข่\n";
});
alert(raikan_ahan);

แบบนี้ก็ไม่ต้องไปสร้างฟังก์ชันขึ้นมาใหม่ก่อน ถ้ายังไงก็จะใช้แค่ตรงนี้แล้วทิ้งไปเลยอยู่แล้ว

ข้อเสียคือเขียนแบบนี้แล้วทำให้วงเล็บซ้อนกัน ดูแล้วเข้าใจยากสำหรับมือใหม่ สาเหตุที่ทำให้จาวาสคริปต์ดูเป็นภาษาที่ไม่สวยงาม มองแล้วยุ่งเหยิงก็มาจากตรงนี้ด้วย

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

เมื่อใช้ .forEach สิ่งที่ถูกส่งเข้าไปในฟังก์ชันนั้นนอกจากข้อมูลในแถวลำดับแล้ว ยังมีเลขลำดับด้วย โดยจะไปแทนในพารามิเตอร์ตัวที่ ๒ ถ้าใส่ลงไปด้วย สามารถใช้เลขลำดับได้
var khrueangduem = ["โอวัลติน", "น้ำเก๊กฮวย", "โค้ก", "ชาอูหลง"];

var raikan = "";
khrueangduem.forEach(function(x, i) {
  raikan += i + ". " + x + "\n";
});
alert(raikan);

ได้
0. โอวัลติน
1. น้ำเก๊กฮวย
2. โค้ก
3. ชาอูหลง

นอกจากนี้หากใส่พารามิเตอร์ตัวที่ ๓ พารามิเตอร์นั้นจะแทนตัวแถวลำดับทั้งหมดเอง เช่น
var chue = ["ฟลุค", "โช้ค", "ท็อป"];
var s = "";
chue.forEach(function(x, i, ar) {
  s += x + "ได้ที่ " + (1 + i) + " ในกลุ่ม {" + ar + "}\n";
});
alert(s);
ได้
ฟลุคได้ที่ 1 ในกลุ่ม {ฟลุค,โช้ค,ท็อป}
โช้คได้ที่ 2 ในกลุ่ม {ฟลุค,โช้ค,ท็อป}
ท็อปได้ที่ 3 ในกลุ่ม {ฟลุค,โช้ค,ท็อป}



ความเปลี่ยนแปลงของข้อมูลในแถวลำดับที่ใช้วนซ้ำ

ข้อมูลในแถวลำดับที่นำมาเข้า .forEach นั้นแค่ถูกใส่เป็นอาร์กิวเมนต์ให้มาแทนในพารามิเตอร์ของฟังก์ชัน ดังนั้นต่อให้มีการแทนค่าใหม่เข้าไปในตัวแปรที่เป็นพารามิเตอร์นั้นก็ไม่ได้เป็นการแก้ค่าแต่อย่างใด
var ar = [1, 2, 3];

ar.forEach(function(x) {
  x = x + 10;
});
alert(ar); // ได้ 1,2,3

จะเห็นว่าค่าในแถวลำดับ ar ไม่ได้มีการเปลี่ยนแปลงแม้จะใส่ค่าใหม่ใน .forEach

แต่หากสมาชิกข้างในเป็นออบเจ็กต์ก็จะเกิดการเปลี่ยนแปลงพรอเพอร์ตีข้างในได้

เช่น
var pokemon = [
  { chue: "พาราส", lv: 20 },
  { chue: "คงปัง", lv: 21 },
  { chue: "ดิกดา", lv: 23 }
];

pokemon.forEach(function(x) {
  x.lv += 2;
});
alert(pokemon[0].lv); // ได้ 22
alert(pokemon[1].lv); // ได้ 23
alert(pokemon[2].lv); // ได้ 25

คุณสมบัติที่กล่าวมานี้ไม่เพียงแค่ forEach แต่ฟังก์ชันอื่นที่จะแนะนำในบทนี้เองก็เช่นกัน



เมธอด map

เมธอด map นั้นคล้ายกับ forEach คือจะวนซ้ำไล่ตามสมาชิกในแถวลำดับ แต่จะต่างกันตรงที่ฟังก์ชันที่ใช้ใน map นั้นจะต้องมีการคืนค่ากลับมา และค่านั้นจะถูกนำไปสร้างเป็นแถวลำดับใหม่

ตัวอย่างเช่น ใช้
var kanmusu = ["คิริชิมะ", "อากางิ", "คงโงว", "ยูดาจิ"];
var kanmusu_kaini = kanmusu.map(function(x) {
  return x + " ไคนิ ";
});
alert(kanmusu_kaini); // ได้ คิริชิมะ ไคนิ ,อากางิ ไคนิ ,คงโงว ไคนิ ,ยูดาจิ ไคนิ
alert(kanmusu); // ได้ คิริชิมะ,อากางิ,คงโงว,ยูดาจิ


map จะคืนค่าที่คืนกลับมาเป็นแถวลำดับใหม่ ส่วนตัวข้อมูลในแถวลำดับเดิมที่ถูกใช้ map นั้นจะไม่ได้มีการเปลี่ยนแปลง



เมธอด filter

เมธอด filter ใช้สำหรับเป็นตัวกรองข้อมูลเพื่อคัดเหลือเฉพาะส่วนที่ต้องการ

วิธีการใช้คล้ายกับ map แต่ฟังก์ชันที่ใช้กับ filter จะเป็นเงื่อนไขในการเหลือข้อมูลไว้ true หรือ false ผลที่ได้คือจะคืนค่าเป็นแถวลำดับของสมาชิกตัวที่ให้ค่าเป็น true ส่วนตัวที่ false จะไม่ได้มาด้วย

ตัวอย่าง
var lek = [2, 5, 6, 9, 10, 11, 19, 28];

var lekkhu = lek.filter(function(x) {
  return x % 2 == 0;
});
alert(lekkhu); // ได้ 2,6,10,28

var lekkhi = lek.filter(function(x) {
  return x % 2 == 1;
});
alert(lekkhi); // ได้ 5,9,11,19


อีกตัวอย่าง
var pokemon = [
  { chue: "การ์ดี", nak: "19.0" },
  { chue: "เนียวโรโม", nak: "12.4" },
  { chue: "เคซีย์", nak: "19.5" },
  { chue: "วันริกี", nak: "19.5" },
  { chue: "มาดัตสึโบมิ", nak: "4.0" }
];
var pokemon_bao = pokemon.filter(function(x) {
  return x.nak < 15;
});
alert(pokemon_bao.length); // ได้ 2
alert(pokemon_bao[0].chue); // ได้ เนียวโรโม
alert(pokemon_bao[1].chue); // ได้ มาดัตสึโบมิ
alert(pokemon_bao[2].chue); // ได้ TypeError: Cannot read property 'chue' of undefined

จากตัวอย่างนี้เป็นการคัดกรองเฉพาะ pokemon ตัวที่มี nak (น้ำหนัก) ต่ำว่า 15 ผลที่ได้จึงเหลือแค่ ๒ ตัว



เมธอด every และ some

เมธอด every จะนำเอาสมาชิกแต่ละตัวในแถวลำดับมาเข้าฟังก์ชันที่ใส่ลงไป แล้วจะดูว่าทุกตัวล้วนคืนค่า true มาหรือเปล่า ถ้า true ทั้งหมดจึงจะคืนค่า true แต่ถ้ามีไม่ใช่ true แม้แต่ตัวเดียวก็จะคืนค่า false

ส่วน some นั้นจะคล้ายกัน แต่จะคืนค่า true เมื่อมี true แม้แค่เพียงตัวเดียว ต้องไม่มี true สักตัวจึงจะได้ false

ตัวอย่าง
var pokemon = [
  { chue: "อีวุย", sung: 0.3, nak: 6.5 },
  { chue: "เชาเวอร์ส", sung: 1.0, nak: 29.0 },
  { chue: "ธันเดอร์ส", sung: 0.8, nak: 24.5 },
  { chue: "บูสเตอร์", sung: 0.9, nak: 25.0 }
];

var f1 = function(x) {
  return x.sung > 0.5;
};
alert(pokemon.map(f1)); // ได้ false,true,true,true
alert(pokemon.some(f1)); // ได้ true
alert(pokemon.every(f1)); // ได้ false
alert(pokemon.filter(f1).length); // ได้ 3

var f2 = function(x) {
  return x.nak > 30;
};
alert(pokemon.map(f2)); // ได้ false,false,false,false
alert(pokemon.some(f2)); // ได้ false
alert(pokemon.every(f2)); // ได้ false
alert(pokemon.filter(f2).length); // ได้ 0

var f3 = function(x) {
  return x.nak < 30;
};
alert(pokemon.map(f3)); // ได้ true,true,true,true
alert(pokemon.some(f3)); // ได้ true
alert(pokemon.every(f3)); // ได้ true
alert(pokemon.filter(f3).length); // ได้ 4

อนึ่ง หากไม่สนเรื่องค่าที่คืนกลับมาแล้ว เมธอดทั้ง every, some, filter, map ล้วนสามารถทำหน้าที่แทน forEach ได้ เพราะยังไงฟังก์ชันที่ถูกใส่ลงไปก็จะถูกนำไปทำกับสมาชิกทุกตัว เพียงแต่ forEach จะไม่ได้คืนค่าอะไรออกมาเท่านั้น



เมธอด reduce และ reduceRight

เมธอดสุดท้ายในกลุ่มนี้ ซึ่งมีการทำงานที่ค่อนข้างซับซ้อนกว่า ๕ ตัวที่แนะนำมาข้างต้นสักหน่อย นั่นคือ .reduce

เมธอด reduce จะทำการจัดการกับสมาชิกในแถวลำดับ โดยที่รอบแรกจะทำกับ ๒ ตัวแรก (ตำแหน่ง 0 กับ 1) โดยจะมาแทนในพารามิเตอร์ตัวแรกและตัวที่ ๒

จากนั้นค่าที่คืนกลับในรอบแรกจะกลายมาเป็นพารามิเตอร์ในการวนซ้ำรอบที่ ๒ โดยที่รอบนี้พารามิเตอร์ตัวที่ ๒ จะเป็นค่าตัวที่ ๓ (ตำแหน่ง 2)

จากนั้นค่าที่คืนกลับจากรอบนี้ก็จะถูกนำไปใช้ในรอบถัดไป และไล่ไปเรื่อยๆ จนครบจำนวนสมาชิก

สมมุติว่ามีสมาชิก n ตัว ฟังก์ชันจะถูกทำทั้งหมด n-1 ครั้ง โดยครั้งที่ i จะใช้สมาชิกตัวที่ i มาเป็นพารามิเตอร์ตัวที่ ๒

หากต้องการเลขลำดับให้ใส่เป็นพารามิเตอร์ตัวที่ ๓ ต่อได้

รูปแบบการเขียนเป็นดังนี้
แถวลำดับ.reduce(function(a, b, i) {
  return ค่าที่จะแทนใน a ในรอบถัดไป;
});

ในที่นี้ b คือสมาชิกตัวหลังที่ไล่ไปถึง ส่วน i คือลำดับของสมาชิกตัวนั้น ส่วน a ในรอบแรกจะเป็นสมาชิกตัวแรก (ลำดับที่ 0) แต่ตั้งแต่รอบถัดไปจะเป็นค่าที่ได้มาจากรอบที่แล้ว

อธิบายเป็นคำพูดแบบนี้อาจเข้าใจได้ยาก ดูตัวอย่างประกอบน่าจะช่วยให้เข้าใจได้ง่ายขึ้น

ตัวอย่าง
var merazoma = ["เม", "รา", "โซ", "ม่า"];
var s = "";
var phonlap = merazoma.reduce(function(a, b, i) {
  s += "รอบที่ " + i + ": ";
  s += "a = " + a + ", ";
  s += "b = " + b + "\n";
  return a + b;
});
alert(s + "สุดท้ายได้: " + phonlap);
ได้
รอบที่ 1: a = เม, b = รา
รอบที่ 2: a = เมรา, b = โซ
รอบที่ 3: a = เมราโซ, b = ม่า
สุดท้ายได้: เมราโซม่า

ในตัวอย่างนี้รอบแรก a เป็นสมาชิกตัวแรก คือ "เม" ส่วน b เป็นตัวถัดมา คือ "รา" สุดท้ายฟังก์ชันนี้คืนค่า "เมรา" ออกมา และถูกนำไปแทนเป็น a ในรอบถัดไป

ไล่แบบนี้ไปเรื่อยๆ จนจบแถวลำดับ

หากเขียนใหม่ให้ทำแบบเดียวกันโดยใช้ while จะได้แบบนี้
var merazoma = ["เม", "รา", "โซ", "ม่า"];
var s = "";
var i = 1;
var len = merazoma.length;
var a = merazoma[0];
while (i < len) {
  var b = merazoma[i];
  s += "รอบที่ " + i + ": ";
  s += "a = " + a + ", ";
  s += "b = " + b + "\n";
  a = a + b;
  i++;
}
var phonlap = a;
alert(s + "สุดท้ายได้: " + phonlap);

จะเห็นว่าพอใช้ .reduce แล้วจะเขียนสั้นกว่าเขียน while ตามแบบเดิม

ส่วนเมธอด reduceRight จะคล้ายกับเมธอด reduce แต่ต่างกันตรงที่ทำจากขวามาซ้าย ไล่จากตัวสุดท้ายก่อน

ตัวอย่าง
var merazoma = ["ม่า", "รา", "กี", "เบ"];
var s = "";
var phonlap = merazoma.reduceRight(function(a, b, i) {
  s += "รอบที่ " + i + ": ";
  s += "a = " + a + ", ";
  s += "b = " + b + "\n";
  return a + b;
});
alert(s + "สุดท้ายได้: " + phonlap);
ได้
รอบที่ 2: a = เบ, b = กี
รอบที่ 1: a = เบกี, b = รา
รอบที่ 0: a = เบกีรา, b = ม่า
สุดท้ายได้: เบกีราม่า

เมื่อใช้ .reduceRight เลขลำดับซึ่งแทนในพารามิเตอร์ตัวที่ ๓ นั้นจะนับถอยหลังแทน โดยเริ่มจาก ความยาว-2 ไล่ไปจนถึง 0



ประยุกต์ใช้ reduce และ reduceRight

ประโยชน์ที่พอจะเห็นใช้งาน .reduce และ .reduceRight กันนั้น เช่น

การรวมสมาชิกทุกตัวในแถวลำดับ
var lek = [3, 5, 9, 12, 18];
var ruam = lek.reduce(function(a, b) {
  return a + b;
});
alert(ruam); // ได้ 47

การหาค่าสูงสุดหรือต่ำสุดในแถวลำดับ
var lek = [66, 32, 11, 71, 43, 25, 38];

var maksut = lek.reduce(function(a, b) {
  if (a > b) return a;
  else return b;
});
alert(maksut); // ได้ 71

var noisut = lek.reduce(function(a, b) {
  if (a < b) return a;
  else return b;
});
alert(noisut); // ได้ 11

หาว่าตัวไหนยาวที่สุด พร้อมทั้งดูค่าลำดับของตัวนั้น
var chue = ["นางาโตะ", "มุตสึ", "อิเสะ", "ยุกิกาเซะ", "ฮิวงะ", "คางะ"];
var yaosut = chue.reduce(function(a, b, i) {
  if (i == 1) a = { i: 0, chue: a };
  b = { i: i, chue: b };
  if (a.chue.length > b.chue.length) return a;
  else return b;
});
alert("ที่ชื่อยาวสุดคือ " + yaosut.chue + " ลำดับที่ " + yaosut.i); // ได้ ที่ชื่อยาวสุดคือ ยุกิกาเซะ ลำดับที่ 3


จะเห็นว่าฟังก์ชันต่างๆเหล่านี้ช่วยให้การวนซ้ำเพื่อทำอะไรต่างๆในสมาชิกของแถวลำดับนั้นง่ายลง เขียนสั้นลง หากเทียบกับการใช้ while เพื่อวนซ้ำ หากใช้เป็นก็สามารถนำไปใช้ทำอะไรต่างๆได้มากมาย




-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> javascript

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
python
-- numpy
-- matplotlib

-- pandas
-- pytorch
maya
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
javascript
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
เรียนภาษาจีน
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

เรียนรู้วิธีการใช้ regular expression (regex)
หลักการเขียนทับศัพท์ภาษาจีนกวางตุ้ง
การใช้ unix shell เบื้องต้น ใน linux และ mac
หลักการเขียนทับศัพท์ภาษาจีนกลาง
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
บันทึกการเที่ยวสวีเดน 1-12 พ.ค. 2014
แนะนำองค์การวิจัยและพัฒนาการสำรวจอวกาศญี่ปุ่น (JAXA)
เล่าประสบการณ์ค่ายอบรมวิชาการทางดาราศาสตร์โดยโซวเคนได 10 - 16 พ.ย. 2013
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
บันทึกการเที่ยวญี่ปุ่นครั้งแรกในชีวิต - ทุกอย่างเริ่มต้นที่สนามบินนานาชาติคันไซ
หลักการเขียนทับศัพท์ภาษาญี่ปุ่น
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ
ทำไมถึงอยากมาเรียนต่อนอก
เหตุผลอะไรที่ต้องใช้ภาษาวิบัติ?

ไทย

日本語

中文