كائن `Set`

تشرح هذه المقالة كائن الـSet۔

سنشرح كائن الـSet بأمثلة عملية۔

YouTube Video

كائن Set

Set هو كائن مدمج يُستخدم لمعالجة مجموعات من القيم الفريدة غير المكررة۔ يتيح لك التخلص من التكرارات والتحقق من الوجود بشكل أبسط من المصفوفات، ويجعل عمليات المجموعات مثل الاتحاد والتقاطع أسهل في التنفيذ۔

الأساسيات: إنشاء واستخدام الـ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۔

طرق التكرار على العناصر

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)، ولكن في الواقع غالباً ما تحتاج فقط إلى قيمة العنصر الأول۔

التحويل بين المصفوفات وSet (مفيد لإزالة التكرارات)

هنا نوضح تقنية بسيطة لإزالة التكرارات من مصفوفة، وكيفية تحويل كائن 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 بناءً على المرجع، لذا فإن النسخ المختلفة التي تحتوي نفس المحتوى تعتبر عناصر منفصلة۔

يوضح الكود التالي ما يحدث عند إضافة كائنات إلى الـ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، ولكنه يستخدم مراجع ضعيفة، مما يسمح بجمع عناصره عند عدم الحاجة إليها (garbage collection)۔ يبين ما يلي بعض الاستخدامات الأساسية لـ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 يقارن عن طريق المرجع، فيمكنك استخدام المعرفات أو المفاتيح أو تسلسل الكائنات إلى قيم أولية قبل استخدامها إذا تطلب الأمر المقارنة بحسب القيمة۔
  • Set مفيد بشكل خاص لتحسين وضوح الكود عند التعامل مع مجموعات صغيرة إلى متوسطة الحجم۔ من ناحية أخرى، إذا كنت تتعامل مع عدد كبير جدًا من العناصر أو تقوم كثيرًا بالتحويل بين المصفوفات و Sets، فمن المستحسن إجراء اختبارات أداء حقيقية۔

الأخطاء الشائعة

Set مريح وسهل الاستخدام، لكن إذا لم تكن على دراية بمواصفاته فقد تتفاجأ بسلوكيات غير متوقعة۔ فيما يلي بعض النقاط التي يجب الانتباه إليها:۔

  • يتم مقارنة الكائنات عن طريق المرجع، حتى لو كان محتواها متطابقاً، لا تعتبر الكائنات المختلفة مكررة۔
  • Set يحافظ على ترتيب الإدخال، لكن لا يمكنك الوصول للعناصر حسب الفهرس كما هو الحال مع المصفوفات۔ إذا أردت الوصول إلى عناصره باستخدام الفهرسة، قم بتحويل الـSet إلى مصفوفة أولاً۔
  • WeakSet لا يمكن تعداده، و يمكنه فقط تخزين الكائنات۔ يرجى ملاحظة أن استخداماته محدودة۔
  • NaN يتم التعامل معه كقيمة واحدة، كما لا يتم التمييز بين +0 و-0۔ ويرجع ذلك إلى قاعدة المقارنة Same-value-zero۔

الملخص

Set هو بنية بيانات مريحة تتيح لك التعامل مع مجموعات من القيم الفريدة بسهولة۔ يمكنك استخدامه لإزالة التكرارات من المصفوفات، وإجراء تحقق سريع من الوجود، أو تنفيذ عمليات المجموعات كالانحاد والتقاطع باستخدام كود بسيط وواضح۔

من ناحية أخرى، وبما أن الكائنات تتم مقارنتها حسب المرجع، فستحتاج لاتخاذ تدابير إضافية إذا أردت الحكم على التساوي بناءً على محتواها۔

من خلال فهم هذه الخصائص واستخدامها بشكل مناسب، يصبح Set اختيارًا قويًا لتحسين وضوح الكود وسهولة صيانته۔

يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔

YouTube Video