مسیرها
/
Go
Go
/
تمرین‌ها
/
روز انتخابات
روز انتخابات

روز انتخابات

تمرین یادگیری

مقدمه

Go هم مانند بسیاری از زبان‌های دیگر، «اشارهگر» دارد. اگر تازه با اشارهگرها آشنا شده‌اید، ممکن است کمی مرموز به نظر برسند، اما وقتی به آن‌ها عادت کنید، کاملاً ساده می‌شوند. اشارهگرها بخش مهمی از Go هستند، پس وقت بگذارید و آن‌ها را واقعاً درک کنید.

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

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

متغیرها و حافظه

فرض کنید یک متغیر صحیح معمولی به اسم a داریم:

var a int

وقتی متغیری تعریف می‌کنیم، Go باید جایی در حافظه برای ذخیره‌ی مقدار آن پیدا کند. این کار تا حد زیادی از دید ما پنهان است؛ وقتی بخواهیم مقدار ذخیره‌شده در آن بخش از حافظه را واکشی کنیم، کافی است با اسم متغیر به آن ارجاع دهیم.

برای مثال، وقتی a + 2 را می‌نویسیم، در واقع مقدار ذخیره‌شده در حافظه‌ی مرتبط با متغیر a را واکشی می‌کنیم و ۲ به آن اضافه می‌کنیم.

به همین ترتیب، وقتی بخواهیم مقدار موجود در بخش حافظه‌ی a را تغییر دهیم، می‌توانیم با اسم متغیر یک انتساب انجام دهیم:

a = 3

بخش حافظه‌ای که با a مرتبط است، اکنون مقدار 3 را ذخیره می‌کند.

اشارهگرها

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

var p *int // 'p' contains the memory address of an integer

اینجا متغیری به اسم p از نوع «اشارهگر به int» (*int) تعریف می‌کنیم. یعنی p نشانی حافظه‌ی یک عدد صحیح را نگه می‌دارد. «مقدار صفر» اشارهگرها nil است، چون اشارهگر nil هیچ نشانی حافظه‌ای در خود ندارد.

گرفتن اشارهگری به یک متغیر

برای یافتن نشانی حافظه‌ی مقدار یک متغیر، می‌توانیم از عملگر & استفاده کنیم. برای مثال، اگر بخواهیم نشانی حافظه‌ی متغیر a را پیدا کنیم و در اشارهگر p ذخیره کنیم، می‌توانیم این کار را انجام دهیم:

var a int
a = 2

var p *int
p = &a // the variable 'p' contains the memory address of 'a'

دسترسی به مقدار از طریق اشارهگر (مرجع‌زدایی)

وقتی اشارهگری داریم، ممکن است بخواهیم مقدار ذخیره‌شده در نشانی حافظه‌ای را که اشارهگر نمایندگی می‌کند بدانیم. این کار را می‌توانیم با عملگر * انجام دهیم:

var a int
a = 2

var p *int
p = &a // the variable 'p' contains the memory address of 'a'

var b int
b = *p // b == 2

عملیات *p مقداری را که در نشانی حافظه‌ی ذخیره‌شده در p قرار دارد واکشی می‌کند. به این عملیات اغلب «مرجع‌زدایی» می‌گویند.

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

var a int        // declare int variable 'a'
a = 2            // assign 'a' the value of 2

var pa *int
pa = &a          // 'pa' now contains to the memory address of 'a'
*pa = *pa + 2    // increment by 2 the value at memory address 'pa'

fmt.Println(a)   // Output: 4
                 // 'a' will have the new value that was changed via the pointer!

انتساب به *pa مقداری را که در نشانی حافظه‌ای که pa نگه می‌دارد تغییر می‌دهد. چون pa نشانی حافظه‌ی a را در خود دارد، با انتساب به *pa در واقع مقدار a را تغییر می‌دهیم!

اما یک نکته‌ی احتیاط: پیش از مرجع‌زدایی، همیشه بررسی کنید که اشارهگر nil نباشد. مرجع‌زدایی از یک اشارهگر nil باعث می‌شود برنامه در زمان اجرا از کار بیفتد!

var p *int // p is nil initially
fmt.Println(*p)
// panic: runtime error: invalid memory address or nil pointer dereference

اشارهگرها به structها

تا اینجا فقط اشارهگرها به مقادیر اولیه را دیده‌ایم. می‌توانیم برای structها هم اشارهگر بسازیم:

type Person struct {
    Name string
    Age  int
}

var peter Person
peter = Person{Name: "Peter", Age: 22}

var p *Person
p = &peter

می‌توانستیم یک Person تازه هم بسازیم و بلافاصله اشارهگری به آن ذخیره کنیم:

var p *Person
p = &Person{Name: "Peter", Age: 22}

وقتی اشارهگری به یک struct داریم، لازم نیست پیش از دسترسی به یکی از فیلدها اشارهگر را مرجع‌زدایی کنیم:

var p *Person
p = &Person{Name: "Peter", Age: 22}

fmt.Println(p.Name) // Output: "Peter"
                    // Go automatically dereferences 'p' to allow
                    // access to the 'Name' field

اسلایس‌ها و مپ‌ها از قبل اشارهگر هستند

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

func incrementPeterAge(m map[string]int) {
	m["Peter"] += 1
}

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

ages := map[string]int{
  "Peter": 21
}
incrementPeterAge(ages)
fmt.Println(ages)
// Output: map[Peter:22]
// The changes the function 'incrementPeterAge' made to the map are visible after the function ends!

همین موضوع وقتی یک عنصر موجود در یک اسلایس را تغییر می‌دهیم هم صدق می‌کند.

اما کارهایی که یک اسلایس تازه برمی‌گردانند، مانند append، حالت ویژه‌ای دارند و ممکن است اسلایس بیرون از تابع را تغییر ندهند. این به نحوه‌ی کار داخلی اسلایس‌ها مربوط است، اما در این تمرین به جزئیات آن نمی‌پردازیم، چون موضوعی پیشرفته‌تر است. اگر واقعاً کنجکاو هستید، می‌توانید در وبلاگ Go: سازوکارهای append بیشتر بخوانید.

دستورالعمل‌ها

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

در انتخابات امسال، تصمیم گرفته شد که به یک سیستم دیجیتال جدید برای شمارش آرا نیاز است. مدرسه برای ساختن این سیستم جدید به کمک شما نیاز دارد.

1. یک شمارنده‌ی آرا بسازید

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

تابعی به اسم NewVoteCounter بسازید که تعداد آرای اولیه‌ی یک نامزد را می‌پذیرد و یک اشاره‌گر به یک int برمی‌گرداند که با تعداد آرای اولیه‌ی داده‌شده مقداردهی شده است.

var initialVotes int
initialVotes = 2

var counter *int
counter = NewVoteCounter(initialVotes)
*counter == initialVotes // true

2. تعداد آرا را از یک شمارنده بگیرید

حالا راهی برای ساختن شمارنده‌های جدید دارید! اما اکنون متوجه می‌شوید که سیستم جدید به راهی برای گرفتن تعداد آرا از یک شمارنده هم نیاز خواهد داشت.

تابعی به اسم VoteCount بسازید که یک شمارنده (*int) را به عنوان آرگومان می‌گیرد و تعداد آرا در شمارنده را برمی‌گرداند. اگر شمارنده nil باشد، باید فرض کنید که شمارنده هیچ رأیی ندارد:

var votes int
votes = 3

var voteCounter *int
voteCounter = &votes

VoteCount(voteCounter)
// => 3

var nilVoteCounter *int
VoteCount(nilVoteCounter)
// => 0

3. آرا را در یک شمارنده افزایش دهید

بالاخره وقت شمارش آرا رسیده است! حالا به راهی برای افزایش آرا در یک شمارنده نیاز دارید.

تابعی به اسم IncrementVoteCount بسازید که یک شمارنده (*int) و تعدادی رأی را به عنوان آرگومان می‌گیرد و شمارنده را به اندازه‌ی آن تعداد آرا افزایش می‌دهد. می‌توانید فرض کنید اشاره‌گری که پاس داده می‌شود هرگز nil نخواهد بود.

var votes int
votes = 3

var voteCounter *int
voteCounter = &votes

IncrementVoteCount(voteCounter, 2)

votes == 5          // true
*voteCounter == 5   // true

4. نتایج انتخابات را بسازید

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

تابعی به اسم NewElectionResult بسازید که اسم یک نامزد و تعداد آرای او را دریافت می‌کند و یک نتیجه‌ی انتخاباتی جدید برمی‌گرداند.

var result *ElectionResult
result = NewElectionResult("Peter", 3)

result.Name == "Peter"  // true
result.Votes == 3       // true

ساختار نتیجه‌ی انتخابات از قبل برایتان ساخته شده است و به این صورت تعریف می‌شود:

type ElectionResult struct {
    // Name of the candidate
    Name    string
    // Votes of votes the candidate had
    Votes   int
}

5. نتایج را اعلام کنید

وقت آن است که رئیس جدید را به مدرسه اعلام کنید! رئیس در تابلوهای پیام دیجیتال کوچکی که مدرسه دارد اعلام خواهد شد. پیام باید اسم رئیس جدید و تعداد آرای او را با قالب زیر نشان دهد: <candidate_name> (<votes>). این نمونه‌ای از چنین پیامی است: "Peter (51)".

تابعی به اسم DisplayResult بسازید که یک *ElectionResult را به عنوان آرگومان دریافت می‌کند و رشته‌ای با پیام قابل نمایش برمی‌گرداند.

var result *ElectionResult
result = &ElectionResult{
    Name: "John",
    Votes: 32,
}

DisplayResult(result)
// => John (32)

6. بازشماری آرا

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

تابعی به اسم DecrementVotesOfCandidate بسازید که نتایج نهایی و اسم نامزدی را دریافت می‌کند که باید تعداد آرای او را کاهش دهید. نتایج نهایی به شکل یک map[string]int داده می‌شوند که کلیدها اسم نامزدها و مقادیر کل آرای آن‌ها هستند.

var finalResults = map[string]int{
    "Mary":  10,
    "John":  51,
}

DecrementVotesOfCandidate(finalResults, "Mary")

finalResults["Mary"]
// => 9
ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
Go Exercism

آماده‌اید روز انتخابات را شروع کنید؟

در Exercism ثبت‌نام کنید تا Go را همراه با 34 مفهوم165 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.