خوارزمی چگونه معادلات را به الگوریتم تبدیل کرد؟ پیادهسازی روش او با Python
فرض کنید با معادله x² + 10x = 39 روبهرو شدهایم. امروز احتمالاً آن را به یک کتابخانه ریاضی میدهیم یا از فرمول عمومی استفاده میکنیم. اما حالا تمام نمادها را حذف کنید: نه x داریم، نه علامت مساوی، نه توان دو و نه حتی عدد منفی. آیا هنوز میتوان روشی نوشت که هر شخصی با دنبالکردن دقیق مراحل آن، به پاسخ یکسان برسد؟ محمد بن موسی خوارزمی بیش از هزار سال پیش چنین کاری انجام داد — و این دقیقاً همان نقطهای است که تاریخ ریاضیات به تفکر الگوریتمی نزدیک میشود.
#
از یک معادله تا یک روش قابل اجرا
اهمیت کار خوارزمی در جای دیگری است: این دانشمند ایرانی حل معادلات خطی و درجهدو را بهشکلی منظم، آموزشی و قاعدهمند عرضه کرد. مسئله ابتدا به یکی از چند فرم مشخص تبدیل میشد و سپس دستور حل متناسب با همان فرم اجرا میشد. با زبان برنامهنویسی امروز، ساختار کلی کار او چنین است:
روش خوارزمی به زبان مهندسی نرمافزار
صورت مسئله
استانداردسازی
تشخیص نوع
انتخاب روش
اجرای مرحلهبهمرحله
پاسخ + توجیه هندسی
دو میراث زبانی متفاوت: واژه algorithm از نام لاتینشده خوارزمی (Algoritmi) در ترجمه آثار حسابیاش آمد، اما واژه algebra از al-jabr در عنوان کتاب مشهور او گرفته شده است. نام یک دانشمند به «الگوریتم» رسید و بخشی از عنوان کتابش نام یک شاخه بزرگ ریاضیات شد.
#
مسئلهای بدون x و علامت مساوی چگونه نوشته میشد؟
کتاب خوارزمی با نام عربی الکتاب المختصر فی حساب الجبر والمقابله جبر را با نثر توضیح میداد و نمادگذاری آشنای امروز هنوز شکل نگرفته بود. آنچه ما اینگونه مینویسیم:
MODERN
x2+10x=39
در بیان آن دوره با سه نوع کمیت توصیف میشد:
x²
مربع یا مال
آنچه امروز توان دوم مجهول مینامیم
x
ریشه یا شیء
خود مجهول
c
عدد
مقدار ثابت معادله
صورت مسئله در ترجمه سده نوزدهمی اثر تقریباً چنین معنایی دارد: «مربعی همراه با ده ریشه آن برابر با سیونه است؛ آن مربع کدام است؟» راهحل نمیتوانست فقط یک فرمول فشرده باشد؛ باید بهصورت دستورهایی نوشته میشد که بهترتیب اجرا شوند:
1
تعداد ریشهها را نصف کن
2
عدد حاصل را در خودش ضرب کن
3
نتیجه را به عدد ثابت اضافه کن
4
از مجموع ریشه بگیر
5
نصف ریشهها را از آن کم کن
این متن، از دید یک برنامهنویس، تقریباً آماده تبدیلشدن به تابع است.
#
شش نوع معادله؛ چیزی شبیه Type System برای مسئلهها
در دستگاه خوارزمی، ضرایب و جوابها بهصورت مقدارهای مثبت در نظر گرفته میشدند. به همین دلیل، عبارتهایی که امروز با عدد منفی و یک فرم عمومی مینویسیم، آن زمان به شش نوع جدا تقسیم میشدند:
۱
مربعها برابر ریشهها
ax² = bx
۲
مربعها برابر عدد
ax² = c
۳
ریشهها برابر عدد
bx = c
۴
مربعها و ریشهها برابر عدد
ax² + bx = c
۵
مربعها و عدد برابر ریشهها
ax² + c = bx
۶
ریشهها و عدد برابر مربعها
bx + c = ax²
در جبر مدرن میتوان همه را به فرم ax² + bx + c = 0 برد، اما این نمایش به پذیرش ضریبهای منفی، صفر و نمادگذاری جبری وابسته است. وقتی تمام کمیتها مثبتاند، جابهجایی یک جمله از یک طرف به طرف دیگر آن را به «منفی همان جمله» تبدیل نمیکند؛ بلکه نوع مسئله را تغییر میدهد. از دید برنامهنویسی، فهرست ششتایی خوارزمی شبیه تعریف حالتهای مجاز ورودی است: ابتدا Pattern را تشخیص میدهیم، سپس Branch مناسب را اجرا میکنیم.
#
الجبر و المقابله؛ مرحله Normalization پیش از حل
پیش از اجرای دستور حل، معادله باید پاکسازی و به یکی از فرمهای استاندارد تبدیل میشد. دو عمل مشهور در عنوان کتاب همینجا ظاهر میشوند:
al-jabr — تکمیل
جملهای کمشده با افزودن مقدار مناسب به هر دو طرف حذف میشود.
x² = 40x − 4x² → 5x² = 40x
al-muqabala — مقابله
کمیتهای همنوع در دو طرف کاهش مییابند.
50 + 3x + x² = 29 + 10x → 21 + x² = 7x
این مرحله به کاری شبیه است که یک Compiler یا Query Optimizer انجام میدهد. ورودیها ممکن است ظاهرهای مختلفی داشته باشند، اما پیش از پردازش اصلی به یک نمایش استاندارد تبدیل میشوند:
Normalization
نمایشهای متفاوت مسئله
الجبر / المقابله
یکی از شش فرم استاندارد
روش حل مشخص
خوارزمی نام normalize() روی این فرایند نگذاشت، اما جداسازی «آمادهسازی مسئله» از «اجرای روش حل» یکی از آشناترین الگوهای برنامهنویسی امروز است.
#
مثال اصلی: یک مربع و ده ریشه برابر ۳۹
به معادله آغاز مقاله برگردیم. این معادله از نوع چهارم است: مربعها و ریشهها برابر یک عدد. روش خوارزمی را با زبان ریاضی امروز قدمبهقدم اجرا میکنیم:
STEP 1
نصف ضریب ریشهها
102=5
STEP 2
مربع آن
52=25
STEP 3
افزودن به عدد
39+25=64
STEP 4
ریشه مجموع
√64=8
RESULT
x=8−5=3
برای اطمینان، پاسخ را در معادله قرار میدهیم: 3² + 10×3 = 9 + 30 = 39 ✓
نکته مهم این است که این دنباله فقط برای عددهای ۱۰ و ۳۹ نوشته نشده. اگر معادله شکل عمومی x² + bx = c را داشته باشد، همان مراحل با ورودیهای b و c اجرا میشوند:
GENERAL
x=√c + (b/2)²1−b2
فرمول آخر خلاصه مراحل است؛ اما الگوریتم، خود دنباله عملیات و شرطهای اجرای آن است.
#
چرا باید یک مربع را «کامل» کنیم؟
اگر فقط عددها را دنبال کنیم، افزودن ۲۵ ممکن است یک ترفند حفظی به نظر برسد. توجیه هندسی نشان میدهد این عدد از کجا آمده است. عبارت x² را مساحت مربعی با ضلع x در نظر بگیرید. عبارت 10x را به دو مستطیل 5x تقسیم میکنیم و آنها را کنار دو ضلع مربع قرار میدهیم:
x² + 10x + 25 = (x+5)² — گوشه گمشده دقیقاً همان ۲۵ است
geometry.txt
مساحت موجود = x² + 10x = 39
مساحت گوشه گمشده = 5² = 25
مساحت مربع کامل = 39 + 25 = 64
ضلع مربع کامل = √64 = 8
ضلع اولیه = 8 − 5 = 3
این توجیه هندسی فقط نتیجه را اعلام نمیکند؛ توضیح میدهد چرا مراحل درستاند. در زبان مهندسی نرمافزار، میتوان آن را چیزی نزدیک به استدلال درستی الگوریتم دانست: اگر مساحتها برابر بمانند و قطعه افزودهشده دقیقاً گوشه ناقص را پر کند، پاسخ نهایی باید معادله اولیه را ارضا کند.
#
دستورهای کلامی را به شبهکد تبدیل کنیم
ALGORITHM solve_square_and_roots(b, c)
INPUT:
b = تعداد ریشهها
c = عدد طرف دیگر
PRECONDITION:
b > 0 و c > 0 و equation is x² + bx = c
STEPS:
half ← b / 2
missing_square ← half × half
completed_area ← c + missing_square
completed_side ← square_root(completed_area)
root ← completed_side − half
OUTPUT:
root
این شبهکد ویژگیهای اصلی یک الگوریتم را دارد: ورودیهایش مشخصاند، پیششرط دارد، هر مرحله روشن و قابلاجراست، پس از تعداد محدودی مرحله متوقف میشود و خروجی تعریفشدهای تولید میکند. فاصله میان متن خوارزمی و کد Python حالا بسیار کم شده است.
این Trace پلی میان سه نمایش مختلف است — تغییر زبان، منطق مرحله را عوض نکرده است:
۱
دستور تاریخی
«نصف ریشهها را بگیر»
۲
ریاضی امروز
b / 2
۳
کد
half = b / 2
#
اگر ضریب مربع یک نباشد چه میشود؟
تا اینجا ضریب x² برابر یک بود. حالا معادله عمومیتری در همان نوع چهارم در نظر بگیرید: ax² + bx = c. پیش از تکمیل مربع، باید ضریب مربع را به یک تبدیل کنیم؛ تمام معادله را بر a تقسیم میکنیم و سپس همان الگوریتم قبلی را روی ضرایب نرمالشده اجرا میکنیم:
print(solve_ax2_plus_bx_eq_c(2, 20, 78)) # 2x² + 20x = 78 → پس از تقسیم بر ۲ همان مثال قبل
3.0
اینجا دو مرحله مستقل دیده میشود: Normalization (ضریب مربع را به یک تبدیل کن) و Solution (مربع را کامل کن). این جداسازی در برنامهنویسی نیز ارزشمند است؛ هرچه تعداد شکلهای ورودی بیشتر باشد، تبدیل همه آنها به یک نمایش داخلی استاندارد، کد حلکننده را سادهتر میکند.
#
یک حلکننده برای هر شش نوع تاریخی بسازیم
اکنون میتوانیم از یک مثال فراتر برویم و شش نوع معادله را در یک برنامه پیادهسازی کنیم. این برنامه قرار نیست جای یک کتابخانه جبر نمادین را بگیرد؛ هدف آن مدلکردن ساختار دستگاه خوارزمی با ابزارهای امروز است:
برنامه از چند ایده آشنای علوم کامپیوتر استفاده میکند: Enum فقط شش حالت مجاز را تعریف میکند، جدول REQUIRED_COEFFICIENTS قرارداد ورودی هر حالت را نگه میدارد، تابع ابتدا ورودی را اعتبارسنجی میکند، هر Branch یک قاعده مشخص را اجرا میکند و خروجی فقط شامل ریشههای حقیقی مثبت است تا با چارچوب تاریخی سازگار بماند.
این کد ترجمه حرفبهحرف نسخه خطی نیست. Enum، عدد اعشاری، sqrt() و نمادها ابزارهای جدیدند. چیزی که بازآفرینی شده، ترتیب تصمیمگیری و روش محاسبه است.
#
چرا نوع پنجم میتواند دو پاسخ داشته باشد؟
معادله x² + 21 = 10x یکی دیگر از مثالهای مشهور این سنت است. نصف ریشهها ۵ است و مربع آن ۲۵ میشود. این بار ۲۱ را از ۲۵ کم میکنیم: 25 − 21 = 4. ریشه ۴ برابر ۲ است. بنابراین x = 5 − 2 = 3 — اما پاسخ دیگری نیز وجود دارد: x = 5 + 2 = 7. هر دو مقدار معادله را درست میکنند:
verify.txt
3² + 21 = 30 = 10(3) ✓
7² + 21 = 70 = 10(7) ✓
به همین دلیل تابع solve_historical() بهجای یک عدد، همیشه list[float] برمیگرداند. این تصمیم طراحی، شکل خروجی را برای حالتهای مختلف یکسان نگه میدارد:
output_contract.txt
[] → پاسخ حقیقی مثبت وجود ندارد
[3.0] → یک پاسخ مثبت
[3.0, 7.0]→ دو پاسخ مثبت
انتخاب ساختمان داده مناسب، بخشی از تبدیل روش ریاضی به نرمافزار است؛ حتی اگر خود محاسبه فقط چند خط باشد.
#
پاسخ منفی کجا رفت؟
معادله اصلی را با فرم مدرن بنویسیم: x² + 10x − 39 = 0. فرمول درجهدو دو پاسخ میدهد: x = 3 و x = −13. اما حل تاریخی فقط ۳ را برمیگرداند. این اتفاق Bug برنامه نیست؛ بخشی از مدلی است که انتخاب کردهایم. در چارچوب خوارزمی، «ریشه»، «مربع» و «عدد» مقدارهای مثبت بودند و جواب منفی در فهرست پاسخهای قابلقبول قرار نمیگرفت.
همین موضوع درباره ax² = bx نیز دیده میشود. جبر مدرن میگوید x(ax−b) = 0 پس x = 0 یا x = b/a. حلکننده تاریخی ما فقط b/a را نگه میدارد، زیرا صفر نیز در آن صورتبندی نقش پاسخ امروزی را نداشت.
درس برنامهنویسی: درستبودن خروجی همیشه نسبت به Specification سنجیده میشود. اگر مشخصات سیستم «ریشههای حقیقی مثبت در شش فرم تاریخی» باشد، حذف صفر و منفی رفتار مورد انتظار است. اگر هدف «تمام ریشههای مختلط یک چندجملهای» باشد، همین برنامه ناقص است.
مدل تاریخی مقاله
فرم ورودی: یکی از شش نوع | ضرایب منفی و پاسخ صفر/منفی/مختلط: ندارد | توضیح: قاعده کلامی و هندسی
حلکننده مدرن
فرم ورودی: ax² + bx + c = 0 | ضرایب منفی و پاسخ صفر/منفی/مختلط: پشتیبانی میشود | توضیح: فرمول نمادین عمومی
نسخه مدرن دامنه بزرگتری دارد، اما نسخه تاریخی چیز دیگری به ما یاد میدهد: چگونه پیش از داشتن نمادگذاری فشرده، مسئله را به رویهای قابل آموزش و تکرار تبدیل کنیم.
#
الگوریتم با فرمول چه تفاوتی دارد؟
فرمول یک رابطه فشرده است، اما یک برنامه واقعی باید به پرسشهای بیشتری پاسخ دهد: ورودی از کجا میآید؟ آیا ضرایب معتبرند؟ معادله متعلق به کدام نوع است؟ اگر عبارت زیر رادیکال منفی شد چه کنیم؟ آیا یک پاسخ داریم یا دو پاسخ؟ آیا صفر و منفی مجازند؟ الگوریتم فقط «رابطه ریاضی» نیست؛ قرارداد اجرای رابطه است.
برای مثال، در نوع پنجم باید پیش از sqrt() این شرط را بررسی کنیم:
PRECONDITION — TYPE 5
b2a2−ca≥0
اگر شرط برقرار نباشد، در دامنه اعداد حقیقی پاسخی نداریم. همین شرط در کد به Branch تبدیل میشود:
branch.py
remaining_square = half**2 - normalized_c
if remaining_square < 0:
return [] # پاسخ حقیقی مثبت وجود ندارد
اینجا ریاضیات مشخص میکند چه وضعیتی ممکن است و برنامهنویسی مشخص میکند نرمافزار آن وضعیت را چگونه نمایش دهد.
#
از کجا بفهمیم کد واقعاً درست کار میکند؟
دیدن خروجی 3.0 کافی نیست. میتوان دو نوع آزمون نوشت: آزمون مثالهای شناختهشده و بررسی جایگذاری پاسخ در معادله:
در محاسبات اعشاری عمومی بهتر است بهجای برابری مستقیم از math.isclose() استفاده کنیم؛ زیرا نمایش دودویی بعضی عددهای اعشاری دقیق نیست. اگر هدف محاسبه نمادین یا کسرهای دقیق باشد، ابزارهایی مانند fractions.Fraction یا decimal.Decimal انتخاب مناسبتری هستند. برنامه این مقاله آموزشی است؛ برای نرمافزار Production باید دامنه عددی، خطای گردکردن و رفتار در مرزها دقیقتر تعریف شود.
#
کدام بخش روش خوارزمی به برنامهنویسی امروز شبیه است؟
شباهت اصلی در ظاهر فرمولها نیست؛ در سازماندهی حل مسئله است:
01
Classification
پیش از حل، مسئله به یکی از شش نوع تعلق میگیرد؛ مثل Parser یا لایه Validation که ابتدا شکل ورودی را تشخیص میدهد
02
Normalization
عبارتهای اضافی حذف و ضریب مربع به یک میرسد؛ مثل تبدیل ورودیها به فرم Canonical
03
Dispatch
نوع معادله تعیین میکند کدام قاعده اجرا شود؛ معادل if، match یا جدول Dispatch
04
State قابل مشاهده
هر مرحله یک مقدار میانی تولید میکند؛ همانها که برای Debugging و Trace مفیدند
05
Preconditions
هر روش فقط روی فرم خاصی معتبر است؛ اجرای فرمول درست روی ورودی اشتباه، پاسخ اشتباه میسازد
06
Correctness Argument
استدلال هندسی توضیح میدهد چرا مراحل درستاند؛ الگوریتم خوب دلیلی برای اعتماد به خروجی دارد
07
Reuse
قاعده برای یک عدد خاص نیست؛ هر معادلهای با پیششرطها از همان مراحل عبور میکند
#
آیا میتوان خوارزمی را «مخترع الگوریتم» نامید؟
این عبارت جذاب است، اما از نظر تاریخی بیشازحد سادهسازی میکند. الگوریتم بهعنوان دستورالعمل مرحلهای بسیار قدیمیتر از خوارزمی است؛ روش اقلیدس برای محاسبه بزرگترین مقسومعلیه مشترک نمونهای پیشین است. نسبت دقیقتر این است:
1
خوارزمی روشهای حساب و جبر را نظاممند و مرحلهای عرضه کرد
2
آثار حسابی او در انتقال دستگاه عددنویسی هندی–عربی به اروپا اثرگذار بود
3
صورت لاتینی نام او به واژهای برای روشهای محاسبه تبدیل شد
4
معنای آن واژه گسترش یافت و به algorithm امروزی رسید
بنابراین دقیقتر است بگوییم نام و آثار او در تاریخ روشهای محاسباتی و شکلگیری واژه Algorithm جایگاهی بنیادی دارند — نه اینکه «مفهوم هر الگوریتمی را از صفر اختراع کرد». همین دقت درباره جبر نیز لازم است: خوارزمی نخستین انسان تاریخ نبود که معادله درجهدو حل کرد؛ اهمیت کتاب او در صورتبندی منظم، عمومی و آموزشی علم حل معادلات است. این اصلاح چیزی از جذابیت داستان کم نمیکند؛ نشان میدهد پیشرفت علم معمولاً نتیجه یک لحظه جادویی و یک مخترع تنها نیست؛ دانش موجود جمعآوری، منظم و سپس در نسلهای بعد گسترش داده میشود.
#
اگر خوارزمی امروز این تابع را میدید، چه چیزهایی برایش ناآشنا بود؟
تقریباً تمام Syntax برنامه — موارد زیر همگی متعلق به جهان جدیدند: حروف بهعنوان نام متغیرها، علامت مساوی در نقش انتساب، تواننویسی **، تابع کتابخانهای sqrt()، عدد صفر و ضریب منفی، نوع float، و مفهوم تابع، ورودی، خروجی و Exception. اما اگر هر خط را به دستور کلامی برگردانیم، هسته محاسبه آشنا میشود:
translation.txt
b / 2 → نصف تعداد ریشهها
(b / 2) ** 2 → ضرب آن مقدار در خودش
c + (b / 2) ** 2 → افزودن مربع به عدد
sqrt(...) → گرفتن ریشه مجموع
... - b / 2 → کمکردن نصف ریشهها
ما یک برنامه تاریخی کشف نکردهایم؛ یک روش تاریخی را در محیط اجرایی امروز مدل کردهایم. این تفاوت، معنای دقیق «پیادهسازی روش خوارزمی با Python» است.
#
نسخه نهایی جریان حل
تمام مقاله را میتوان در یک Pipeline خلاصه کرد:
Pipeline کامل
مسئله مربع و ده ریشه برابر ۳۹
نمایش مدرن x² + 10x = 39
تشخیص نوع نوع ۴
نرمالسازی ضریب = ۱
الگوریتم نصف ← مربع ← جمع ← ریشه ← تفریق
نتیجه + بررسی x = 3 ✓
ریاضی مسئله را تعریف میکند؛ الگوریتم ترتیب حل را مشخص میکند؛ و برنامه آن ترتیب را به عملیاتی تبدیل میکند که ماشین میتواند اجرا کند.
#
خوارزمی کد ننوشت؛ اما مسئله را قابل اجرا کرد
میراث خوارزمی فقط این نیست که یک معادله خاص را حل کرد. نکته ماندگارتر، تبدیل انبوهی از مسئلههای ظاهراً متفاوت به چند فرم شناختهشده و چند روش تکرارپذیر بود. در مثال x² + 10x = 39، او از خواننده نمیخواهد پاسخ ۳ را حدس بزند؛ یک مسیر میدهد: ضریب ریشه را نصف کن، مربعش کن، به ۳۹ بیفزا، ریشه بگیر و مقدار نصفشده را کم کن. هرکس مراحل را درست انجام دهد، مستقل از حدس و شهود شخصی، به همان پاسخ میرسد.
ما امروز همین ایده را داخل یک تابع قرار دادیم، برای ورودی قرارداد نوشتیم، حالتها را با Enum مشخص کردیم، نتیجه را در یک List برگرداندیم و با Test بررسی کردیم. ابزارها جدیدند، اما هسته تفکر آشناست: مسئله را به دستورهای روشن و قابل تکرار تبدیل کن.
خوارزمی کامپیوتر نداشت؛ اما روشی نوشت که میتوانست اجرا شود.
takeaway.txt
خوارزمی کد ننوشت؛
مسئله را به گامهای تکرارپذیر شکست.
تاریخ انتشار: ۱۴۰۵/۰۶/۱۳ · آخرین بهروزرسانی: ۱۴۰۵/۰۶/۱۳
author.dat
ALI ZAMANI
Full Stack Developer
توسعهدهنده فولاستک تخصصدار در وب، هوش مصنوعی و بازیسازی.