Do you want to pick up from where you left of?
Take me there

مجموعه‌ها

لیست‌ها، چندتایی‌ها، لیست‌های کلمات کلید، و نگاشت‌ها.

لیست‌ها

لیست‌ها مجموعه‌های ساده‌ای از مقادیر هستند که ممکن است شامل چندین نوع داده باشند؛ لیست‌ها ممکن است شامل مقادیر غیر یکتا هم باشند:

iex> [3.14, :pie, "Apple"]
[3.14, :pie, "Apple"]

الکسیر مجموعه‌های از نوع لیست را با استفاده از لیست پیوندی پیاده‌سازی می‌کند. این یعنی دسترسی به طول یک لیست نسبت خطی با طول لیست دارد (O(n)). به همین دلیل، معمولا افزودن به آغاز لیست سریعتر از افزودن به پایان لیست است:

iex> list = [3.14, :pie, "Apple"]
[3.14, :pie, "Apple"]
# Prepending (fast)
iex> ["π" | list]
["π", 3.14, :pie, "Apple"]
# Appending (slow)
iex> list ++ ["Cherry"]
[3.14, :pie, "Apple", "Cherry"]

پیوند لیست

برای پیوند دو لیست از عملگر ++/2 استفاده می‌شود:

iex> [1, 2] ++ [3, 4, 1]
[1, 2, 3, 4, 1]

یک نکته درباره‌ی قالب به کار رفته در نام (++/2) که در بالا استفاده شده: در الکسیر (و ارلنگ که الکسیر بر پایه‌ی آن ساخته شده)، نام یک تابع یا عملگر دو بخش دارد: نامی که به آن می‌دهید (در اینجا ++) و اریتی (arity) هنگام صحبت از کد الکسیر (و ارلنگ) اریتی مفهوم بسیار مهمی است. اریتی تعداد آرگومان‌هایی است که یک تابع دریافت می‌کند (در این مورد، دو تا). اریتی و نام با یک خط اریب (نشان ممیز) با هم ادغام می‌شوند. بعدا در این مورد بیشتر صحبت خواهیم کرد؛ فعلا این دانش به شما در فهم این نشانه‌گذای کمک خواهد کرد.

تفریق لیست

تفریق لیست با عملگر --/2 پشتیبانی می‌شود؛ تفریق یک مقدار ناموجود امن است و مشکلی ایجاد نمی‌کند:

iex> ["foo", :bar, 42] -- [42, "bar"]
["foo", :bar]

مراقب مقادیر تکراری باشید. به ازای هر مورد از سمت راست، تنها اولین مورد یافت شده در سمت چپ از بین می‌رود:

iex> [1,2,2,3,2,3] -- [1,2,3,2]
[2, 3]

نکته: تفریق لیستی از مقایسه‌ی سخت‌گیرانه برای تطابق مقادیر استفاده می‌کند. برای مثال:

iex> [2] -- [2.0]
[2]
iex> [2.0] -- [2.0]
[]

سر / دنباله

معمول است که هنگام استفاده از لیست‌ها، با سر و دنباله‌ی لیست کار شود. سر عنصر نخست لیست است و دنباله لیستی است شامل دیگر عناصر. الکسیر دو تابع مفید hd و tl را برای کار باین این بخش‌ها فراهم کرده است:

iex> hd [3.14, :pie, "Apple"]
3.14
iex> tl [3.14, :pie, "Apple"]
[:pie, "Apple"]

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

iex> [head | tail] = [3.14, :pie, "Apple"]
[3.14, :pie, "Apple"]
iex> head
3.14
iex> tail
[:pie, "Apple"]

چندتایی‌ها

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

iex> {3.14, :pie, "Apple"}
{3.14, :pie, "Apple"}

معمول است که از چندتایی‌ها برای برگرداندن اطلاعات اضافی از توابع استفاده شود؛ کاربرد این قضیه وقتی به مبحث تطابق الگو برسیم آشکارتر خواهد شد:

iex> File.read("path/to/existing/file")
{:ok, "... contents ..."}
iex> File.read("path/to/unknown/file")
{:error, :enoent}

لیست کلمات کلیدی

لیست‌‌های کلمات کلیدی و نگاشت‌ها، مجموعه‌های ربطی الکسیر هستند. در الکسیر لیست کلمات کلیدی، لیست ویژه‌ای از عناصری از نوع داده‌ی چندتایی است که هر کدام ۲ عضو دارند و عضو اول یک اتم است؛ از لحاظ کارایی هم مانند لیست هستند:

iex> [foo: "bar", hello: "world"]
[foo: "bar", hello: "world"]
iex> [{:foo, "bar"}, {:hello, "world"}]
[foo: "bar", hello: "world"]

سه ویژگی لیست کلمات کلیدی اهمیت آنها را برجسته می‌کند:

به این دلایل معمولا از لیست‌های کلمات کلیدی برای فرستادن گزینه‌ها (option) به توابع استفاده می‌شود.

نگاشت‌ها

در الکسیر، نگاشت‌ها متداولترین نوع داده‌ی برای ذخیره‌ی زوج‌های کلید-مقدار هستند. برخلاف لیست‌های کلمات کلیدی، نگاشت‌ها هر نوع کلیدی را می‌پذیرند و مرتب هم نیستند. برای تعریف یک نگاشت از %{} استفاده می‌کنیم:

iex> map = %{:foo => "bar", "hello" => :world}
%{:foo => "bar", "hello" => :world}
iex> map[:foo]
"bar"
iex> map["hello"]
:world

از الکسیر نسخه ۱.۲، متغیرها به عنوان کلید نگاشت معتبر هستند:

iex> key = "hello"
"hello"
iex> %{key => "world"}
%{"hello" => "world"}

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

iex> %{:foo => "bar", :foo => "hello world"}
%{foo: "hello world"}

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

iex> %{foo: "bar", hello: "world"}
%{foo: "bar", hello: "world"}
iex> %{foo: "bar", hello: "world"} == %{:foo => "bar", :hello => "world"}
true

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

iex> map = %{foo: "bar", hello: "world"}
%{foo: "bar", hello: "world"}
iex> map.hello
"world"

ویژگی جالب دیگر نگاشت‌ها این است که قاعده‌ی خاص خودشان را برای به‌روزرسانی دارند (نکته: این یک نگاشت جدید می‌سازد)

iex> map = %{foo: "bar", hello: "world"}
%{foo: "bar", hello: "world"}
iex> %{map | foo: "baz"}
%{foo: "baz", hello: "world"}

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

به جای آن، برای ساخت کلید جدید ازMap.put/3 استفاده کنید:

iex> map = %{hello: "world"}
%{hello: "world"}
iex> %{map | foo: "baz"}
** (KeyError) key :foo not found in: %{hello: "world"}
    (stdlib) :maps.update(:foo, "baz", %{hello: "world"})
    (stdlib) erl_eval.erl:259: anonymous fn/2 in :erl_eval.expr/5
    (stdlib) lists.erl:1263: :lists.foldl/3
iex> Map.put(map, :foo, "baz")
%{foo: "baz", hello: "world"}
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!