تخفیف ویژه

برنامه ماشین حساب پیشرفته در پایتون

‏  17 دقیقه
۲۱ دی ۱۳۹۹
برنامه ماشین حساب پیشرفته در پایتون

در مقاله‌ی برنامه ماشین حساب ساده در پایتون، نحوه‌ی نوشتن برنامه یک ماشین حساب ساده را به شما آموزش دادیم. اما در این قسمت از آموزش پایتون قصد داریم، برنامه نویسی یک ماشین حساب پیشرفته، که عبارات پیچیده‌ی ریاضی را از ما می‌گیرد و جواب را باز می‌گرداند، به شما آموزش دهیم. اگر از مشتاقان یادگیری برنامه نویسی پایتون هستید، تا پایان با آموزش برنامه نویسی ماشین حساب پیشرفته در پایتون همراه ما باشید.

فهرست محتوای این مقاله

ماشین حساب پیشرفته در پایتون

برنامه‌ای که ما خواهیم نوشت، یک ماشین حساب خط فرمان (Command Line) است، که به صورت خودکار، با گرفتن عبارات ساده و پیچیده‌ی ریاضی، پاسخ را به ما نمایش می‌دهد.

برای نوشتن چنین برنامه‌ای، لازم است با روش‌های خاصی کاری کنیم که رایانه بتواند عبارت نوشته شده را بخواند و عملیات خواسته شده را روی آن اعمال کند. ما در این آموزش، از Regex برای این منظور، استفاده خواهیم کرد.

Regex چیست؟

Regex یا Regular Expression به معنی عبارت باقاعده، یک زبان خاص رایانه‌ای است، که با آن می‌توان عبارات خاصی را در رشته‌ها جستجو یا جایگزین کرد. این زبان در موتورهای جستجو، اعتبارسنجی داده‌ها، جستجو و جایگزینی رشته‌ها، ویرایشگرهای متن، وب اسکرپینگ (Web Scraping) و... کاربرد دارد.

روش کار با ریجکس به این صورت است که با استفاده از الگوها و قواعدی که دارد، سینتکسی می‌نویسیم که زبان برنامه نویسی، آن بخش مورد نظر ما را در رشته‌ی مورد نظر جستجو کرده و پیدا کند.

در زیر با بعضی از قواعد Regex، که برخی از آن‌ها نیز در این آموزش استفاده خواهد شد، آشنا می‌شوید:

  • \d : در رشته، هر عددی که مطابقت داشته باشد را بر می‌گرداند.
  • \w : در رشته، هر کاراکتر از a تا Z را که منطبق باشد بر می‌گرداند (حتی اعداد).
  • \s : هر فضای خالی یا Space منطبق را باز می‌گرداند.
  • .  : تمامی کاراکترها را شامل می‌شود.
  • [abc] : هر کدام از این کاراکترها که در رشته منطبق باشد را بر می‌گرداند.
  • ^ : آنچه که با این شروع می‌شود.
  • $ : آنچه که با این پایان می‌یابد.
  • () : برای گروه بندی
  • * : از صفر تا بی‌نهایت
  • + : از یک تا بی‌نهایت
  • \ : برای نمایش کاراکتر خاص

قواعد و الگوهای Regex بیشتر از این موارد هستند. ما جهت منحرف نشدن از آموزش برنامه نویسی ماشین حساب پیشرفته در پایتون، از ذکر آن‌ها پرهیز کرده‌ایم. به شما پیشنهاد می‌کنیم جهت کسب اطلاعات بیشتر و آموزش Regex، مستندات Regex در سایت پایتون را مطالعه کنید.

Regex در پایتون

پایتون نیز همانند بسیاری دیگر از زبان‌های برنامه نویسی، از Regex پشتیبانی می‌کند. در این زبان برنامه نویسی، از طریق وارد کردن ماژول “re” در برنامه می‌توانیم Regex را در دسترس قرار داده و از امکانات آن استفاده کنیم.

مثال:

import re

#Check if the string starts with "The" and ends with "Spain":

txt = "The rain in Spain"
x = re.search("^The.*Spain$", txt)

if x:
    print("YES! We have a match!")
else:
    print("No match")

خروجی:

YES! We have a match!

برای کسب اطلاعات بیشتر در مورد این ماژول، می‌توانید مستندات ماژول re در سایت پایتون را مطالعه نمایید.

شروع کدنویسی برنامه ماشین حساب پیشرفته در پایتون با Regex

با توجه به اینکه ما خواهان استفاده از Regex هستیم، پس ابتدا کتابخانه‌ی re را به ابتدای کد خود وارد می‌کنیم. مانند زیر:

import re

تابع محاسبات در برنامه ماشین حساب پیشرفته در پایتون

ماشین حسابی که خواهیم نوشت، چهار عمل اصلی ریاضی شامل جمع، تفریق، ضرب و تقسیم را محاسبه می‌کند. به همین جهت، قبل از هر چیز، تابعی می‌نویسیم که دو عدد و یک عملگر دریافت کرده و پس از محاسبه، جواب را به عنوان خروجی چاپ کند. این تابع را به شکل زیر می‌نویسیم:

import re

# تابع محاسبه عمليات رياضي
def operation(num1, num2, operator):
    if operator == "+":
        output = num1 + num2
    elif operator == "-":
        output = num1 - num2
    elif operator == "*":
        output = num1 * num2
    elif operator == "/":
        output = num1 / num2
    return output

یافتن پرانتزها با Regex

فرض کنید، معادله‌ای شبیه زیر داریم:

(24*32)+(66/(24+641))

برای آن که رایانه این معادله را به درستی محاسبه کند، باید ابتدا عبارات داخل هر پرانتز را محاسبه کند و به حدی معادله ساده شود که در انتها چند عدد و عملگر بدون هیچ پرانتزی باقی بماند. این کار راحتی نیست، اما قابل انجام است.

در قدم اول با نوشتن یک عبارت Regex و استفاده از متد ()findall ماژول re می‌توانیم، پرانتزها و عبارات داخل آن را بیابیم. برای آن که بدانیم، عبارت ریجکسی که می‌نویسیم به درستی کار می‌کند، می‌توانیم از سایت‌های آنلاینی که در این مورد ساخته شده است، استفاده کنیم. (مانند regex101 ،regexr و...)

عبارت ریجکسی که برای انتخاب پرانتز و اعداد و عملگر داخل آن نوشته‌ایم، به این صورت است:

\([\+\-]*\d+\.*\d*[\+\-\/\*][\+\-]*\d+\.*\d*\)

در تصویر زیر، نحوه‌ی استفاده از یک سایت ریجکس آنلاین، برای نوشتن صحیح عبارت Regex را مشاهده می‌کنید.

نحوه استفاده از یک سایت آنلاین Regex

نکته: خواندن یک ریجکس، کار ساده‌ای نیست، اما نوشتن آن به راحتی امکان‌پذیر است.

حال ما ریجکس نوشته شده و نیز معادله‌ای که داشتیم را در متد findall جایگزین می‌کنیم. به این صورت:

...

str_in= '(24*32)+(66/(24+641))'

result_list = re.findall(r'\([\+\-]*\d+\.*\d*[\+\-\/\*][\+\-]*\d+\.*\d*\)', str_in)

print (result_list)

خروجی:

['(24*32)', '(24+641)']

در خروجی می‌بینیم دو عبارت داخل پرانتز، پیدا شده است. اما برای پرانتز سوم باید دو عبارت به دست آمده را محاسبه کنیم. برای این کار، از یک حلقه‌ی تکرار for استفاده می‌کنیم، که در آن اعداد و عملگر داخل هر یک از پرانتزها جدا می‌شود و سپس با فراخوانی تابعی که در ابتدا نوشتیم، جواب را به دست می‌آوریم. به این صورت:

import re

# تابع محاسبه عمليات رياضي
def operation(num1, num2, operator):
    if operator == "+":
        output = num1 + num2    
    elif operator == "-":
        output = num1 - num2     
    elif operator == "*":
        output = num1 * num2 
    elif operator == "/":
        output = num1 / num2
    return output

str_in= '(24*32)+(66/(24+641))'

result_list = re.findall(r'\([\+\-]*\d+\.*\d*[\+\-\/\*][\+\-]*\d+\.*\d*\)', str_in)

for res in result_list:
    temp = re.findall(r'\(([\+\-]*\d+\.*\d*)([\+\-\*\/])([\+\-]*\d+\.*\d*)\)', res)
    number = operation(float(temp[0][0]),float(temp[0][2]), temp[0][1])

    print (number)

خروجی:

768.0

665.0

نکته: استفاده از تابع پرینت، به جهت تست و نمایش اجرای صحیح کدهایمان است، اما در کد کامل شده نیازی به آن‌ها نیست.

جایگزینی جواب در معادله اصلی

باید جواب‌هایی که از قطعه کد بالا به دست آوردیم را در معادله‌ی اصلی جایگزین کنیم تا عبارت پرانتزدار بعدی برای محاسبه پیدا شود. برای آن که بتوانیم جواب را به طور صحیح در رابطه‌ی اصلی قرار دهیم، از دیکشنری پایتون استفاده می‌کنیم. به این صورت که عبارت پرانتز را به عنوان کلید دیکشنری و جواب حاصل را به عنوان مقدار آن ذخیره می‌کنیم.

…

dict_out ={}

for res in result_list:
    temp = re.findall(r'\(([\+\-]*\d+\.*\d*)([\+\-\*\/])([\+\-]*\d+\.*\d*)\)', res)
    number = operation(float(temp[0][0]),float(temp[0][2]), temp[0][1])
    dict_out[res] = str(number)

print (dict_out)

خروجی:

{'(24*32)': '768.0', '(24+641)': '665.0'}

برای جایگزینی، از یک حلقه‌ی تکرار for دیگر استفاده می‌کنیم و با کمک از متد replace رشته‌ها، جواب را جایگزین معادله اصلی می‌کنیم. به این صورت:

…

print (str_in) # جهت نمایش معادله‌ی اصلی

for item in dict_out:
    str_in = str_in.replace(item, dict_out[item])

print (str_in)  #  معادله‌ی اصلی بعد از جایگزینی

خروجی:

(24*32)+(66/(24+641))

768.0+(66/665.0)

حلقه‌ی While برای جستجوی دوباره

همانطور که گفتیم، بعد از جایگزینی جواب در معادله‌ی اصلی، لازم است تا زمانی که تمامی پرانتزها از معادله‌ی اصلی حذف شوند، مراحل بالا، ادامه پیدا کند. به همین جهت، حلقه‌ی تکرار while را به کار می‌بریم و تمامی قطعه کدهای نوشته شده‌ی بالا را داخل آن می‌گذاریم. به این صورت:

...

str_in= '(24*32)+(66/(24+641))'

again = True
while again:
    dict_out ={}

    # پيدا کردن پرانتزها در معادله اصلی
    result_list = re.findall(r'\([\+\-]*\d+\.*\d*[\+\-\/\*][\+\-]*\d+\.*\d*\)', str_in)

    # محاسبه و انجام عمليات رياضی روی پرانتزهای پيدا شده
    for res in result_list:
        temp = re.findall(r'\(([\+\-]*\d+\.*\d*)([\+\-\*\/])([\+\-]*\d+\.*\d*)\)', res)
        number = operation(float(temp[0][0]),float(temp[0][2]), temp[0][1])

        # وارد کردن پرانتزها و جواب به دست آمده در ديکشنری پايتون
        dict_out[res] = str(number)

    # جايگزينی جواب‌های به دست آمده در معادله‌ی اصلي
    for item in dict_out:
        str_in = str_in.replace(item, dict_out[item])

    # شرط پایان دادن به حلقه
    if  len(result_list) == 0:
        again = False

print (str_in)

همان‌طور که در کد بالا مشاهده می‌کنید، شرط پایان یافتن حلقه، صفر شدن را طول لیستی قرار داده‌ایم که لیست معادله‌های پرانتزدار در آن ذخیره می‌شود.

با این حلقه، معادله‌ی ورودی به قدری ساده می‌شود که پس از پایان این حلقه، فقط اعداد و عملگرها، باقی می‌مانند.

خروجی:

768.0+0.09924812030075188

جدا کردن اعداد و عملگرها با Regex

با آن که در این مرحله، معادله‌ی اصلی هیچ‌گونه پرانتزی ندارد، اما همان‌طور که در خروجی بالا می‌بینید، هنوز اعداد و عملگرها در قالب یک رشته هستند که باید از یکدیگر جدا شوند. این بار نیز، از ریجکس استفاده می‌کنیم و اعداد و عملگرها را در یک لیست به صورت جداگانه قرار می‌دهیم.

…
  
temp_list = re.split(r'([\+\-]*\d+\.*\d*)([\+\-\*\/])', str_in)
print (temp_list)

خروجی:

['', '768.0', '+', '0.09924812030075188']

همان‌طور که ملاحظه می‌کنید، در خروجی بالا، آیتم‌های خالی نیز وجود دارد که با یک حلقه‌ی for می‌توانیم آن‌ها را از لیست حذف کنیم. به این صورت:

...

temp_list = re.split(r'([\+\-]*\d+\.*\d*)([\+\-\*\/])', str_in)

out_cal = []
for item in temp_list :
    if len(item) != 0:
        out_cal.append(item)

print (out_cal)

خروجی:

['768.0', '+', '0.09924812030075188']

تعیین اولویت عملگرها

بر خلاف مثالی که در بالا آوردیم، ممکن است خروجی، چند عدد با چند عملگر از جمله تقسیم و ضرب باشد. از این رو، باید اولویت عملگرها برای محاسبه‌ی عملیات ریاضی را رعایت کنیم. ما با قطعه کد زیر، عملیات ضرب و تقسیم را انجام داده و جواب به دست آمده را در معادله‌ی اصلی جایگزین می‌کنیم. سپس برای آن که اعداد و عملگرهای محاسبه شده بار دیگر محاسبه نشوند و از آنجایی که حدف آن‌ها از لیست باعث محاسبات اشتباه خواهد شد، آن‌ها را با یک رشته‌ی خالی جایگزین می‌کنیم. به این طریق هم اعداد و عملگرها را حذف کردیم و هم در لیست تداخل ایجاد نمی‌کنیم. محاسبه جمع و تفریق را در مرحله بعد انجام می‌دهیم.

...

str_in = '25*63+51+86-6*92/32' # برای درک بهتر  این کد، معادله‌ی اصلی را عوض کردیم

print (out_cal)
# حلقه براي محاسبه اولويت ضرب و تقسيم در عمليات رياضي
for i in range(len(out_cal)):
    if out_cal[i] == '*' or out_cal[i] == '/':
        num = operation(float(out_cal[i-1]), float(out_cal[i+1]), out_cal[i])
        out_cal.remove(out_cal[i+1])
        out_cal.insert(i+1, str(num))
        out_cal.remove(out_cal[i])
        out_cal.insert(i, '')
        out_cal.remove(out_cal[i-1])
        out_cal.insert(i-1, '')

print (out_cal)

خروجی:

['25', '*', '63', '+', '51', '+', '86', '-', '6', '*', '92', '/', '32']

['', '', '1575.0', '+', '51', '+', '86', '-', '', '', '', '', '17.25']

همان‌طور که ملاحظه می‌کنید همانند چند مرحله‌ی قبل، خروجی ما، دارای چندین آیتم خالی است که باید مانند قبل آن‌ها را حذف کنیم. به این صورت:

...

list_out = []

# حذف آيتم هاي اضافي ليست به دست آمده
for item in out_cal:
    if len(item)!= 0:
        list_out.append(item)

خروجی:

['1575.0', '+', '51', '+', '86', '-', '17.25']

محاسبه‌ی عملیات جمع و تفریق

پس از گذر معادله‌ی ابتدایی از چند حلقه و شرط، خروجی معادله‌ی ما چند عدد و عملگر جمع و تفریق خواهد بود. حال نگرانی از جهت اولویت‌ عملگرها وجود ندارد و فقط باید عملیات محاسبه را از سمت چپ به راست انجام دهیم. لذا با قطعه کد زیر، می‌توانیم عملیات محاسبات جمع و تفریق را انجام داده و جواب را در یک متغیر قرار دهیم. به این صورت:

result = 0.0
tmp = 0

# حلقه تکرار براي انجام عميات جمع و تفريق
for i in range(len(list_out)):
    if list_out[i] == '+':
        result = result + float(list_out[i+1])
        tmp = list_out[i+1]
    elif list_out[i] == '-':
        result = result - float(list_out[i+1])
        tmp = list_out[i+1]
    else:
        if tmp == list_out[i]:
            pass
        else:
            result = result + float(list_out[i])

اکنون برای نمایش جواب نهایی به دست آمده، فقط کافی است متغیر result را چاپ کنیم.

# جواب پاياني
print (result)

خروجی:

1694.75

قدم نهایی

در اینجا، ما از یک رشته‌ی ثابت به عنوان معادله‌ی اصلی استفاده کردیم، اما در عمل باید معادله را در ورودی از کاربر بگیریم. در ضمن قصد داریم ماشین حساب به صورت مداوم کار کند و کاربر با خواست خود از آن خارج شود. لذا از یک حلقه‌ی تکرار while استفاده می‌کنیم و شرط خروج از حلقه را وارد کردن حروف قرار می‌دهیم. به این صورت:

import re

# تابع محاسبه عمليات رياضي
def operation(num1, num2, operator):
    if operator == "+":
        output = num1 + num2  
    elif operator == "-":
        output = num1 - num2     
    elif operator == "*":
        output = num1 * num2
    elif operator == "/":
        output = num1 / num2
    return output

print('Type math expression and press enter to calculate the result')
str_in = input('>')

# حلقه تکرار ادامه يافتن ماشين حساب
while True:

     # شرط پايان دادن به حلقه تکرار
     if str_in.isalpha():
         break

     .

     .

     .

     .

    str_in = input('>')

کد تکمیلی

در زیر می‌توانید کد تکمیل شده‌ی یک ماشین حساب پیشرفته در پایتون را ببینید:

# Title : Advenced Calculator
#  Created by : Abdolreza Molaei
#  Created on : 06 Januray 2021

import re

# تابع محاسبه عمليات رياضي
def operation(num1, num2, operator):
    if operator == "+":
        output = num1 + num2
    elif operator == "-":
        output = num1 - num2
    elif operator == "*":
        output = num1 * num2
    elif operator == "/":
        output = num1 / num2
    return output

#==========================================

print('Type math expression and press enter to calculate the result')
str_in = input('>')

# حلقه تکرار ادامه يافتن ماشين حساب
while True:

# شرط پايان دادن به حلقه تکرار
    if str_in.isalpha():
        break

    again = True
    # حلقه تکرار ادامه يافتن عمليات براي پيدا کردن تمام پرانتزهاي داخل رشته اصلي
    while again:
        dict_out ={}

        # پيدا کردن پرانتزها در عبارات رياضي
        result_list = re.findall(r'\([\+\-]*\d+\.*\d*[\+\-\/\*][\+\-]*\d+\.*\d*\)', str_in)

        # و انجام عمليات رياضي روي پرانتزهاي پيدا شده محاسبه
        for res in result_list:
            temp = re.findall(r'\(([\+\-]*\d+\.*\d*)([\+\-\*\/])([\+\-]*\d+\.*\d*)\)', res)
            number = operation(float(temp[0][0]),float(temp[0][2]), temp[0][1])

            # وارد کردن پرانتزها و جواب به دست آمده در ديکشنري پايتون
            dict_out[res] = str(number)

        # جايگزيني جوابهاي به دست آمده با پرانتزهاي داخل رشته ي اصلي
        for item in dict_out:
            str_in = str_in.replace(item, dict_out[item])

        if  len(result_list) == 0:
            again = False

    # جدا کردن عملگراها و اعداد
    temp_list = re.split(r'([\+\-]*\d+\.*\d*)([\+\-\*\/])', str_in)

    out_cal = []
    # حذف آيتم هاي اضافي ليست قبلي
    for item in temp_list :
        if len(item)!= 0:
            out_cal.append(item)

    # حلقه براي محاسبه اولويت ضرب و تقسيم در عمليات رياضي
    for i in range(len(out_cal)):
        if out_cal[i] == '*' or out_cal[i] == '/':
            num = operation(float(out_cal[i-1]), float(out_cal[i+1]), out_cal[i])
            out_cal.remove(out_cal[i+1])
            out_cal.insert(i+1, str(num))
            out_cal.remove(out_cal[i])
            out_cal.insert(i, '')
            out_cal.remove(out_cal[i-1])
            out_cal.insert(i-1, '')

    list_out = []
    # حذف آيتم هاي اضافي ليست به دست آمده
    for item in out_cal:
        if len(item)!= 0:
            list_out.append(item)

    result = 0.0
    tmp = 0
    # حلقه تکرار براي انجام عميات جمع و تفريق
    for i in range(len(list_out)):
        if list_out[i] == '+':
            result = result + float(list_out[i+1])
            tmp = list_out[i+1]
        elif list_out[i] == '-':
            result = result - float(list_out[i+1])
            tmp = list_out[i+1]
        else:
            if tmp == list_out[i]:
                pass
            else:
                result = result + float(list_out[i])

    # جواب پاياني
    print (result)




str_in = input('>')

در نهایت ماشین حسابی خواهیم داشت که از چهار عملیات ریاضی پشتیبانی می‌کند. اما می‌توانیم با کمی کدنویسی بیشتر و اضافه کردن ویژگی‌های دیگری نظیر محاسبه‌ی توان، رادیکال، عدد پی و...، محاسبات پیچیده‌تری با این ماشین حساب انجام دهیم. ما به شما پیشنهاد می‌کنیم، جهت تمرین و یادگیری بهتر، کد نوشته شده‌ی این آموزش را بهبود ببخشید و کد ماشین حسابی بنویسید که توانایی بیشتری در حل مسائل مختلف داشته باشد.

 

جمع بندی:

در این آموزش، در کنار هم کد یک ماشین حساب پیشرفته در پایتون را نوشتیم. نقطه قوت ما برای نوشتن این ماشین حساب، استفاده از Regex بود، به همین دلیل شما را با ریجکس و برخی از قواعد و الگوهای آن آشنا کردیم. همچنین ماژول re را به شما معرفی کردیم که در پایتون، با وارد کردن آن به برنامه، امکانات ریجکس را در اختیار ما قرار می‌دهد. در ادامه نیز مرحله به مرحله، جلو رفتیم و کدهای خود را کامل کردیم تا در پایان، با یک کد کامل شده ماشین حساب پیشرفته، آموزش خود را به پایان رساندیم. این یکی از نقاط قوت زبان برنامه نویسی پایتون است که با این تعداد خط کد، توانستیم برنامه‌ی خود را بنویسیم. در صورتی که برای نوشتن چنین برنامه‌ای در زبان‌های دیگر، باید چندین برابر این کد می‌نوشتیم. امیدواریم این آموزش، دلیلی برای یادگیری بیشتر شما در حوزه‌ی برنامه نویسی پایتون شود.

خوشحال می‌شویم نظرات و پیشنهادات خود را در رابطه با برنامه نویسی ماشین حساب پیشرفته در پایتون با ما در میان بگذارید.

اگر به یادگیری بیشتر در زمینه‌ی برنامه نویسی پایتون علاقه داری، یادگیری زبان پایتون بسیار ساده است. و با شرکت در دوره‌ی متخصص پایتون توسعه وب در آینده می‌تونی اپلیکیشن موبایل و دسکتاپ بسازی و وارد حوزه‌ی هوش مصنوعی هم شوی.

چه امتیازی به این مقاله می دید؟
نویسنده عبدالرضا مولایی
هیچوقت برای یادگیری دیر نیست؛ همیشه چیزهای جدید برای آموختن وجود دارد.. این دو جمله، همیشه آویزه‌ی گوش منه، چون باعث میشه از یادگیری چیزهای تازه هراسی نداشته باشم.
ارسال دیدگاه
خوشحال میشیم دیدگاه و یا تجربیات خودتون رو با ما در میون بذارید :

 

نظرات کاربران

اولین دیدگاه این پست رو تو بنویس !

ما در سون لرن با محدودسازی دسترسی آزاد به اینترنت مخالفیم     اطلاعات بیشتر