التابع Enumerable.sort_by في روبي

من موسوعة حسوب

يرتِّب التابع sort_by عناصر الكائن القابل للتعداد الذي استدعي معه باستعمال مجموعة من المفاتيح المولدة عبر تمرير قيم العناصر إلى الكتلة المعطاة.

لا يُضمَن بأن تكون النتيجة مستقرة. عندما تعيد عملية الموازنة بين مفتاحين القيمة 0، إذ لن يُتوقَع كيفية ترتيب هذين العنصرين ومن منهما سيسبق الآخر.

إن لم تُعطَ أية كتلة، فسيعيد التابع كائنًا جديدًا من النوع Enumerator.

البنية العامة

sort_by { |obj| block }  array
sort_by  an_enumerator

التنفيذ الحالي للتابع sort_by يولد مصفوفةً من الصفوف (tuples) تحوي مجموعة العناصر الأصلية والقيم المعيَّنة إلى كلٍّ منها. هذا يجعل تنفيذ التابع sort_by يستهلك كثيرًا من الموارد عندما تكون مجموعات المفاتيح بسيطة.

require 'benchmark'

a = (1..100000).map { rand(100000) }

Benchmark.bm(10) do |b|
  b.report("Sort")    { a.sort }
  b.report("Sort by") { a.sort_by { |a| a } }
end

ناتج تنفيذ الشيفرة هو:

user     system      total        real
Sort        0.180000   0.000000   0.180000 (  0.175469)
Sort by     1.980000   0.040000   2.020000 (  2.013586)

على أي حال، افترض أن موازنة المفاتيح هي عملية مهمة. ترتِّب الشيفرة التالية بعض الملفات بحسب وقت التعديل باستعمال التابع sort الأساسي:

files = Dir["*"]
sorted = files.sort { |a, b| File.new(a).mtime <=> File.new(b).mtime }
sorted   #=> ["mon", "tues", "wed", "thurs"]

للأسف، عملية الترتيب هذه غير ناجعة، إذ تولد كائنين جديدين من النوع File أثناء إجراء كل عملة موازنة. هنالك تقنية أفضل بعض الشيء وهي استعمال التابع Kernel.test لتوليد أوقات التعديل مباشرة:

files = Dir["*"]
sorted = files.sort { |a, b|
  test(?M, a) <=> test(?M, b)
}
sorted   #=> ["mon", "tues", "wed", "thurs"]

هذا لا يزال يولد العديد من الكائنات Time غير الضرورية. هنالك طريقة أفضل وهي تخزين مفاتيح الترتيب (التي هي أوقات التعديل في حالتنا هذه) قبل إجراء عملية الترتيب. يسمي مستخدمو Perl غالبًا هذه الطريقة "تحويل شورتزيان" (Schwartzian transform) نسبةً إلى راندال شوارتز (Randal Schwartz). سننشئ أولًا مصفوفة مؤقتة، إذ يكون كل عنصر مصفوفةً - بحد ذاته - تحوي مفاتيح الترتيب جنبًا إلى جنب مع اسم الملف. سنرتب هذه المصفوفة ثم نستخرج اسم الملف منها بعدئذٍ:

sorted = Dir["*"].collect { |f|
   [test(?M, f), f]
}.sort.collect { |f| f[1] }
sorted   #=> ["mon", "tues", "wed", "thurs"]

هذا بالضبط ما يفعله التابع sort_by ضمنيًّا:

sorted = Dir["*"].sort_by { |f| test(?M, f) }
sorted   #=> ["mon", "tues", "wed", "thurs"]

القيمة المعادة

تعاد مصفوفة جديد تحوي عناصر الكائن القابل للترتيب المعطى بعد ترتيبها.

أمثلة

مثال على استعمال التابع sort_by:

%w{apple pear fig}.sort_by { |word| word.length }
              #=> ["fig", "pear", "apple"]

انظر أيضًا

  • التابع map: يعيد مصفوفة جديدة تحوي النتائج المقابلة لكل عنصر من عناصر الكائن القابل للتعداد المعادة من الكتلة المعطاة بعد تمريره إليها.

مصادر