تعريف الكلاس unordered_multiset
تم إضافة هذا الكلاس إبتداءاً من الإصدر c++11
و هو يستخدم لإنشاء كائن يمثل حاوية تخزن العناصر التي نضيفها فيها بترتيب معين يتم تحديده من قبل دالة مخصصة لذلك إسمها Hash()
تقوم بالتشييك على قيم أي عنصر سيتم إدخاله لتحديد المكان الذي يجب وضعه فيه مع الإشارة إلى أنه يمكن تخزين قيم مكررة فيها.
بالمبدأ الدالة Hash()
تقوم بإجراء عملية حسابية على قيم أي عنصر يتم إدخاله.
أي ناتج جديد غير مكرر ترجعه الدالة Hash()
يتم تخزينه في الذاكرة في مكان خاص يقال له Bucket.
أي عنصر جديد يتم إضافته تقوم الدالة Hash()
بإجراء العملية الحسابية عليه و مقارنة الناتج النهائي مع قيمة كل Buckets نتجت سابقاً و في حال تطابق الناتج مع قيمة أي Bucket سيتم وضعه في آخرها, أما في حال عدم تطابق الناتج مع قيمة أي Bucket سيتم إضافة Bucket جديد و وضعه فيها كالتالي.

كما أنه بإمكانك تعريف الدالة طريقة عمل الدالة Hash()
بنفسك حتى تحدد الطريقة التي سيتم على أساسها إنشاء Buckets و ترتيب العناصر ضمنهم.
على سبيل المثال إذا كنت تنوي إنشاء حاوية خاصة لتخزين الأعداد, يمكنك جعل الدالة Hash()
تنشئ Buckets على حسب عدد الأرقام التي يتألف منها كل عدد. عندها سيتم ترتيب العناصر كالتالي:
- العدد الذي يتألف من رقم واحد مثل
3
- 7
سيتم وضعهما في Bucket.
- العدد الذي يتألف من رقمين واحد مثل
12
- 45
سيتم وضعهما في Bucket.
- العدد الذي يتألف من ثلاثة أرقام مثل
100
- 273
سيتم وضعهما في Bucket و هكذا..
معلومة تقنية
الفرق الوحيد بين الحاوية التي تنشئها من الكلاس unordered_set
و الحاوية التي تنشئها من الكلاس unordered_multiset
هو أن هذا الأخير يمكنه أن يحتوي على أكثر من عنصر عندهم نفس القيمة.
الآن, بما أن مفاتيح العناصر في هذه الحاوية يمكن أن تكون مكررة فهذا يعني أنه لا يمكنك الإعتماد على قيم المفاتيح للتفرقة بين العناصر حيث أن المفتاح الواحد يمكن أن يرمز لعدة عناصر.
لاستخدام الكلاس unordered_multiset
- أي حتى تتمكن من إنشاء كائنات منه - يجب تضمين الملف #include<unordered_set>
لأنه موجود فيه.
بناء الكلاس
template < class Key, // unordered_multiset::key_type/value_type
class Hash = hash<Key>, // unordered_multiset::hasher
class Pred = equal_to<Key>, // unordered_multiset::key_equal
class Alloc = allocator<Key> // unordered_multiset::allocator_type
> class unordered_multiset;
إذاً عند إنشاء كائن من الكلاس unordered_multiset
يجب أن نمرر له نوع البيانات الذي نريد تخزينه فيه مكان الباراميتر Key
.
أمثلة شاملة حول التعامل مع الكلاس unordered_multiset
في كل مثال موضوع قمنا باستخدام دوال جديدة حتى تعرف كيف تستخدم جميع الدوال التي ذكرناها في الجدول.
في المثال التالي قمنا بتعريف كائن من unordered_multiset
مع تحديد أنه يمكن أن يحتوي على عناصر نوعها string
.
بعدها قمنا بإضافة بعض العناصر فيه و من ثم طباعة كم Bucket تم إنشاؤها و إجمالي عدد العناصر التي قمنا بإضافتها.
بعدها قمنا بعرض جميع قيم العناصر الموجودة فيه بواسطة حلقة.
ملاحظة: قمنا باستخدام الدالة emplace()
لإضافة العناصر, الدالة bucket_count()
لمعرفة كم Bucket تم إنشاؤها بشكل تلقائي, الدالة size()
لمعرفة عدد العناصر التي تم إضافتها. عند عرض جميع قيم عناصر الكائن, قمنا باستخدام الدالة begin()
للحصول على مؤشر للعنصر الأول لأننا سنبدأ من عنده و الدالة end()
للحصول على مؤشر للعنصر الأخير لأننا سنتوقف عنده.
المثال الأول
main.cpp
#include <iostream>
#include <unordered_set>
using namespace std;
int main()
{
// string يمكنه أن يحتوي على قيم نوعها unordered_multiset هنا قمنا بتعريف كائن من الكلاس
unordered_multiset<string> ums;
// emplace() باستخدام الدالة ums هنا قمنا بإضافة 9 عناصر في الكائن
ums.emplace("Mon");
ums.emplace("Tue");
ums.emplace("Wed");
ums.emplace("Thu");
ums.emplace("Fri");
ums.emplace("Sat");
ums.emplace("Sun");
ums.emplace("Thu"); // تعمدنا إعادة إدخال هذه القيمة مرة ثانية حتى تلاحظ أنه يمكن إدخال قيم مكررة
ums.emplace("Fri"); // تعمدنا إعادة إدخال هذه القيمة مرة ثانية حتى تلاحظ أنه يمكن إدخال قيم مكررة
// bucket_count() باستخدام الدالة ums تم إنشاؤها في الكائن Bucket هنا قمنا بطباعة كم
cout << "ums total buckets = " << ums.bucket_count() << endl;
// size() باستخدام الدالة ums هنا قمنا بطباعة عدد عناصر الكائن
cout << "ums total elements = " << ums.size() << endl;
cout << "ums values =";
// ums هنا قمنا بإنشاء حلقة تقوم في كل دورة بطباعة قيمة عنصر جديد من العناصر الموجودة في الكائن
// Bucket وصولاً لآخر عنصر موجود في آخر Bucket إبتداءاً من أول عنصر موجود في أول
for (auto it = ums.begin(); it != ums.end(); ++it)
{
cout << " " << *it;
}
return 0;
}
سنحصل على النتيجة التالية عند التشغيل.
ums total buckets = 17
ums total elements = 9
ums values = Sun Tue Wed Thu Thu Fri Fri Mon Sat
في المثال التالي قمنا بتعريف كائن من unordered_multiset
مخصص لتخزين قيم نوعها string
مع إضافة عدة قيم فيه عند تعريفه.
بعدها قمنا بعرض القيم الموجودة في كل Bucket فيه بواسطة حلقتين متداخلتين.
ملاحظة: قمنا باستخدام الدالة bucket_count()
لمعرفة كم Bucket تم إنشاؤها بشكل تلقائي و الدالة bucket_size()
لمعرفة عدد العناصر الموجودة في كل Bucket.
المثال الثاني
main.cpp
#include <iostream>
#include <unordered_set>
using namespace std;
int main()
{
// بالإضافة إلى أننا قمنا بإضافة عدة قيم فيه string يمكنه أن يحتوي على عناصر نوعها unordered_multiset هنا قمنا بتعريف كائن من الكلاس
unordered_multiset<string> ums = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
// totalBuckets التي تم إنشاؤها في المتغير Buckets هنا قمنا بتخزين عدد جميع الـ
int totalBuckets = ums.bucket_count();
// ums الموجودين في الكائن Buckets هنا قمنا بإنشاء حلقة تمر على جميع الـ
for(int i=0; i<totalBuckets; i++)
{
// Bucket في كل دورة سيتم طباعة رقم الـ
cout << "bucket[" << i << "] elements:";
// و من ثم طباعة القيم الموجودة فيها بواسطة الحلقة التالية
for(auto it=ums.begin(i); it!=ums.end(i); ++it)
{
cout << " " << *it;
}
cout << endl;
}
}
سنحصل على النتيجة التالية عند التشغيل.
bucket[0] elements: Fri
bucket[1] elements:
bucket[2] elements: Sat Mon
bucket[3] elements:
bucket[4] elements: Sun Wed
bucket[5] elements:
bucket[6] elements:
bucket[7] elements: Tue
bucket[8] elements:
bucket[9] elements:
bucket[10] elements: Thu
في المثال التالي قمنا بتعريف كائن من unordered_multiset
مخصص لتخزين قيم نوعها string
مع إضافة عدة قيم فيه عند تعريفه.
بعدها قمنا بعرض القيم الموجودة في كل Bucket فيه بواسطة حلقتين متداخلتين.
ملاحظة: قمنا باستخدام الدالة bucket_count()
لمعرفة كم Bucket تم إنشاؤها بشكل تلقائي, الدالة size()
لمعرفة عدد العناصر التي تم إضافتها. عند عرض قيم العناصر الموجودة في كل Bucket قمنا باستخدام الدالة begin()
للحصول على مؤشر للعنصر الأول فيها لأننا سنبدأ من عنده و الدالة end()
للحصول على مؤشر للعنصر الأخير فيها لأننا سنتوقف عنده.
المثال الثالث
main.cpp
#include <iostream>
#include <unordered_set>
using namespace std;
int main()
{
// بالإضافة إلى أننا قمنا بإضافة عدة قيم فيه string يمكنه أن يحتوي على عناصر نوعها unordered_multiset هنا قمنا بتعريف كائن من الكلاس
unordered_multiset<string> ums = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
// totalBuckets التي تم إنشاؤها في المتغير Buckets هنا قمنا بتخزين عدد جميع الـ
int totalBuckets = ums.bucket_count();
// ums الموجودين في الكائن Buckets هنا قمنا بإنشاء حلقة تمر على جميع الـ
for(int i=0; i<totalBuckets; i++)
{
// و عدد العناصر الموجودة فيها Bucket في كل دورة سيتم طباعة رقم الـ
cout << "bucket[" << i << "] total elements = " << ums.bucket_size(i);
cout << endl;
}
}
سنحصل على النتيجة التالية عند التشغيل.
bucket[0] total elements = 1
bucket[1] total elements = 0
bucket[2] total elements = 2
bucket[3] total elements = 0
bucket[4] total elements = 2
bucket[5] total elements = 0
bucket[6] total elements = 0
bucket[7] total elements = 1
bucket[8] total elements = 0
bucket[9] total elements = 0
bucket[10] total elements = 1
في المثال التالي قمنا بتعريف كائن من unordered_multiset
مخصص لتخزين قيم نوعها string
مع إضافة عدة قيم فيه عند تعريفه.
بعدها قمنا بالبحث عن قيم محددة فيه لمعرفة كم عنصر يملك هذه القيم.
ملاحظة: قمنا باستخدام الدالة count()
لمعرفة كم مرة القيم مكررة في الكائن.
المثال الرابع
main.cpp
#include <iostream>
#include <unordered_set>
using namespace std;
int main()
{
// بالإضافة إلى أننا قمنا بإضافة عدة قيم فيه string يمكنه أن يحتوي على عناصر نوعها unordered_multiset هنا قمنا بتعريف كائن من الكلاس
unordered_multiset<string> ums = {"Apple", "Orange", "Apple", "Tomato", "Banana", "Pineapple", "Blueberry"};
// "Apple" التي تملك القيمة ums هنا قمنا بطباعة عدد العناصر الموجودة في الكائن
cout << "'Apple' is exist " << ums.count("Apple") << " time(s)\n";
// "Carrot" التي تملك القيمة ums هنا قمنا بطباعة عدد العناصر الموجودة في الكائن
cout << "'Carrot' is exist " << ums.count("Carrot") << " time(s)";
return 0;
}
سنحصل على النتيجة التالية عند التشغيل.
'Apple' is exist 2 time(s)
'Carrot' is exist 0 time(s)
في المثال التالي قمنا بتعريف كائن من unordered_multiset
مخصص لتخزين قيم نوعها string
مع إضافة عدة قيم فيه عند تعريفه.
بعدها قمنا بالبحث عن أول عنصر يملك قيمة محددة فيه و في حال كانت موجودة سنقوم بحذف العنصر الذي يملكها من الكائن.
ملاحظة: قمنا باستخدام الدالة find()
للبحث عن القيمة في الكائن و الدالة erase()
لحذف العنصر من الكائن.
المثال الخامس
main.cpp
#include <iostream>
#include <unordered_set>
using namespace std;
int main()
{
// بالإضافة إلى أننا قمنا بإضافة عدة قيم فيه string يمكنه أن يحتوي على عناصر نوعها unordered_multiset هنا قمنا بتعريف كائن من الكلاس
unordered_multiset<string> ums = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
// لتخزين مكان العنصر الذي نجد القيمة التي نبحث عنها فيه unordered_multiset<string>::iterator منا قمنا بتعريف كائن من
unordered_multiset<string>::iterator it;
// it عن عنصر يملك القيمة 4, و بالتالي في حال وجود عنصر يملك القيمة 4 سيتم تخزين عنوانه في الكائن ums هنا قمنا بالبحث في الكائن
// فيه للإشارة إلى أنه لم يتم إيجاد أي عنصر يملك هذه القيمة ums.end() في حال عدم وجود عنصر يملك القيمة 4 سيتم تخزين القيمة التي ترجعها
it = ums.find("Wed");
// فهذا يعني أنه تم إيجاد القيمة و بالتالي سيتم تنفيذ الكود الموضوع فيها ums.end() لا تساوي القيمة التي ترجعها الدالة it في حال كانت قيمة
if (it != ums.end())
{
// و من ثم قمنا بحذفه ums هنا قمنا بطباعة قيمة العنصر الذي تم إيجاده في الكائن
cout << "First element with value '"<< *it << "' is removed from ums\n";
ums.erase(it);
}
cout << "ums values =";
// إبتداءاً من أول عنصر وصولاً لآخر عنصر فيه ums هنا قمنا بإنشاء حلقة تقوم في كل دورة بطباعة قيمة عنصر جديد من العناصر الموجودة في الكائن
for (auto it = ums.begin(); it != ums.end(); ++it)
{
cout << " " << *it;
}
return 0;
}
سنحصل على النتيجة التالية عند التشغيل.
First element with value 'Wed' is removed from ums
ums values = Fri Thu Sun Tue Sat Mon
في المثال التالي قمنا بتعريف كائنين من unordered_multiset
مع تحديد أنه يمكن أن يحتويان على عناصر نوعها int
.
بعدها قمنا بتبديل عناصرهما و من ثم طباعة القيم التي أصبحت موجودة في كلٍّ منهما.
ملاحظة: قمنا باستخدام الدالة swap()
لتبديل قيمهما.
المثال السادس
main.cpp
#include <iostream>
#include <unordered_set>
using namespace std;
int main()
{
// بالإضافة إلى أننا قمنا بإضافة عدة قيم فيهما int يمكنهما أن يحتويان على قيم نوعها unordered_multiset هنا قمنا بتعريف كائنين من الكلاس
unordered_multiset<int> ums1 = {1, 2, 3, 4};
unordered_multiset<int> ums2 = {5, 6, 7, 8};
// ums2 مع قيم الكائن ums1 هنا قمنا بتبديل قيم الكائن
ums1.swap(ums2);
cout << "ums1 values = ";
// إبتداءاً من أول عنصر وصولاً لآخر عنصر فيه ums1 هنا قمنا بإنشاء حلقة تقوم في كل دورة بطباعة قيمة عنصر جديد من العناصر الموجودة في الكائن
for (auto it = ums1.begin(); it != ums1.end(); ++it)
{
cout << *it << " ";
}
cout << "\nums2 values = ";
// إبتداءاً من أول عنصر وصولاً لآخر عنصر فيه ums2 هنا قمنا بإنشاء حلقة تقوم في كل دورة بطباعة قيمة عنصر جديد من العناصر الموجودة في الكائن
for (auto it = ums2.begin(); it != ums2.end(); ++it)
{
cout << *it << " ";
}
return 0;
}
سنحصل على النتيجة التالية عند التشغيل.
ums1 values = 8 7 6 5
ums2 values = 4 3 2 1
في المثال التالي قمنا بتعريف كائن من unordered_multiset
مخصص لتخزين قيم نوعها int
مع تحديد الطريقة التي سيتم على أساسها توزيع القيم في Buckets و إضافة عدة قيم فيه عند تعريفه.
لتحديد الطريقة التي سيتم على أساسها توزيع القيم في Buckets قمنا بإنشاء كلاس إسمه Hash
و إعادة تعريف العامل ()
حتى يقارن قيمة أي عنصر نريد إضافته نسبةً لعدد الأحرف الموجودة فيه, عندها يتم وضع الأعداد التي تتألف من رقم واحد في Bucket, و الأعداد التي تتألف من رقم رقمين في Bucket, و الأعداد التي تتألف من ثلاثة أعداد في Bucket و هكذا..
في النهاية قمنا بعرض القيم الموجودة في كل Bucket فيه بواسطة حلقتين متداخلتين.
المثال السابع
main.cpp
#include <iostream>
#include <unordered_set>
using namespace std;
// unordered_multiset سنستخدمه لاحقاً لتحديد كيف ستترتب العناصر في الحاوية التي ننشئها من الكلاس Hash هنا قمنا بتعريف كلاس إسمه
class Hash
{
public:
// Bucket حتى يقرر في أي unordered_multiset هنا قمنا بتعريف العامل الذي سيستخدمه الكائن الذي ننشئه من الكلاس
// val سيتم وضع العنصر الذي نضيفه فيه مع الإشارة إلى أن القيمة التي يملكها العنصر سيتم تمريرها مكان الباراميتر
size_t operator() (const int &val) const
{
// بشكل مؤقت لأننا نحتاج إجراء تعديل عليها val سيتم وضع قيمة num المتغير
// val سنستخدمه لحساب عدد الأرقام الموجودة في المتغير digits المتغير
int num = val;
int digits = 1;
// num و من ثم حذف أول رقم في المتغير digits هنا قمنا بإنشاء حلقة تقوم في كل دورة بإضافة واحد على قيمة المتغيرد
// num تمثل عدد الأرقام الموجودة في العدد الذي كان يحتويه المتغير digits عندما تتوقف هذه الحلقة ستكون قيمة المتغير
while (num != 0)
{
digits++;
num = num / 10;
}
// سيتم إضافة العنصر Bucket لأنها التي ستحدد في digits في النهاية سيتم إرجاع قيمة المتغير
return digits;
}
};
int main()
{
// مع تحديد أنه يعتمد على الكلاس string يمكنه أن يحتوي على عناصر نوعها unordered_multiset هنا قمنا بتعريف كائن من الكلاس
// من أجل تحديد الطريقة التي سيتم فيها ترتيب العناصر التي نضيفها فيه, بالإضافة إلى أننا قمنا بإضافة عدة قيم فيه Hash
unordered_multiset<int, Hash> ums = {6, 34, 72, 3, 1, 123, 55, 100, 2020};
// totalBuckets التي تم إنشاؤها في المتغير Buckets هنا قمنا بتخزين عدد جميع الـ
int totalBuckets = ums.bucket_count();
// ums الموجودين في الكائن Buckets هنا قمنا بإنشاء حلقة تمر على جميع الـ
for(int i=0; i<totalBuckets; i++)
{
// Bucket في كل دورة سيتم طباعة رقم الـ
cout << "bucket[" << i << "] elements:";
// و من ثم طباعة القيم الموجودة فيها بواسطة الحلقة التالية
for(auto it=ums.begin(i); it!=ums.end(i); ++it)
{
cout << " " << *it;
}
cout << endl;
}
return 0;
}
سنحصل على النتيجة التالية عند التشغيل, و لاحظ كيف تم ترتيب العناصر على أساس عدد الأرقام التي يتألف منها كل عنصر.
bucket[0] elements:
bucket[1] elements: 1 3 6
bucket[2] elements: 55 72 34
bucket[3] elements: 100 123
bucket[4] elements: 2020
bucket[5] elements:
bucket[6] elements:
bucket[7] elements:
bucket[8] elements:
bucket[9] elements:
bucket[10] elements: