کامپیوتر چگونه مسئله را حل میکند؟ ورودی، پردازش و قرارداد خروجی
در این درس ۸ بخش
این دوره برای شروع مبانی کامپیوتر و تفکر الگوریتمی در مهندسی نرمافزار و علوم کامپیوتر است. پیش از کدنویسی باید بفهمیم چه داریم، چه میخواهیم و کدام فرضها معتبرند. شبهکدها مستقل از زباناند؛ JavaScript پلی برای اجرای ایدههاست. پیشنیاز، کار معمول با فایل و پوشه و محاسبهٔ ساده است. ابتدا مثالها را روی کاغذ حل کنید. در پایان این درس باید برای محاسبهای کوچک، قرارداد بنویسید و پاسخ را پیش از اجرا بررسی کنید.
داده چگونه به اجرا میرسد؟
کامپیوتر را در این سطح، سامانهای برای دریافت، نگهداری، پردازش و ارائهٔ داده میبینیم. صفحهکلید، فایل یا پیام شبکه میتوانند ورودی بدهند؛ خروجی ممکن است متن، تصویر، فایل یا پاسخ شبکه باشد. پردازنده یا CPU دستورهای ماشین را اجرا میکند. RAM فضای کار برنامهٔ در حال اجراست و با حافظهٔ ماندگار، مانند SSD، یک نقش ندارد. ذخیرهکردن فایل باعث نمیشود تمام مقدارهای موقت برنامه هم خودکار در آن بمانند. این مدل ساده برای فهم اجرای مثالهاست؛ جزئیات معماری پردازنده و حافظه را در درس تخصصی معماری کامپیوتر میخوانیم.
سیستمعامل منابعی مانند فایل، حافظه و اجرای فرایندها را مدیریت میکند. ویرایشگر به نوشتن متن برنامه کمک میکند و محیط اجرا، امکانات لازم برای اجرای آن زبان را فراهم میسازد. فایل JavaScript را معمولاً خود پردازنده مستقیماً بهعنوان متن نمیخواند؛ موتور زبان آن را تحلیل و اجرا میکند و ممکن است در جریان کار بخشی را به کد ماشین تبدیل کند. Node.js از موتور V8 استفاده میکند و امکاناتی مانند دسترسی برنامه به فایل و شبکه میدهد. تقسیم سادهٔ «همهٔ زبانها فقط کامپایلری یا فقط تفسیریاند» توصیف کاملی از سامانههای امروزی نیست.
الگوریتم، روش دقیق حل یک دسته مسئله است؛ برنامه، بیان قابل اجرا از آن روش در یک زبان و محیط مشخص است. دو برنامه میتوانند یک الگوریتم را به زبانهای متفاوت اجرا کنند. از طرف دیگر، اجرای بدون خطای یک برنامه ثابت نمیکند مسئله را درست فهمیدهایم. ممکن است واحد پول اشتباه باشد یا حالت سفارش خالی را فراموش کرده باشیم. در #variable یک نام و مقدار میبینید، اما تصمیم دربارهٔ معنای آن مقدار از خود مسئله میآید. ماشین نمیتواند از نام «قیمت» تشخیص دهد که منظور ریال بوده یا تومان.
پیش از راهحل، قرارداد بنویسید
فرض کنید مرکز تکثیر میخواهد هزینهٔ چاپ را حساب کند. در مدل آموزشی ما، همهٔ صفحهها قیمت واحد یکسان دارند و تخفیف، صحافی و ارسال وجود ندارد. تعداد صفحه باید عدد صحیح نامنفی باشد؛ قیمت واحد نیز عدد صحیح تومان است. برای قابل کنترل ماندن تمرین، هرکدام را حداکثر صد هزار میگیریم. این سقف و سیاست قیمت، فرض همین مثالاند و تعرفهٔ واقعی چاپخانه نیستند. خروجی، هزینهٔ کل به تومان است. ورودی نامعتبر باید رد شود؛ تبدیل متن نامفهوم به صفر، گزارش ظاهراً موفق اما غلط میسازد.
ورودی: pages و unitToman؛ عدد صحیح در بازهٔ 0..100000
خروجی: totalToman؛ عدد صحیح نامنفی
پیششرط: همهٔ صفحهها یک قیمت دارند؛ واحد پول تومان است
رابطهٔ خروجی: totalToman = pages × unitToman
ورودی نامعتبر: اعلام خطا؛ بدون محاسبهٔ هزینهپیششرط یعنی فرضی که پیش از اجرای بخش محاسبه باید برقرار باشد؛ رابطهٔ خروجی میگوید جواب چگونه به ورودی مربوط است. در اینجا صفر صفحه مجاز است و هزینهٔ آن صفر میشود. این تصمیم باید پیش از نوشتن شرطها معلوم باشد. اگر بعداً صحافی اضافه شود، قرارداد و آزمونهای مربوط به آن هم تغییر میکنند. مشخصات مسئله را کنار نمونهٔ داده بنویسید تا همگروهی شما مجبور نباشد قانونها را از چند عدد حدس بزند. #validation-contracts نام همین اهمیتِ مرز روشن برای پذیرش داده را یادآوری میکند.
الگوریتم CopyCost(pages, unitToman)
معتبر بودن نوع و محدودهٔ هر دو ورودی را بررسی کن
اگر معتبر نیستند، خطا بده و پایان بده
totalToman ← pages × unitToman
totalToman را برگردانبرای صد صفحه با قیمت هزار و پانصد تومان، ابتدا اعتبار هر دو ورودی را میسنجیم؛ سپس صد را در هزار و پانصد ضرب میکنیم و صد و پنجاه هزار تومان برمیگردانیم. این ردگیری کوتاه، اجرای دستی الگوریتم است. آزمون صفر صفحه نیز لازم است، چون نمونهٔ معمولی نشان نمیدهد آیا برنامه صفر را بیجهت خطا گرفته است یا نه. ورودی منفی، سفارش معتبر نیست و نباید با یک هزینهٔ منفی وارد گزارش شود.
مثال حلشدهٔ اول: محاسبه و بررسی هزینه
import assert from 'node:assert/strict';
function copyCost(pages, unitToman) {
if (!Number.isInteger(pages) || pages < 0 || pages > 100000) {
throw new RangeError('pages: 0..100000');
}
if (!Number.isInteger(unitToman) || unitToman < 0 || unitToman > 100000) {
throw new RangeError('unitToman: 0..100000');
}
return pages * unitToman;
}
assert.equal(copyCost(100, 1500), 150000);
assert.equal(copyCost(0, 1500), 0);
assert.throws(() => copyCost(-1, 1500), RangeError);
assert.throws(() => copyCost('100', 1500), RangeError);
console.log(copyCost(100, 1500));بلوک را در فایل 01-copy-cost.mjs ذخیره و با Node.js اجرا کنید؛ خروجی 150000 است. دستورهای assert نتیجهٔ مورد انتظار را بررسی میکنند و در صورت اختلاف، اجرا را با خطا متوقف میکنند. چهار بررسی میبینید: سفارش عادی، سفارش خالی، تعداد منفی و متن بهجای عدد. هدف این نیست که همهٔ ورودیهای دنیا را در چند خط بررسی کردهایم؛ نشان میدهیم قرارداد چگونه به آزمون تبدیل میشود. در محدودهٔ تعیینشده، حاصل ضرب حداکثر ده میلیارد است و در محدودهٔ عدد صحیح امن JavaScript میماند.
مثال حلشدهٔ دوم: محیط یک مستطیل
اکنون مسئله را عوض میکنیم: برای حصار دور یک مستطیل، طول و عرض بر حسب متر داده شدهاند. هر ضلع باید عدد محدود، بزرگتر از صفر و حداکثر هزار متر باشد؛ اندازهٔ اعشاری هم مجاز است. خروجی، دو برابر مجموع طول و عرض است. طول دوازده و عرض هشت به محیط چهل متر میرسد. تفاوت مهم با چاپ، فقط فرمول نیست: تعداد صفحه باید صحیح باشد، ولی طول میتواند اعشاری باشد. بنابراین اعتبارسنجی را نباید بدون توجه به معنا از مسئلهٔ قبلی کپی کرد.
الگوریتم Perimeter(lengthMetres, widthMetres)
بررسی کن هر ضلع عدد محدود و در بازهٔ (0, 1000] باشد
perimeterMetres ← 2 × (lengthMetres + widthMetres)
perimeterMetres را برگردانimport assert from 'node:assert/strict';
function perimeter(lengthMetres, widthMetres) {
for (const value of [lengthMetres, widthMetres]) {
if (!Number.isFinite(value) || value <= 0 || value > 1000) {
throw new RangeError('Each side must be in (0, 1000] metres');
}
}
return 2 * (lengthMetres + widthMetres);
}
assert.equal(perimeter(12, 8), 40);
assert.equal(perimeter(2.5, 1.5), 8);
assert.throws(() => perimeter(12, 0), RangeError);
assert.throws(() => perimeter(Infinity, 8), RangeError);
console.log(perimeter(12, 8));فایل مستقل 01-perimeter.mjs خروجی 40 میدهد. مقدار Infinity نیز از نوع عدد است، اما طول قابل قبول برای این قرارداد نیست؛ به همین دلیل محدودبودن آن را بررسی میکنیم. چاپ یک عدد به کنسول با برگرداندن جواب فرق دارد: return مقدار را به فراخواننده میدهد، و console.log آن را برای مشاهده نمایش میدهد. برنامهٔ بعدی میتواند محیط را از تابع بگیرد و بدون چاپ، در محاسبهٔ هزینه استفاده کند. مدخل #return این تفاوت را در نحو زبان توضیح میدهد.
خطا را با مرحلهٔ وقوعش پیدا کنید
سه مشکل را از هم تشخیص دهید: خطای نگارشی ممکن است مانع فهم کد شود؛ خطای اجرا ممکن است هنگام دریافت داده یا فراخوانی رخ دهد؛ خطای منطقی ممکن است عددی تولید کند که با قرارداد سازگار نیست. اگر بهجای محیط، مساحت را حساب کنید، برنامه احتمالاً اجرا میشود ولی مسئلهٔ حصار را حل نکرده است. برای عیبیابی، ورودی، انتظار، خروجی واقعی و نخستین محل اختلاف را ثبت کنید. تغییر همزمان چند قسمت، تشخیص علت را دشوار میکند. ابتدا یک نمونهٔ کوچک و قابل محاسبه انتخاب کنید.
برای اجرا چه کاری انجام دهید؟
اگر Node.js روی رایانه ندارید، راهنمای محیط ویندوز درسآوند را بخوانید: https://www.darsavand.com/courses/nodejs-windows-backend-start . یک پوشهٔ مستقل بسازید، هر مثال را با نام گفتهشده و پسوند mjs ذخیره کنید و ترمینال را در همان پوشه باز کنید. برای این مثالها نصب بسته با npm لازم نیست. نحو اولیهٔ زبان را در https://www.darsavand.com/courses/javascript-from-zero-to-project پیدا میکنید؛ این دوره بر طراحی روش حل تمرکز دارد و آن درسها را از ابتدا تکرار نمیکند.
node --version
node 01-copy-cost.mjs
node 01-perimeter.mjsنمونهسؤال و پاسخ تشریحی
در سفارش ۴۰ صفحه با قیمت واحد ۱۲۰۰ تومان، خروجی را حساب کنید. اگر قیمت واحد به ریال دریافت شود چه چیزی باید تغییر کند؟
پاسخ تشریحی: ۴۰ × ۱۲۰۰ برابر ۴۸۰۰۰ تومان است. اگر ورودی ریال باشد، قرارداد واحد باید عوض شود یا تبدیل صریح و معتبر پیش از محاسبه انجام گیرد. تغییر نام برچسب خروجی بدون تبدیل، پاسخ را ده برابر اشتباه میکند. برای ۱۲۰۰۰ ریال در هر صفحه، مجموع ۴۸۰۰۰۰ ریال است که ۴۸۰۰۰ تومان میشود.
برنامهای برای خرید کابل، طول مسیر ۱۲ متر را ورودی میگیرد و خروجی ۱۲ چاپ میکند. آیا مسئله مشخص و پاسخ درست است؟
پاسخ تشریحی: هنوز مشخص نیست؛ باید واحد خروجی، طول ذخیرهٔ لازم، تعداد کابلها و سیاست گردکردن روشن شود. اگر قرارداد فقط یک کابل دقیقاً هماندازهٔ مسیر باشد، ۱۲ متر درست است؛ اگر دو متر ذخیره لازم باشد، ۱۴ متر میخواهیم. از عدد ورودی بهتنهایی نمیتوان قانون مسئله را حدس زد. یک نمونهٔ موفق جای تعریف قرارداد را نمیگیرد.
برای محیط مستطیل با طول ۷٫۵ و عرض ۲٫۵ متر پاسخ بدهید و دو ورودی نامعتبر پیشنهاد کنید. چرا آزمون فقط مربع کافی نیست؟
پاسخ تشریحی: محیط برابر ۲ × (۷٫۵ + ۲٫۵)، یعنی ۲۰ متر است. ضلع صفر و متن «۷ متر» طبق قرارداد تابع رد میشوند. مربع بهتنهایی ممکن است اشتباهِ استفاده از یک ضلع بهجای هر دو را پنهان کند؛ مستطیل با ضلعهای متفاوت، دادهٔ تشخیصی بهتری است. در آزمون خطا نیز انتظار را مشخص کنید: استثنا، نه عدد صفر ساختگی.
منابع و معیار پایان درس
معیار تحویل: برای یک مسئلهٔ تازه، ورودی، واحد، نوع، محدوده، رابطهٔ خروجی و رفتار خطا را بنویسید؛ سپس یک نمونهٔ عادی و یک نمونهٔ مرزی را دستی حل کنید. منابع تکمیلی رسمی، برای بررسی مفاهیم و ادامهٔ مطالعه معرفی شدهاند و متن و مثالهای فارسی تألیف مستقل تحریریه درسآوند هستند.
MIT OCW 6.0001 — Syllabus: https://ocw.mit.edu/courses/6-0001-introduction-to-computer-science-and-programming-in-python-fall-2016/pages/syllabus/
CS50x 2026 — Lecture 0: https://cs50.harvard.edu/x/notes/0/
CS50x 2026 — Lecture 1: https://cs50.harvard.edu/x/notes/1/
Node.js — Introduction: https://nodejs.org/learn/getting-started/introduction-to-nodejs
فایل مثالها، راهنمای فارسی اجرا و عیبیابی، پروژهٔ گزارش نمرهها و آزمون جمعبندی تألیفی با پاسخ تشریحی، رایگان در صفحهٔ بستهٔ تمرین همین دوره ارائه میشود:
https://www.darsavand.com/resources/computer-algorithms-practice-kit
تألیف و مثالهای اختصاصی: تحریریه درسآوند.
متن درسهای عمومی را بدون ورود بخوانید.