Skip to main content

Solving FizzBuzz in Ruby

Episode 127 · July 4, 2016

See how different approaches can affect the code you write using FizzBuzz as an example

Basics


Transcripts

Earn a free month

لعبة FizzBuzz بلغة البرمجة روبي

سنتحدث في هذه الحلقة عن أمرٍ مختلفٍ عن الحلقات السابقة. حيث سينصب الحديث على مشكلة برمجية، وسنتكلّم عن العقلية والفلسفة المستخدمة لحل مشكلة ما وكيف لذلك أن يؤثّر على كتابة الشيفرة البرمجية في حل مشكلة ما.

لا بد وأن سمعت من قبل عن لعبة FizzBuzz. الفكرة ببساطة ما هي إلا سلسلة من الأرقام، فمثلا من الرقم واحد وحتى مئة أو ألف. ولكن مع استبدال الأرقام التي تقبل القسمة على ثلاثة ومن دون باقي بالكلمة Fizz. واستبدال الأرقام التي تقبل القسمة على خمسة بالكلمة Buzz. أما الأرقام التي تقبل القسمة على الرقم ثلاثة وخمسة معًا فتسُتبدّل بـِ FizzBuzz

ما دفعني إلى اختيار هذه اللعبة كمثال هو أنها مثال واضح يظهر من خلاله كيف أن طريقة التفكير ومعالجة مشكلةٍ ما لها من الأثر الكبير على طريقة كتابة الشيفرة البرمجية.

من الحلول الشائعة لحل هذه المشكلة (الحل الأول):

1.upto(100) do |i|
  if i % 3 == 0 && i % 5 == 0
    puts ”FizzBuzz”
  elsif i % 3 == 0
    puts “Fizz”
  elsif i % 5 == 0
    puts “Buzz”
  else
    puts i
  end
end

لاحظ أن الشرط الأول (if i % 3 == 0 && i % 5 == 0) يجب أن يكون في البداية وإلا لن يتمّ الوصول إليه وذلك لأن باقي الشروط (الثاني والثالث) تحقق هذا الشرط فهي مجموعة جزئية منه.

الآن إلى مربط الفرس، والفكرة التي أريد الحديث عنها هو كيف أن تركيزنا انصب على المُتغيّر i أي العدد (integer) فكل شيء في الشيفرة السابقة يدور حول هذا الرقم (لاحظ تكرار i)، وهو ما يحدد النتيجة النهائية وما سيتمّ طباعته كمُخرج. وهذا سيدفعنا إلى أسلوب التجربة والتخمين في حل المشكلة والحل وربما قد يكون الحل أسهل في الوصول إليه من الطرق الأخرى وهو حل مقبول ولا شائبة فيه، ولكنه في نفس الوقت معقد ولا يتناسب مع مشكلة صغيرة مثل لعبة FizzBuzz. ربما هناك حلول أبسط من الحل السابق.

في حقيقة الأمر مع المشاكل البسيطة مثل المشكلة التي بين يدينا من الممكن التفكير بالمشكلة بمنظور آخر تمامًا، وذلك عن طريق التركيز على المُخرج والذي هو السلسلة النصيّة (Sting). إن لب المشكلة يتمحور حول الرقم ثلاثة وخمسة (أو أيا كان الرقم)، فماذا لو صببنا تركيزنا على السلسلة النصية (المُخرج output)؟:

1.upto(100) do |i|
  str = “”

  #Code Here

  puts str
end

كما هو مُلاحظ سنبدأ مع سلسلة نصية خاوية ونجعل من هذه السلسلة هي المُخرج (output). والآن ما سنقوم به هو التالي (الحل الثاني):

1.upto(100) do |i|
  str = “”

  str += “Fizz” if i % 3 == 0
  str += “Buzz” if i % 5 == 0
  str += i      if str.empty?

  puts str
end

المُميز حول التغيير السابق كون أن هذين الشرطان المُستقلين يُكافئان الشرط الأول (if i % 3 == 0 && i % 5 == 0) من الحل الأول ضمنيًا، والذي كان يحتوي على شرطين بنفس الوقت. أما في الحل الثاني ضربنا عصفورين بحجر واحد وذلك بما أن الشرطين مُستقلين بالفعل عن بعضها في الحل الثاني فسيتم الجمع بين السلسلتين النصيتين لتكونان معًا “FizzBuzz”.

كل ما تبقى لدينا هو طباعة الرقم نفسه وذلك فقط عندما تكون السلسلة النصية خاوية. بمعنى عدم تحقق الشرطين السابقين.

لاحظ أنه في الحل الثاني ثم الاستغناء تمامًا عن الشرط الأول في الحل الأول والذي يحتوي تكرار لا غاية منه (duplication) في حساب كل حالة على حدة وذلك في: elsif i % 3 == 0 وفي: elsif i % 5 == 0 مقابل حساب الحالتين معًا if i % 3 == 0 && i % 5 == 0. أما في الحل الثاني فقد تم تجنب هذا التكرار الفائض تمامًا. فالشيفرة البرمجية تعي المشكلة التي بين أيدينا تمامًا وعلى مستوى دقيق للغاية لدرجة أنه لا داعي الأخذ بعين الاعتبار الحالة “FizzBuzz”، وذلك بها أن هذه الحالة قد تم أخذها بعين الاعتبار ضمنيًا ومُسبقًا. وهذا أمر حسن لا شك، فعندما يتمّ النظر إلى المشكلة والأخذ بعين الاعتبار هذه التفاصيل الدقيقة فيمكن عندها كتابة شيفرة برمجية مُبتكرة ومقروءة تركّز على المُخرجات (output). لاحظ كيف أن التركيز كان منصبًا على المُتغير i في الحل الأول على عكس الحل الثاني والذي يُركّز على المُخرجات وهو السلسلة النصية str.

تجدر الإشارة هنا إلى أنه لا يمكن الاستفادة من أسلوب البرمجة المقادة بالاختبار (TDD) وذلك لأن TDD يركز على جزئية: فيما إذا كان التطبيق العملي للشيفرة البرمجية صحيح أم لا، ولا يدفعك بالقدر الكافي على تقديم شيفرة برمجية مقروءة على الرغم من المراحل الثلاث التي يمر بها.

يُستفاد من هذا الأسلوب في التفكير في حل المشاكل البرمجية هو أن الطريقة المستخدمة في معالجة المشكلة البرمجية ستسهل من تطوير الشيفرة مستقبلًا، بمعنى أنه من الممكن جدًا أن تتطوّر لعبة "فز-بز" ويُضاف لها تعديلات وخواص إضافية وهنا أحد الحلين السابقين سيكون قابلا للتعامل مع هذا التطوّر بشكل أفضل من الآخر.

نستنتج مما سبق أن الشيفرة البرمجية المكتوبة ما هي إلا انعكاس لكيفية فهمنا للمشكلة وكيف قد قام العقل بمعالجتها، وبالتالي عندما لا نستطيع فهم المشكلة سيقود هذا الأمر المبرمج إلى محاولة حل المشكلة عن طريق التجربة والخطأ، وذلك فقط للوصول إلى حلٍ ما، وبالتالي تجربة احتمالات قد تكون مقعدة لمشكلة بسيطة، ليظهر هذا التعقيد على الشيفرة ويجعل منها مقعدة وصعبة القراءة والفهم.

طبعا المشكلة الحالية يمكن لها أن تحل بطرق أخرى، فالحلين السابقين يعتمدان على حلقة تكرار (loop) ولكن من الممكن جدًا استخدام الأصناف (classes) أيضًا، ولا بأس في ذلك على الإطلاق. طبعًا يَعتمد الأمر دائمًا على المشكلة التي بين يدينا وما هي الحلول التي نبحث عنها، وكل ما عليك فعله هو تجربة طرق مختلفة للوصول إلى الحل المناسب.

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

إن كنت ترغب بالتركيز على هذا النوع من المواضيع أعلمني في التعليقات.

Loading...

Subscribe to the newsletter

Join 31,353+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.