مرحبا وأهلا بكم،
في الأسبوع الماضي طرحت نشاط صنع خوارزمية، وبعد طرح النشاط بدقائق، اكتشفت أني ارتكبت بعض الأخطاء الجسيمة
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
تستطيعون استعمال أيّ طريقة تريدونها ما دامت النتيجة ستكون واضحة،
وبالطبع بأي لغة محببة لكم،
سأطرح أنا مثالي في الرد الأول وهو باستخدام الجافاسكربت
بالتوفيق
التعليقات
تمرين حلو، لكن ألا ترى لو تجعل العضو يبحث عن أفضل طريقة لحل النشاط بدلا من وضع الطرق؟ البحث يحسّن من قدرة العضو على تحليل أفضل طريقة و تطبيقها. و من خلالها يستطيع الوصول لمصادر جيّدة تساعده في حل مشاكل شبيهه.
بالنسبة لحلي، استخدمت 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
مشاركتي بالجافاسكربت
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 ثم يتأكد الكود من أنّ ما أدخلته هو رقم وليس حرفا، ثم يحسب، لقد استخدمت طريقة الطرح هنا، وقد كتبت هذا الكود منذ وقت طويل عندما كنت أتعلم الجافاسكربت
مثال حي هنا
إعادة كتابة الكود مع اختصار باستعمال دالة 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);
ماذا لو لم يدخل المستخدم " اي المدخلات " ستعرض نتائج خاطئة لذلك عدلت تعديل بسيط (وهي اول مره اكتب كود بجافا سكربت العادة استخدم الاكواد الجاهزة) قد اكون مخطئ في شيء
دالة 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 ? صحيح.
هذه هي الطريقة التي اتبعتها، باستعمال 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++ فمثلاً ان كان كلا الرقمين صفر فسيضطر سابقاً للمقارنة مرتين اضافيتين، بينما ان لم يكن صفراً من الاساس فلديه طريق طويل قليلاً من المقارنات، ما فعلته في هذا التحديث البسيط قد لا يكون بالكثير، لكني دائماً احاول تحسين اداء البرنامج بقدر ما تسمح لي خبرتي المتواضعة (مازلت مبتدئاً). وشكراً لطرحك النشاط المسلي، كنت افضل ان تدع لنا البحث عن افضل طريقة ممكنة.
قد لا افهم تماماً ما تقصد (لاني لست محترفاً)، لكن في ال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);
?>
يمكن تشغيلها هنا:
جميلة، نفس النتائج، لكن ما لاحظته في هذا الكود والكود الذي سبقه أنّك تستعمل اختصارات كبيرة في الدوار الشرطية مثل ? و :
مما يصعب قراءة الكود وفهمه لشخص آخر غير صاحبه الأصلي، مستقبلا إذا عملت في فريق ما، سيصعب على أعضاء الفريق متابعة القراءة من بعدك
يمكنك استبدال السطر الرابع والسابع بدالة 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);
?>
احسنت تقصد 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);
?>
الخوارزميتين بلغة بايثون
أسهل ما يكون
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)
مع abs انظر
يمكن صياغة طريقة اقليدس كما يلي
القاسم المشترك الأكبر : بتكرار قسمة المقسوم عليه على باقي القسمة ... قبل الوصول للصفر
مثال 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] لماذا لا يهم اسخدام الرقم الاكبر كبداية؟
لانه حين تقسم العدد الاصغر على الاكبر سيكون باقي القسمة هو الرقم نفسه. وفي الخطوة التالية ستقسم المقسوم عليه وهو في هذه الحالة الرقم الاكبر على الباقي اي الرقم الأصغر :) كما لو كنت بدأت بقسمة الاكبر