ออบเจ็กต์ `Set`

ออบเจ็กต์ `Set`

บทความนี้อธิบายเกี่ยวกับออบเจ็กต์ Set

เราจะอธิบายออบเจ็กต์ Set พร้อมตัวอย่างการใช้งานจริง

YouTube Video

ออบเจ็กต์ Set

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

พื้นฐาน: การสร้างและใช้งาน Set

ก่อนอื่น มาดูวิธีการสร้าง Set การเพิ่มและลบสมาชิก การตรวจสอบค่าที่มีอยู่ และการนับจำนวนสมาชิก

ด้านล่างนี้คือตัวอย่างพื้นฐานของการสร้าง Set ใหม่และแสดงการใช้ add, has, delete และ size

 1// Create a Set and demonstrate add, has, delete, and size
 2const s = new Set();
 3
 4s.add(1);
 5s.add(2);
 6s.add(2); // duplicate, ignored
 7
 8console.log(s.has(1)); // true
 9console.log(s.has(3)); // false
10
11s.delete(2);
12console.log(s.size); // 1
13
14console.log([...s]); // [1]
  • ดังที่แสดงในโค้ดนี้ Set จะลบค่าซ้ำกันของชนิดข้อมูลพื้นฐานโดยอัตโนมัติ และคุณสามารถรับจำนวนของสมาชิกได้ด้วย size

วิธีการวนรอบ (Iteration)

Set สามารถวนซ้ำได้ ดังนั้นคุณสามารถวนลูปผ่านมันได้ด้วย for...of หรือ forEach ลำดับจะแสดงตามลำดับที่เพิ่มค่าเข้าไป

นี่คือตัวอย่างการใช้ for...of และ forEach กับ Set

 1// Iterate a Set with for...of and forEach
 2const s = new Set(['a', 'b', 'c']);
 3
 4for (const v of s) {
 5  console.log('for...of:', v);
 6}
 7
 8s.forEach((value, sameValue, setRef) => {
 9  // Note: second arg is same as first for Set API to match Map signature
10  console.log('forEach:', value);
11});
  • พารามิเตอร์ของ callback forEach คือ value, value, set (เพื่อความเข้ากันกับ Map) แต่ในทางปฏิบัติโดยปกติจะใช้แค่พารามิเตอร์แรกคือ value

การแปลงระหว่าง Array และ Set (มีประโยชน์สำหรับการลบค่าซ้ำ)

ตรงนี้จะแสดงเทคนิคง่ายๆ ในการลบค่าซ้ำใน array และวิธีแปลง Set กลับเป็น array

ตัวอย่างด้านล่างเป็นการลบค่าซ้ำใน array โดยการนำไปผ่าน Set

1// Deduplicate an array using Set
2const arr = [1, 2, 2, 3, 3, 3];
3const deduped = [...new Set(arr)];
4console.log(deduped); // [1, 2, 3]
5
6// Convert a Set to an array using Array.from
7const s = new Set([4, 5, 6]);
8const arrFromSet = Array.from(s);
9console.log(arrFromSet); // [4, 5, 6]
  • รูปแบบนี้กระชับและรวดเร็ว จึงนิยมใช้สำหรับลบค่าซ้ำใน array เหมาะอย่างยิ่งสำหรับค่าประเภท primitive

ออบเจ็กต์และการจัดการ Reference

ออบเจ็กต์ใน Set จะถูกเปรียบเทียบตาม Reference ดังนั้นออบเจ็กต์ที่มีค่าเท่ากันแต่เป็นคนละ instance จะถูกนับเป็นสมาชิกต่างหาก

โค้ดตัวอย่างต่อไปนี้จะแสดงสิ่งที่เกิดขึ้นเมื่อคุณเพิ่มออบเจ็กต์เข้าไปใน Set

 1// Objects are compared by reference in a Set
 2const obj1 = { x: 1 };
 3const obj2 = { x: 1 };
 4
 5const s = new Set();
 6s.add(obj1);
 7s.add(obj2);
 8
 9console.log(s.size); // 2 (different references)
10console.log(s.has(obj1)); // true
11console.log(s.has({ x: 1 })); // false (different object)
  • การตรวจสอบค่าซ้ำของออบเจ็กต์จะขึ้นอยู่กับ Reference ดังนั้นถ้าคุณต้องการลบซ้ำตามเนื้อหาของออบเจ็กต์ คุณต้อง serialize หรือประมวลผลเพิ่มเติมเอง

ค่าพิเศษ: วิธีจัดการ NaN และ -0/+0

Set ใช้กฎการเปรียบเทียบแบบ Same-value-zero เพื่อตัดสินความเท่ากันของค่า วิธีการเปรียบเทียบนี้มีลักษณะดังนี้เมื่อเกี่ยวกับตัวเลข:

  • NaN จะถือว่าเท่ากับ NaN
  • +0 และ -0 จะไม่ถูกแยกแยะและถือว่าเป็นค่าเดียวกัน

ดังนั้นเมื่อคุณเพิ่มค่าดังกล่าวลงใน Set จะได้พฤติกรรมดังนี้:

 1// NaN and zero behavior in Set
 2const s = new Set();
 3
 4s.add(NaN);
 5s.add(NaN);
 6console.log(s.size); // 1 (NaN considered the same)
 7
 8s.add(+0);
 9s.add(-0);
10console.log(s.size); // still 2 (NaN + 0)
11console.log([...s]); // [NaN, 0] (order may vary but only one zero)
  • โดยปกติ (NaN === NaN) จะได้ค่า false แต่สำหรับ Set แล้ว NaN ทุกค่า ถือเป็น 'ค่าที่เท่ากัน'
  • +0 และ -0 สามารถแยกได้ทางคณิตศาสตร์ แต่ใน Set จะถูกมองว่าเป็น 0
  • ผลลัพธ์คือ ใน Set จะมีเพียง NaN เดียวและ 0 เดียวเท่านั้น
  • กฎการเปรียบเทียบของ Set คล้ายคลึงกับ Object.is แต่ ไม่เหมือนกันทุกประการ Object.is(+0, -0) จะได้ค่า false แต่ใน Set แล้วจะถือว่าเป็นค่าเดียวกัน โปรดสังเกตความแตกต่างนี้

การใช้งานทั่วไป: การดำเนินการแบบเซ็ต (ยูเนียน อินเตอร์เซกชัน ดีฟเฟอเรนซ์)

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

นี่คือตัวอย่างของฟังก์ชัน union (รวม), intersection (ร่วม), และ difference (ต่างกัน)

 1// Set operations: union, intersection, difference
 2function union(a, b) {
 3  return new Set([...a, ...b]);
 4}
 5
 6function intersection(a, b) {
 7  return new Set([...a].filter(x => b.has(x)));
 8}
 9
10function difference(a, b) {
11  return new Set([...a].filter(x => !b.has(x)));
12}
13
14// Demo
15const A = new Set([1, 2, 3]);
16const B = new Set([3, 4, 5]);
17
18console.log('union', [...union(A, B)]); // [1,2,3,4,5]
19console.log('intersection', [...intersection(A, B)]); // [3]
20console.log('difference A\\B', [...difference(A, B)]); // [1,2]
  • คุณสามารถดำเนินการแบบเซ็ตได้โดยใช้ filter ร่วมกับ Set และ array อย่างง่าย เมื่อจัดการข้อมูลจำนวนมาก ความเร็ว O(1) ของ has ช่วยให้การดำเนินการเร็วขึ้น

ตัวอย่างการใช้งานจริง: การหาความแตกต่างของ array (ตรวจจับค่าที่เพิ่ม/ลบ)

ตัวอย่างต่อไปนี้จะแสดงวิธีการใช้ Set เพื่อหาความแตกต่างระหว่างสอง array (รายการเก่ากับรายการใหม่) วิธีนี้ช่วยค้นหาได้ว่าสมาชิกใดถูกเพิ่มและสมาชิกใดถูกลบออก

 1// Find added and removed items between two arrays
 2function diffArrays(oldArr, newArr) {
 3  const oldSet = new Set(oldArr);
 4  const newSet = new Set(newArr);
 5
 6  const added = [...newSet].filter(x => !oldSet.has(x));
 7  const removed = [...oldSet].filter(x => !newSet.has(x));
 8
 9  return { added, removed };
10}
11
12const oldList = [1, 2, 3];
13const newList = [2, 3, 4, 5];
14
15console.log(diffArrays(oldList, newList));
16// { added: [4,5], removed: [1] }
  • วิธีนี้สะดวกมากสำหรับการตรวจสอบความแตกต่างของรายการ ID, รายการแท็ก หรือกรณีที่คล้ายกัน เหมาะที่สุดเมื่อใช้กับค่าประเภท primitive

ความแตกต่างระหว่าง WeakSet และ Set (การจัดการหน่วยความจำ)

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

1// WeakSet basics (objects only, not iterable)
2const ws = new WeakSet();
3let obj = { id: 1 };
4ws.add(obj);
5
6console.log(ws.has(obj)); // true
7
8obj = null; // Now the object is eligible for GC; WeakSet won't prevent collection

WeakSet สามารถเก็บได้เฉพาะออบเจ็กต์และไม่สามารถวนรอบสมาชิกได้ ด้านล่างนี้คือตัวอย่างข้อจำกัดของ WeakSet—เก็บได้เฉพาะออบเจ็กต์และไม่สามารถวนรอบได้

 1// WeakSet basics (objects only, not iterable)
 2const ws = new WeakSet();
 3
 4// --- Only objects can be added ---
 5try {
 6	ws.add(1); // number
 7} catch (e) {
 8	console.log("Error: WeakSet can only store objects. Adding a number is not allowed.");
 9}
10
11try {
12	ws.add("text"); // string
13} catch (e) {
14	console.log("Error: WeakSet can only store objects. Adding a string is not allowed.");
15}
16
17// --- WeakSet is not iterable ---
18try {
19	for (const value of ws) {
20		console.log(value);
21	}
22} catch (e) {
23	console.log("Error: WeakSet is not iterable. You cannot use for...of to loop over its elements.");
24}
25
26// --- Cannot convert to array ---
27try {
28	console.log([...ws]);
29} catch (e) {
30	console.log("Error: WeakSet cannot be converted to an array because it does not support iteration.");
31}
32
33// The object becomes eligible for garbage collection
34let obj = { id: 1 };
35ws.add(obj);
36obj = null;
  • WeakSet เหมาะสำหรับการติดตามการมีอยู่ของออบเจ็กต์แบบชั่วคราว แต่คุณไม่สามารถนับจำนวนสมาชิกหรือตรวจ enumerate ได้

ประสิทธิภาพและวิธีเลือกใช้งาน

เมื่อจะตัดสินใจเลือกใช้ Set ควรเข้าใจประสิทธิภาพและลักษณะข้อมูลของคุณก่อน

  • โดยทั่วไป has, add และ delete จะทำงานได้ใกล้เคียง O(1) โดยเฉลี่ย ดังนั้นในกรณีที่ต้องเช็คการมีอยู่หรือกำจัดค่าซ้ำบ่อย ๆ Set จะเหมาะสมกว่าการใช้ array
  • ควรระวังหากคุณต้องการลบค่าซ้ำตาม เนื้อหาของออบเจ็กต์ (ค่าภายใน) เนื่องจาก Set เปรียบเทียบตาม reference วิธีปฏิบัติที่ดีคือ ใช้ ID หรือคีย์อื่น ๆ หรือ serialize ออบเจ็กต์ให้เป็นค่า primitive ก่อนจะนำไปเก็บใน Set เมื่อจำเป็นต้องเปรียบเทียบตามค่า
  • Set มีประโยชน์มากในการปรับปรุงความอ่านง่ายของโค้ดสำหรับ ชุดข้อมูลขนาดเล็กถึงขนาดกลาง ในทางกลับกัน หากต้องจัดการกับข้อมูลจำนวนมากหรือแปลง array กับ Set บ่อย ๆ ควรทดสอบและวัดประสิทธิภาพจริงก่อนใช้งาน

ข้อควรระวังที่พบบ่อย

Set ใช้งานง่าย แต่หากไม่เข้าใจลักษณะการทำงาน อาจพบพฤติกรรมที่คาดไม่ถึงได้ นี่คือจุดที่ควรระวังเป็นพิเศษ:

  • ออบเจ็กต์จะถูก เปรียบเทียบตาม Reference ดังนั้นถึงจะมีเนื้อหาเหมือนกัน ก็ถือว่าเป็นคนละตัว
  • Set คงลำดับการเพิ่มสมาชิก แต่คุณ ไม่สามารถเข้าถึงสมาชิกโดยใช้ index เหมือน array ถ้าต้องการเข้าถึงสมาชิกด้วย index ให้แปลง Set เป็น Array ก่อน
  • WeakSet ไม่สามารถ enumerate และ เก็บได้เฉพาะออบเจ็กต์เท่านั้น ควรทราบว่าการใช้งานมีข้อจำกัด
  • NaN จะถูกถือว่าเป็นค่าเดียวกัน และ +0 กับ -0 จะไม่ถูกแยกออกจากกัน เนื่องจากกฎการเปรียบเทียบแบบ Same-value-zero

สรุป

Set เป็นโครงสร้างข้อมูลที่สะดวกและช่วยให้จัดการ ชุดค่าที่ไม่ซ้ำกัน ได้อย่างเป็นธรรมชาติ คุณสามารถใช้ Set เพื่อลบค่าซ้ำใน array ตรวจสอบการมีอยู่ของค่าอย่างรวดเร็ว หรือดำเนินการแบบเซ็ต เช่น union และ intersection ด้วยโค้ดที่ อ่านง่ายและกระชับ

ในทางกลับกัน เนื่องจากออบเจ็กต์จะ เปรียบเทียบตาม Reference หากต้องการเปรียบเทียบตามเนื้อหาจะต้องมีวิธีจัดการพิเศษเพิ่มเติม

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

คุณสามารถติดตามบทความข้างต้นโดยใช้ Visual Studio Code บนช่อง YouTube ของเรา กรุณาตรวจสอบช่อง YouTube ด้วย

YouTube Video