אובייקט `Set`

אובייקט `Set`

מאמר זה מסביר את אובייקט ה-Set.

נסביר את אובייקט ה-Set עם דוגמאות מעשיות.

YouTube Video

אובייקט Set

Set הוא אובייקט מובנה שמטרתו לטפל באוספים של ערכים ייחודיים ללא כפילויות. הוא מאפשר לבצע הסרת כפילויות ובדיקות קיום בצורה פשוטה יותר ממערכים, ומקל לכתוב פעולות קבוצתיות כמו איחוד וחיתוך.

יסודות: יצירה ושימוש ב-Sets

ראשית, נבחן כיצד ליצור 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.

שיטות איטרציה

Set ניתן לאיטרציה, כך שניתן לעבור עליו באמצעות for...of או forEach. הסדר הוא סדר ההכנסה.

להלן דוגמאות נפוצות לשימוש ב-for...of ו-forEach.

 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});
  • החתימה של הפונקציה ב-forEach היא value, value, set (לשם תאימות עם Map), אך בפועל בדרך כלל יש צורך רק בפרמטר הראשון - value.

המרה בין מערכים ל-Sets (שימושי להסרת כפילויות)

כאן נציג שיטה פשוטה להסרת כפילויות ממערך, וכיצד להמיר Set חזרה למערך.

להלן דוגמה להסרת כפילויות ממערך באמצעות 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]
  • דפוס עבודה זה קצר ומהיר, ולכן הוא נפוץ להסרת כפילויות ממערכים. הוא יעיל במיוחד עבור ערכים פרימיטיביים.

אובייקטים וטיפול בהפניות

אובייקטים ב-Set נבדקים לפי הפניה (reference), לכן מופעים שונים עם תוכן זהה ייחשבו כאיברים נפרדים.

הקוד הבא מדגים מה קורה כאשר מוסיפים אובייקטים ל-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)
  • זיהוי כפילויות עבור אובייקטים מתבצע לפי זהות ההפניה, לכן אם ברצונך להסיר כפילויות לפי תוכן בלבד תצטרך לסדר או לעבד אותם.

ערכים מיוחדים: טיפול ב-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.
  • כתוצאה מכך, יישארו רק NaN אחד ו-0 אחד ב-Set.
  • כלל ההשוואה של Set דומה ל-Object.is, אך לא זהה לחלוטין. Object.is(+0, -0) מחזיר false, אך ב-Set הם נחשבים לזהים. שים לב להבדל זה.

כלי שימוש נפוץ: פעולות קבוצה (איחוד, חיתוך, הפרש)

ניתן לכתוב פעולות קבוצה בצורה ברורה יותר בעזרת Set. להלן דוגמאות מימוש נפוצות.

הנה דוגמאות לפונקציות של איחוד, חיתוך והפרש.

 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]
  • ניתן לכתוב פעולות קבוצה בפשטות באמצעות סינון ושילוב בין Set למערכים. בעת עבודה עם נתונים גדולים, לביצועי ה- O(1) של has יש השפעה מייעלת.

דוגמה מעשית: מציאת הבדלים בין מערכים (זיהוי פריטים שנוספו/הוסרו)

הדוגמה הבאה מראה כיצד להשתמש ב-Set למציאת ההפרש בין שני מערכים (רשימה ישנה וחדשה). כך ניתן לזהות אילו פריטים נוספו ואילו הוסרו.

 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] }
  • שיטה זו נוחה במיוחד לזיהוי שינויים ברשימות מזהים, תגיות ומצבים דומים. הכי קל להשתמש בה לערכים פרימיטיביים.

הבדלים בין 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 שימושי למעקב זמני אחרי קיום אובייקטים, אך אי אפשר למנות את אבריו או לקבל את גודלו.

ביצועים ובחירת מתי להשתמש

בבחינת השימוש ב-Set, חשוב להבין את מאפייני הביצועים שלו ואת טיב הנתונים שלך.

  • has, add ו-delete פועלות בדרך כלל ביעילות של כמעט O(1). לכן, בתרחישים שבהם מבצעים הרבה בדיקות קיום או הסרת כפילויות, Set לרוב עדיף על מערכים.
  • יש להיזהר אם ברצונך להסיר כפילויות של אובייקטים על סמך התוכן (הערכים) שלהם. מכיוון ש-Set משווה לפי הפניה, הדרך הפרקטית היא להשתמש ב-מזהים (IDs) או מפתחות, או לסדר/להמיר אובייקטים לערכים פרימיטיביים כאשר יש צורך בהשוואה לפי ערך.
  • Set שימושי במיוחד לשיפור הבהירות של קוד עבור אוספים קטנים עד בינוניים. מצד שני, אם אתה מטפל בכמות גדולה מאוד של נתונים או ממיר לעיתים קרובות בין מערכים ל-Sets, מומלץ למדוד ביצועים בפועל.

מכשולים נפוצים

Set הוא נוח, אך אם אינך בקיא במאפייניו, ייתכן שתיתקל בהתנהגויות מפתיעות. להלן נקודות שכדאי לשים לב אליהן:.

  • אובייקטים נבדקים לפי הפניה, ולכן גם אם תוכנם זהה, לא נחשבים ככפולים.
  • Set שומר על סדר ההכנסה, אך אי אפשר לגשת לאיברים לפי אינדקס כמו במערכים. אם ברצונך לגשת לפי אינדקס, המרה של ה-Set למערך הכרחית.
  • WeakSet לא ניתן למניה/לאיטרציה, ו-ניתן לאחסן בו רק אובייקטים. שים לב ששימושיו מוגבלים.
  • NaN נחשב לאותו ערך, ו-+0 ו--0 לא מובדלים זה מזה. זה נובע מכלל ההשוואה Same-value-zero.

סיכום

Set הוא מבנה נתונים נוח המאפשר לך לטפל באוספים של ערכים ייחודיים בצורה אינטואיטיבית. באמצעותו ניתן להסיר כפילויות ממערכים, לבצע בדיקות קיום במהירות, או ליישם פעולות קבוצתיות כמו איחוד וחיתוך בקוד פשוט וקריא.

מן הצד השני, מאחר שאובייקטים משווים לפי הפניה, יש לנקוט אמצעים נוספים כדי להשוות לפי תוכן בלבד.

בהבנת התכונות האלו ושימוש נכון בהן, Set הופך לכלי עוצמתי לשיפור הקריאות והתחזוקה של הקוד.

תוכלו לעקוב אחר המאמר שלמעלה באמצעות Visual Studio Code בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.

YouTube Video