مسیرها
/
Ruby
Ruby
/
تمرین‌ها
/
راهبر لکوموتیو
راهبر لکوموتیو

راهبر لکوموتیو

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

مقدمه

تجزیه به عمل استخراج عناصر یک مجموعه، مانند Array یا Hash، اشاره دارد. سپس می‌توان مقادیر تجزیه‌شده را در همان دستور به متغیرها تخصیص داد.

تخصیص چندگانه توانایی تخصیص چندین متغیر برای تجزیه‌ی مقادیر در یک دستور است. این کار باعث می‌شود کد مختصرتر و خواناتر باشد و با جدا کردن متغیرهایی که باید تخصیص داده شوند با کاما انجام می‌شود، مانند first, second, third = [1, 2, 3].

عملگر splat (*) و عملگر double splat (**) اغلب در بافت‌های تجزیه به کار می‌روند.

Caution

نباید *<variable_name> و **<variable_name> را با * و ** اشتباه بگیرید. در حالی که * و ** به ترتیب برای ضرب و توان استفاده می‌شوند، *<variable_name> و **<variable_name> به عنوان عملگرهای ترکیب و تجزیه به کار می‌روند.

تخصیص چندگانه

تخصیص چندگانه به شما امکان می‌دهد چندین متغیر را در یک خط تخصیص دهید. برای جدا کردن مقادیر، از کاما , استفاده کنید:

>> a, b = 1, 2
=> [1, 2]
>> a
=> 1

تخصیص چندگانه به یک نوع داده محدود نمی‌شود:

>> x, y, z = 1, "Hello", true
=> [1, "Hello", true]
>> x
=> 1
>> y
=> 'Hello'
>> z
=> true

از تخصیص چندگانه می‌توان برای جابه‌جایی عناصر در آرایه‌ها استفاده کرد. این کار در الگوریتم‌های مرتب‌سازی بسیار رایج است. برای مثال:

>> numbers = [1, 2]
=> [1, 2]
>> numbers[0], numbers[1] = numbers[1], numbers[0]
=> [2, 1]
>> numbers
=> [2, 1]
Note

به این کار «تخصیص موازی» هم می‌گویند و می‌توان از آن برای جلوگیری از متغیر موقت استفاده کرد.

اگر تعداد متغیرها بیشتر از مقادیر باشد، به متغیرهای اضافی nil تخصیص داده می‌شود:

>> a, b, c = 1, 2
=> [1, 2]
>> b
=> 2
>> c
=> nil

تجزیه

در Ruby می‌توان عناصر آرایه‌ها/هش‌ها را به متغیرهای مجزا تجزیه کرد. چون مقادیر در آرایه‌ها به ترتیب اندیس ظاهر می‌شوند، به همان ترتیب در متغیرها باز می‌شوند:

>> fruits = ["apple", "banana", "cherry"]
>> x, y, z = fruits
>> x
=> "apple"

اگر مقادیری مورد نیاز نباشند، می‌توانید از _ استفاده کنید تا نشان دهید «جمع‌آوری شده اما استفاده نشده است»:

>> fruits = ["apple", "banana", "cherry"]
>> _, _, z = fruits
>> z
=> "cherry"

تجزیه‌ی عمیق

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

>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> (a, b), (c, d) = fruits_vegetables
>> a
=> "apple"
>> d
=> "potato"

همچنین می‌توانید فقط بخشی از یک آرایه‌ی تودرتو را به‌صورت عمیق باز کنید:

>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> a, (c, d) = fruits_vegetables
>> a
=> ["apple", "banana"]
>> c
=> "carrot"

اگر در تجزیه، متغیرهایی با جای‌گیری نادرست و/یا تعداد نادرست مقادیر وجود داشته باشد، خطای نگارش دریافت می‌کنید:

fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]

(a, b), (d) = fruits_vegetables
# syntax error, unexpected '=', expecting '.' or &. or :: or '['

((a, b), (d)) = fruits_vegetables
# syntax error, unexpected ')', expecting '.' or &. or :: or '['

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

تجزیه‌ی یک آرایه با عملگر splat تکی (*)

هنگام تجزیه‌ی یک آرایه می‌توانید از عملگر splat (*) برای گرفتن مقادیر «باقی‌مانده» استفاده کنید. این روش از برش دادن آرایه روشن‌تر است (که در برخی موقعیت‌ها خواناتر نیست). برای مثال، می‌توانیم اولین عنصر را استخراج کنیم و سپس مقادیر باقی‌مانده را در یک آرایه‌ی جدید بدون اولین عنصر قرار دهیم:

>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *last = fruits
>> x
=> "apple"
>> last
=> ["banana", "cherry", "orange", "kiwi", "melon", "mango"]

همچنین می‌توانیم مقادیر ابتدا و انتهای آرایه را استخراج کنیم و در همان حال همه‌ی مقادیر میانی را گروه‌بندی کنیم:

>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *middle, y, z = fruits
>> y
=> "melon"
>> middle
=> ["banana", "cherry", "orange", "kiwi"]

همچنین می‌توانیم از * در تجزیه‌ی عمیق استفاده کنیم:

>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>> (a, *rest), b = fruits_vegetables
>> a
=> "apple"
>> rest
=> ["banana", "melon"]

تجزیه‌ی یک Hash

تجزیه‌ی یک هش کمی با تجزیه‌ی یک آرایه تفاوت دارد. برای اینکه بتوانید یک هش را باز کنید، ابتدا باید آن را به یک آرایه تبدیل کنید. در غیر این صورت هیچ تجزیه‌ای رخ نمی‌دهد:

>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory
>> x
=> {:apple=>6, :banana=>2, :cherry=>3}
>> y
=> nil

برای تبدیل یک Hash به آرایه می‌توانید از متد to_a استفاده کنید:

>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> fruits_inventory.to_a
=> [[:apple, 6], [:banana, 2], [:cherry, 3]]
>> x, y, z = fruits_inventory.to_a
>> x
=> [:apple, 6]

اگر می‌خواهید کلیدها را باز کنید، می‌توانید از متد keys استفاده کنید:

>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.keys
>> x
=> :apple

اگر می‌خواهید مقادیر را باز کنید، می‌توانید از متد values استفاده کنید:

>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.values
>> x
=> 6

ترکیب

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

ترکیب یک آرایه با عملگر splat (*)

ترکیب یک آرایه را می‌توان با عملگر splat (*) انجام داد. این کار همه‌ی مقادیر را در یک آرایه جمع می‌کند.

>> fruits = ["apple", "banana", "cherry"]
>> more_fruits = ["orange", "kiwi", "melon", "mango"]

# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>> combined_fruits = *fruits, *more_fruits

>> combined_fruits
=> ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]

ترکیب یک هش با عملگر double splat (**)

ترکیب یک هش با استفاده از عملگر double splat (**) انجام می‌شود. این کار همه‌ی جفت‌های کلید/مقدار یک هش را در هش دیگر جمع می‌کند، یا دو هش را با هم ادغام می‌کند.

>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> more_fruits_inventory = {orange: 4, kiwi: 1, melon: 2, mango: 3}

# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}

# then the pairs are packed into combined_fruits_inventory
>> combined_fruits_inventory
=> {:apple=>6, :banana=>2, :cherry=>3, :orange=>4, :kiwi=>1, :melon=>2, :mango=>3}

استفاده از عملگر splat (*) و عملگر double splat (**) با متدها

ترکیب با پارامترهای متد

وقتی متدی می‌سازید که تعداد دلخواهی آرگومان می‌پذیرد، می‌توانید در تعریف متد از *arguments یا **keyword_arguments استفاده کنید. از *arguments برای جمع کردن تعداد دلخواهی آرگومان مکانی (غیرکلیدواژه‌ای) استفاده می‌شود و از **keyword_arguments برای جمع کردن تعداد دلخواهی آرگومان کلیدواژه‌ای استفاده می‌شود.

استفاده از *arguments:

# This method is defined to take any number of positional arguments
# (Using the single line form of the definition of a method.)

>> def my_method(*arguments)= arguments

# Arguments given to the method are packed into an array

>> my_method(1, 2, 3)
=> [1, 2, 3]

>> my_method("Hello")
=> ["Hello"]

>> my_method(1, 2, 3, "Hello", "Mars")
=> [1, 2, 3, "Hello", "Mars"]

استفاده از **keyword_arguments:

# This method is defined to take any number of keyword arguments

>> def my_method(**keyword_arguments)= keyword_arguments

# Arguments given to the method are packed into a dictionary

>> my_method(a: 1, b: 2, c: 3)
=> {:a => 1, :b => 2, :c => 3}

اگر متد تعریف‌شده هیچ پارامتر تعریف‌شده‌ای برای آرگومان‌های کلیدواژه‌ای نداشته باشد (**keyword_arguments یا <key_word>: <value>)، آن‌گاه آرگومان‌های کلیدواژه‌ای در یک هش جمع می‌شوند و به آخرین پارامتر تخصیص داده می‌شوند.

>> def my_method(a)= a

>> my_method(a: 1, b: 2, c: 3)
=> {:a => 1, :b => 2, :c => 3}

از *arguments و **keyword_arguments می‌توان در ترکیب با یکدیگر نیز استفاده کرد:

def my_method(*arguments, **keyword_arguments)
  p arguments.sum
  for (key, value) in keyword_arguments.to_a
    p key.to_s + " = " + value.to_s
  end
end


my_method(1, 2, 3, a: 1, b: 2, c: 3)
6
"a = 1"
"b = 2"
"c = 3"

همچنین می‌توانید آرگومان‌هایی پیش و پس از *arguments بنویسید تا آرگومان‌های مکانی مشخصی را ممکن کنید. این کار مانند تجزیه‌ی یک آرایه عمل می‌کند.

Caution

آرگومان‌ها باید به ترتیب مشخصی ساختاربندی شوند:

def my_method(<positional_arguments>, *arguments, <positional_arguments>, <keyword_arguments>, **keyword_arguments)

اگر این ترتیب را رعایت نکنید، خطا دریافت می‌کنید.

def my_method(a, b, *arguments)
  p a
  p b
  p arguments
end

my_method(1, 2, 3, 4, 5)
1
2
[3, 4, 5]

می‌توانید آرگومان‌های مکانی را پیش و پس از *arguments بنویسید:

>> def my_method(a, *middle, b)= middle

>> my_method(1, 2, 3, 4, 5)
=> [2, 3, 4]

همچنین می‌توانید آرگومان‌های مکانی، *arguments، آرگومان‌های کلیدواژه‌ای و **keyword_arguments را ترکیب کنید:

>> def my_method(first, *many, last, a:, **keyword_arguments)
     p first
     p many
     p last
     p a
     p keyword_arguments
     end

>> my_method(1, 2, 3, 4, 5, a: 6, b: 7, c: 8)
1
[2, 3, 4]
5
6
{:b => 7, :c => 8}

نوشتن آرگومان‌ها به ترتیب نادرست منجر به خطا می‌شود:

def my_method(a:, **keyword_arguments, first, *arguments, last)
  arguments
end

my_method(1, 2, 3, 4, a: 5)

syntax error, unexpected local variable or method, expecting & or '&'
... my_method(a:, **keyword_arguments, first, *arguments, last)

تجزیه در فراخوانی متدها

می‌توانید از عملگر splat (*) برای باز کردن یک آرایه از آرگومان‌ها در فراخوانی متد استفاده کنید:

def my_method(a, b, c)
  p c
  p b
  p a
end

numbers = [1, 2, 3]
my_method(*numbers)
3
2
1

همچنین می‌توانید از عملگر double splat (**) برای باز کردن یک هش از آرگومان‌ها در فراخوانی متد استفاده کنید:

def my_method(a:, b:, c:)
  p c
  p b
  p a
end

numbers = {a: 1, b: 2, c: 3}
my_method(**numbers)
3
2
1

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

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

Note

این تمرین را به‌راحتی می‌توان با برش، نمایه‌سازی و متدهای مختلف hash حل کرد. ولی می‌خواهیم هنگام حل هر یک از کارهای زیر، بسته‌بندی مقادیر، بازکردنشان و انتساب چندگانه را تمرین کنید.

1. فهرستی از همه‌ی واگن‌ها بسازید

دوستتان شناسه‌ی هر واگن (ID) را ثبت کرده است، ولی هیچ‌وقت مطمئن نیست که سیستم در هر لحظه قرار است چند واگن را پردازش کند. اگر این داده‌ها در یک آرایهی یکپارچه بسته‌بندی شوند، کار بقیه‌ی برنامه‌ی لجستیک بسیار راحت‌تر می‌شود.

متدی به اسم generate_list_of_wagons() پیاده‌سازی کنید که هر تعداد شناسه‌ی واگن را می‌پذیرد. هر شناسه یک عدد صحیح مثبت خواهد بود. سپس متد باید شناسه‌های داده‌شده را در قالب یک آرایهی واحد return کند.

LocomotiveEngineer.generate_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
# => [1, 7, 12, 3, 14, 8, 5]

2. فهرست واگن‌ها را اصلاح کنید

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

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

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

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

متدی به اسم fix_list_of_wagons() پیاده‌سازی کنید که دو آرایهی حاوی شناسه‌ی واگن‌ها می‌گیرد. این متد باید دو عنصر اول آرایهی نخست را به انتهای آن منتقل کند و مقادیر آرایهی دوم را پشت (یعنی سمت راست) شناسه‌ی لکوموتیو (1) درج کند. سپس متد باید آرایهای را همراه با این تغییرات return کند.

LocomotiveEngineer.fix_list_of_wagons([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15])
# => [1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5]

3. توقف‌های جاافتاده را اضافه کنید

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

متدی به اسم add_missing_stops() پیاده‌سازی کنید که یک هش مسیریابی و پس از آن هر تعداد آرگومان کلیدواژه‌ای می‌گیرد. این آرگومان‌ها می‌توانند به شکل یک هش باشند که یک یا چند توقف را در خود دارد، یا هر تعداد جفت کلیدواژه‌ای stop_<number>: "city". سپس متد شما باید هش مسیریابی را برگرداند، همراه با یک کلید اضافه که آرایهای از همه‌ی توقف‌های اضافه‌شده را به ترتیب در خود دارد.

LocomotiveEngineer.add_missing_stops({from: "New York", to: "Miami"},
                    stop_1: "Washington, DC", stop_2: "Charlotte", stop_3: "Atlanta",
                    stop_4: "Jacksonville", stop_5: "Orlando")
# => {from: "New York", to: "Miami", stops: ["Washington, DC", "Charlotte", "Atlanta", "Jacksonville", "Orlando"]}
Note

توقف‌ها همیشه به ترتیب درست فرستاده می‌شوند، پس نیازی نیست نگران مرتب‌سازی آن‌ها باشید.

4. اطلاعات مسیریابی را گسترش دهید

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

متدی به اسم extend_route_information() پیاده‌سازی کنید که دو هش می‌گیرد. هش اول شامل شهرهای مبدأ و مقصدی است که مسیر قطار میان آن‌ها قرار دارد.

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

Note

هش دوم می‌تواند ویژگی‌هایی متفاوت از نمونه یا بیش از آن داشته باشد.

LocomotiveEngineer.extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"})
# => {"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"}
ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
Ruby Exercism

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

در Exercism ثبت‌نام کنید تا Ruby را همراه با 21 مفهوم121 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.