27

[نشاط] كتابة خوارزمية (القاسم المشترك الأكبر)

مرحبا وأهلا بكم،

في الأسبوع الماضي طرحت نشاط صنع خوارزمية، وبعد طرح النشاط بدقائق، اكتشفت أني ارتكبت بعض الأخطاء الجسيمة

https://arabia.io/go/4733

منها، النشاط كان صعبا جدا، كتابة خوارزمية الميراث امر معقد جدا، ويحتاج دقة بالغة وعددا كبيرا من المدخلات، وأيضا، كان من المفروض أن نبدأ بشيء بسيط

والثاني هو أني لم أوفر شرحا عن كيف ستعمل الخوارزمية

لذا هذه المرة قررت عمل شيء أكثر بساطة، مع توفير شرح بالطبع

القاسم المشترك الأكبر

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

مما يعني أن الدالة تقبل 2 input وتطرح 1 output

لحساب القاسم المشترك هناك طريقتين

الأول باستعمال خوارزمية اقليدس

في حال نسيتم كيف يتم حسابه، فالأمر كالتالي

(سأشرح مثال ويكيبيديا)

لدينا عدد ما، فنقل 252 وعدد آخر 198، ونريد أن نحسب القاسم المشترك الأكبر لهما،

نقوم أولا بقسم 252 على 198 لنجد باقي القسمة، ولنسمه العدد c، ثم نقوم مجددا بقسم العدد الأصغر من مدخلات البداية (وهو 198) على باقي القسمة الجديدة لنجد عدد آخر، ثم نقسم هذا العدد أيضا على العدد السابق (c) ونستمر بالأمر حتى نجد باقي القسمة يساوي 0، بهذا، فإن باقي القسمة التي أتى قبله هو القاسم المشترك الأكبر

مثال ويكي

القاسم المشترك الأكبر للعددين 252 و 198:

252 = 198 * 1 + 54 ‘ أربع وخمسون هو باقي قسمة 252 على 198

فنجد القاسم المشترك للعددين 198 و 54

198 = 54 * 3 + 36 ‘ ست وثلاثون هو باقي القسمة.

نكرر العملية هذه المرة مع : 54 و 36

54 = 36 * 1 + 18

مرة أخرى : 36 = 18 * 2 + 0

هنا وصلنا للصفر فيكون العدد الثاني 18 هو القاسم المشترك الأكبر.

الطريقة الثانية

طريقة الطرح

وهي أن تقوم بطرح العدد الأصغر من الأكبر لتحصل على ناتج، ثم تطرحه من العدد الأصغر في البداية وتقوم بالأمر حتى تجد النتيجة صفر، أي عندما يساوي a = b وعندها ذلك هو القاسم المشترك

252 - 198 = 54
198 - 54 = 144
144 - 54 = 90
90 - 54 = 36
36 - 18 = 18
18 - 18 = 0

والقاسم المشترك هو 18

تستطيعون استعمال أيّ طريقة تريدونها ما دامت النتيجة ستكون واضحة،

وبالطبع بأي لغة محببة لكم،

سأطرح أنا مثالي في الرد الأول وهو باستخدام الجافاسكربت

بالتوفيق

يرجى الدخول لحسابك أو تسجيل حساب لتستطيع إضافة تعليق
حساب جديد دخول

التعليقات

16

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

بالنسبة لحلي، استخدمت Scala. لغة أتمنى تعلمها بسبب Actors ((خارج نطاق النشاط :) )

object arabia {

  def gcd(x: Int, y: Int): Int = {

    require(x >= 0, "X must be greater than 0")
    require(y >= 0, "Y must be greater than 0")

    def loop(x: Int, y: Int): Int = {
            if ( x == y ) x
            else if ( x > y ) loop(x - y, y)
            else                            loop(x, y - x)
    }

    if ( x.min(y) == 0 ) x.max(y)
    loop(x,y)

  }                                               //> gcd: (x: Int, y: Int)Int

    gcd(198,252)                              //> res0: Int = 18
}

السطر

gcd(198,252) 

ينفذ الدالة

  def gcd(x: Int, y: Int): Int = {

تستقبل x و y و كلهم من نوع Int. و الدالة ترجع قيمة من نوع Int

require(x >= 0, "X must be greater than 0")
require(y >= 0, "Y must be greater than 0")

تتأكد من إن القيمتين دائما أكبر من أو تساوي 0. و تقوم بإرجاع خطأ في حالة أقل من صفر. و هذه دالة موفّرة من نفس اللغة

و الدالة الداخلية

def loop(x: Int, y: Int): Int = {
        if ( x == y ) x
        else if ( x > y ) loop(x - y, y)
        else                            loop(x, y - x)
}

لا تنفذ إلا إذا استدعيت من داخل الدالة gcd. قبل استدعائها، نتأكد من أن القيم لا تساوي الصفر.

if ( x.min(y) == 0 ) x.max(y)

في لغة Scala كل شئ object حتى int. ال class من نوع int يوفر دالة اسمها min و max و ترجع القيمة الأقل و الأعلى. في حالة أحدهم صفر، الدالة gcd سترجع الأعلى. لم أأخذ حالة القيمتين صفر و لكنها وضعها سهل.

إذا القيمتين لم تكن اصفارا، دالة gcd ستستدعي دالة loop الداخلية بإرسال قيم x و y.

دالة loop ستتحقق في كل مرة من شروط اقليدس و هي ثلاث شروط

لغة Scala لا تحتاج إلى return لإرجاع القيمة. لغة Scala تسترجع آخر قيمة تم ذكرها.

احتجت لوضع بعض الشروط خارج loop لأن لا حاجة لها في كل مرة loop تنفذ. بعض حلولكم يمكن تحسينها بفصل بعض الشروط لتنفيذها مرة واحدة بدلا من كل مرة تستدعي فيها دالة gcd

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

ويستطيع كل شخص وضع مشاركته، ثم التحسين عليها مرة بعد في الردود، حتى يخرج بطريقة أسلم في كتابة الكود ومقاومة لأي خلل

15

مشاركتي بالجافاسكربت

function gcd(a, b) {
  if (isNaN(a) && isNaN(b)  ) {
    return alert("أدخل رقام من فضلك!");
  }

  if (b === 0) {
    return alert("القاسم المشترك الأكبر هو" + a);
  }
  else if (a === 0) {
    return alert("القاسم المشترك الأكبر هو" + b);
  }
  else if (a > b) {
    remind = a - b;
  }

  else {
    remind = b - a;
  }

  if (b > remind) {
    return gcd(b, remind);
  }
  else {
    return gcd(remind, b);
  }
}

var a = prompt('أدخل الرقم الأول');

var b = prompt('أدخل الرقم الثاني الآ،');

gcd(a,b);

تقوم بإدخال القيم عبر prompt ثم يتأكد الكود من أنّ ما أدخلته هو رقم وليس حرفا، ثم يحسب، لقد استخدمت طريقة الطرح هنا، وقد كتبت هذا الكود منذ وقت طويل عندما كنت أتعلم الجافاسكربت

مثال حي هنا

أظن أن الأمر نفسه يمكن تحقيقه عبر حلقة while سأحاول تطبيقه

إعادة كتابة الكود مع اختصار باستعمال دالة abs (شكرا للأخ علي)

function gcd(a, b) {
  if (isNaN(a) && isNaN(b)  ) {
    return alert("أدخل رقام من فضلك!");
  }

  if (b === 0) {
    return alert("القاسم المشترك الأكبر هو" + a);
  }
  else if (a === 0) {
    return alert("القاسم المشترك الأكبر هو" + b);
  }
  else {
    remind = Math.abs(a - b);
  }

  if (b > remind) {
    return gcd(b, remind);
  }
  else {
    return gcd(remind, b);
  }
}

var a = prompt('أدخل الرقم الأول');

var b = prompt('أدخل الرقم الثاني الآ،');

gcd(a,b);

باستخدام min (شكرا للأخ علي، مجددا) وأيضا max

function gcd(a, b) {
  if (isNaN(a) && isNaN(b)  ) {
    return alert("أدخل رقام من فضلك!");
  }

  if (b === 0) {
    return alert("القاسم المشترك الأكبر هو" + a);
  }
  else if (a === 0) {
    return alert("القاسم المشترك الأكبر هو" + b);
  }
  else {
    remind = Math.abs(a - b);
  }

   return gcd(Math.max(b, remind), Math.min(b, remind));
}

var a = prompt('أدخل الرقم الأول');

var b = prompt('أدخل الرقم الثاني الآ،');

gcd(a,b);

العفو بش تصحيح صغير اسمي اسلام :)

أعتذر حقا،

من اسم مستخدمك (IAli) ظننت ان اسمك علي

لاعليك

ماذا لو لم يدخل المستخدم " اي المدخلات " ستعرض نتائج خاطئة لذلك عدلت تعديل بسيط (وهي اول مره اكتب كود بجافا سكربت العادة استخدم الاكواد الجاهزة) قد اكون مخطئ في شيء

دالة Start بعد اضافاتي:

    function start () {
      while(true){
      var a = prompt('enter the first number');
        if(a.length != 0)
          break;

        alert("Error: You did not enter the first number !!!");
      }

      while(true){
      var b = prompt('now enter the second number');
        if(b.length != 0)
          break;

        alert("Error: you did not enter the second number !!!");
      }

    gcd(a,b);

    }

بحثت سابقا عن كيفية التأكد من أن القيمة فارغة ولم أوفق في الكثير، هذه الطريقة رائعة أيضا

يمكن التعديل ببساطة على الشرط الأول

function gcd(a, b) {
  if (isNaN(a) && isNaN(b)  ) {
    return alert("أدخل رقام من فضلك!");
  }
  else if (a.length = 0 || b.length = 0) {
    return alert("لا تدخل قيمة فارغة");
  }

  if (b === 0) {
    return alert("القاسم المشترك الأكبر هو" + a);
  }
  else if (a === 0) {
    return alert("القاسم المشترك الأكبر هو" + b);
  }
  else {
    remind = Math.abs(a - b);
  }

   return gcd(Math.max(b, remind), Math.min(b, remind));
}

var a = prompt('أدخل الرقم الأول');

var b = prompt('أدخل الرقم الثاني الآ،');

gcd(a,b);

نشاط جميل، وهذه مشاركتي البسيطة لكن بدون استخدام أحد الطريقتين المذكورتين.

اللغة المستخدمة Java

public static void main(String[] args) {

    int a,b,k;
    a = 252;
    b = 198;

    k = a;
    if(a > b) k = b;

    while(k > 0){
        if(a % k == 0 && b % k == 0){
            System.out.println(k);
            break;
        }
        k--;
    }               
}

هل يمكنك أن تشرح الكود قليلا، ما أراه هو أنك تستخدم طريقة إقليدس لحساب باقي القسمة صحيح

صحيح،

أولا احدد العدد الأصغر وأضعه في متغير k.

ثم أبدأ باستخراج باقي القسمة a و b على k، فإذا كان باقي القسمة صفر بالنسبة ل a و b ، فإن k هو القاسم المشترك الأكبر ويتم إيقاف الحلقة التكرارية، فإن لم يمكن يتم طرح 1 من k، وإعادة عملية، وهكذا.

في مشاركة أنت، استخدم مبدأ Recursive function ? صحيح.

لا أعرف ماهو Recursive Function

لكن حسب غوغل فأظن أنك تقصد أني أعيد إدخال الدالة داخل نفسها، مما يسبب دورانها حول نفسها حتى تنتهي

صحيح أخي.

هذه هي الطريقة التي اتبعتها، باستعمال C++ واعتقد انه بسيط كفاية.

int GCD(int &m, int &n)

{

int r;
if (m < 0 || n < 0)
{
    cout << "No Negative numbers allowed!" << endl;
    return -1;  // indicates failure.
}

else if (m == 0 && n != 0)
{
    return n;
}

else if (n == 0 && m != 0)
{
    return m;
}

else if (m == 0 && n == 0)
{
    cout << "Math Error" << endl;
    return -1;
}

else if (m == n)
{
    return m;
}

else if (n > m)      // checks if n is larger than m.
{
    int temp = n;
    n = m;
    m = temp;
}

while (n != 0)
{
    r = (m%n);
    m = n;
    n = r;
}
return m;

}

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

رائع، في مثال قمت أنت بدراسة كافة الاحتمالات الممكنة التي قد تؤول لها الدالة

اشكرك، التحسينات التي أتحدث عنها هي إعادة كتابة للثلاث مقارنات في الحالات الصفرية (وجود رقم او اثنين يساويان صفر) على سبيل المثال كما استعملتها في تطبيق آلة حاسبة متعددة المهام بلغة C#:

   public static double GCD(double m,double n)
    {

        double r;

        if (m == 0 || n == 0)
        {
            if (m == n)
            {           
                return -1;
            }

            else if (n != 0)
            { return n; }

            else if (m != 0)
            { return m; }
        }

        else if (m < 0 || n < 0)
        {
            return -1;
        }

        else if (n > m)
        {
            double temp = n;
            n = m;
            m = temp;
        }

        while (n != 0)
        {
            if (m == n)
            { break; }
            r = (m % n);
            m = n;
            n = r;
        }

        return m;
    }

بهذه الطريقة لن يضيع وقته (ان صح التعبير) في المقارنة بعكس مثالي للـ C++ فمثلاً ان كان كلا الرقمين صفر فسيضطر سابقاً للمقارنة مرتين اضافيتين، بينما ان لم يكن صفراً من الاساس فلديه طريق طويل قليلاً من المقارنات، ما فعلته في هذا التحديث البسيط قد لا يكون بالكثير، لكني دائماً احاول تحسين اداء البرنامج بقدر ما تسمح لي خبرتي المتواضعة (مازلت مبتدئاً). وشكراً لطرحك النشاط المسلي، كنت افضل ان تدع لنا البحث عن افضل طريقة ممكنة.

لماذا تعيد 1-، هي false في C هي 1-؟

قد لا افهم تماماً ما تقصد (لاني لست محترفاً)، لكن في الC# (سي شارب، ليس سي) القواعد محكمة قليلاً فيما يتعلق بأنواع البيانات، فلا يمكنني اعادة False لأي نوع غير الـ bool (في ضوء ما تعلمته حتى الآن)، فأعادة قيمة مثلاً مثل -1 كنوع من التنبيه او التحذير والتي من خلالها سأعلم ان هنالك مشكلة في المدخلات، -1 ببساطة وكما اعتقد انك تعرف وهذا من سؤالك، هي مجرد قيمة مستحيل ان تظهر في الظروف الصحيحة، لا يوجد عددين قاسمهم المشترك -1(على حد علمي)، لكني لا استعملها لكونها تعني false في لغة الـ C، مجرد قيمة مميزة.

أنظر ياعزيزي لست جيداً بالرياضيات ابداً، ولكن بعد ما ابيضّت اخر شعرة سوداء في رأسي خرجت بهذا الناتج، قم بتجربته واخبرني اذا كان صالحاً او لا اذا كان صالحاً سوف اشرحه. انا اختبرته على المدخلات السابقة وكان الناتج صحيحاً واذا احببت ان اترجمة لأي لغة برمجة اخرى أعرفها سأفعل، قمت بعمل الدالة بلغة C++

    int commonDenominator(int x,int y){

        if(((x>y)?x:y) - ((x>y)?y:x) == 0)
            return (x<y)?x:y;
        else
            return commonDenominator(((x>y)?y:x), ((x>y)?x:y) - ((x>y)?y:x));
    }

استخدمت طريقة الطرح، واذا كنت لاتستخدم C++ يمكنك تشغيل البرنامج هنا

كا التالي

    #include <iostream>

    using namespace std;


    int commonDenominator(int x,int y){

        if(((x>y)?x:y) - ((x>y)?y:x) == 0)
            return (x<y)?x:y;
        else
            return commonDenominator(((x>y)?y:x), ((x>y)?x:y) - ((x>y)?y:x));
    }

    int main()
    {

       cout << commonDenominator(252 ,198);

       return 0;
    }

جميل، الكود يعمل بشكل سليم، تستطيع تطويره أكثر بحيث لا يقبل سوى الأرقام وما إلى ذلك

أحاول تجريب الدالة، ولكن لا أنجح

أعدت كتابتها بلغة PHP

<?php

function commonDenominator($x,$y){

    if((($x>$y)?$x:$y) - (($x>$y)?$y:$x) == 0)
        return ($x<$y)?$x:$y;
    else
        return commonDenominator((($x>$y)?$y:$x), (($x>$y)?$x:$y) - (($x>$y)?$y:$x));
}


echo commonDenominator(252,198);
?>

يمكن تشغيلها هنا:

14

جميلة، نفس النتائج، لكن ما لاحظته في هذا الكود والكود الذي سبقه أنّك تستعمل اختصارات كبيرة في الدوار الشرطية مثل ? و :

مما يصعب قراءة الكود وفهمه لشخص آخر غير صاحبه الأصلي، مستقبلا إذا عملت في فريق ما، سيصعب على أعضاء الفريق متابعة القراءة من بعدك

فعلاً هذا صحيح كذلك استخدمت في هذا الكود عملية Recursion، لذلك قد تكون بعض الاكواد اطول ولكنها اسرع من الدالة التي كتبتها في اظهار النتيجة. ولكني اهتميت اكثر بختصار الكود اكثر.

يمكنك استبدال السطر الرابع والسابع بدالة abs لارجاع القيمة المطلقة

3-2 = |2-3| = 1

احسنـــــــت

فتصبح الداله كا التالي:

  <?php


  function commonDenominator($x,$y){

      if(abs($x-$y) == 0)
          return ($x<$y)?$x:$y;
      else
          return commonDenominator((($x>$y)?$y:$x) ,abs($x-$y));
}


    echo commonDenominator(252,198);
  ?>

جميل ايضا

($x<$y)?$x:$y

تكافئ

max ($x, $y)

احسنت تقصد min(x,y)

جميل جميل اصبحت الان الداله سهله جداً

وقابلة للقراءة شكراً لك IAli

<?php
function commonDenominator($x,$y){
    if(abs($x-$y) == 0)
        return min($x,$y);
    else
        return commonDenominator(min($x,$y) ,abs($x-$y));
}
  echo commonDenominator(252,198);
?>

صحيح شكرا للتصويب

وهذه داله بستخدام الطريقة الأولى (خوارزمية اقليدس) ، اسهل:

<?php
function commonDenominator($x,$y){
    if($x % $y == 0)
      return min($x,$y);

    return commonDenominator(min($x,$y), $x % $y);
}

echo commonDenominator(252,198);
?>

أصبح الكود أنظف بكثير الآن،

الآن هل أجد من يساعدني في تنظيف خوارزميتي D:

تصويب

($x<$y)?$x:$y

تكافئ

min ($x,$y)   

لانك وضعت الدالة في int main ( الـ int main ) عبارة عن داله .. لاتضع داخلها داله بل ضع اي داله فوقها كما انك نسيت ( علامة ; ) في نهاية طلب الدالة ونسيت امر الطباعة cout عموما انظر لتعليقي السابق شرحت كيف يكون التشغيل ، اذا لم يكن واضحاً .. سأترجمها لك بلغة اخرى

الخوارزميتين بلغة بايثون

أسهل ما يكون

def gcd1(a, b):
    while b !=0:
        t = b
        b = a % b
        a = t
    return a

def gcd2(a, b):
    while a != b:
        if a>b: a=a-b
        else: b=b-a
    return a


print gcd2(100, 150)

يمكن صياغة طريقة اقليدس كما يلي

القاسم المشترك الأكبر : بتكرار قسمة المقسوم عليه على باقي القسمة ... قبل الوصول للصفر

مثال 252 و 198

فتقسم العدد الأول ولا يهم ان كان الاكبر [1] ولكن سنسخدمه اي 252

نقسمه 252 على الثاني 198 يكون الباقي 54

ثم نقسم المقسوم عليه السابق اي 198 على الباقي السابق 54 فيكون الباقي 36

ثم نقسم المقسوم عليه السابق اي 54 على الباقي السابق 36 فيكون الباقي 18

ثم نقسم المقسوم عليه السابق اي 36 على الباقي السابق 18 فيكون الباقي 0

اذا القاسم المشترك الأكبر : 18

فدائما ما نحتاج المقسوم عليه مرتين مرة مقسوما عليه ومره هو نفسه مقسوم على باقي سابق فاذا سمينا المقسوم divided و المقسوم عليه reminder

فيمكننا كتابة التالي

divided=252  reminder=198 ->
divided=198  reminder=54 ->
divided=54  reminder=36 ->
divided=36  reminder=18 ->
divided=18  reminder=0 

أو

gcd(x, y):
  divided = x
  reminder = y
  while reminder not equal 0:
    temp = reminder
    reminder = divided % reminder
    divided = temp

فيكون الكود بالبايثون

def gcd(divided, reminder):
    while reminder: 
        temp = reminder
        reminder = divided % reminder
        divided = temp
    return divided

و يمكن اختصاره للتالي

def gcd(divided, reminder):
    while reminder:
        divided, reminder = reminder, divided % reminder
    return abs(divided)

لماذا اضيفت دالة القيمة المطلقة؟ لانه بالنهاية ان امكنك القسمة على رقم سالب بدون باقي فيمكنك القسمة على نفس الرقم بالموجب واي رقم موجب اكبر من اي رقم سالب

[1] لماذا لا يهم اسخدام الرقم الاكبر كبداية؟

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

توضيح

x%y

تعطي باقي قسمة الـ x على الـ y

recursive approach

int gcd(int p, int q) {
    if (q>0)
        return gcd(q, p%q);

    return p;
}

أليس الأفضل استخدام الشرط بـ q!=0 لتمكين القيم السالبة