Enum
مجموعهای از الگوریتمها برای برشمردن شمارشیها.
بررسی کوتاه
ماژول Enum
در برگیرندهی بیش از ۷۰ تابع برای کار با شمارشیها^1 است.
همهی مجموعههایی که در درس پیش previous lesson، دربارهی آنها آموختیم، به جز چندتاییها، شمارشی هستند.
این درس تنها بخشی از توابع موجود را پوشش میدهد، گرچه خودمان میتوانیم آنها را امتحان کنیم. بیایید آزمایش کوچکی در IEx انجام دهیم.
iex> Enum.__info__(:functions) |> Enum.each(fn({function, arity}) ->
...> IO.puts "#{function}/#{arity}"
...> end)
all?/1
all?/2
any?/1
any?/2
at/2
at/3
...
آشکار است که قابلیتهای زیادی در دسترس داریم، و این دلیل خوبی دارد. شمارش در مرکز برنامهنویسی تابعی قرار دارد، و این میتواند در کنار سایر امتیازات الکسیر قدرت بسیاری به توسعهدهندگان بدهد.
توابع متداول
فهرست کامل توابع در مستندات رسمی به نشانی Enum
در دسترس است، برای شمارش کندرو[^2] از ماژول Stream
استفاده کنید.
all?
در هنگام استفاده از all?/2
، و بسیاری توابع دیگر از ماژول Enum
، تابعی را به عنوان ورودی استفاده میکنیم تا روی عناصر مجموعه اعمال شود.
در مورد all?/2
، کل مجموعه باید به true
ارزیابی شود در غیر این صورت false
بازگردانده خواهد شد:
iex> Enum.all?(["foo", "bar", "hello"], fn(s) -> String.length(s) == 3 end)
false
iex> Enum.all?(["foo", "bar", "hello"], fn(s) -> String.length(s) > 1 end)
true
any?
بر خلاف مثال بالا، اگر دست کم یک عنصر به true
ارزیابی شود مقدار بازگشتی any?/2
برابر با true
خواهد بود:
iex> Enum.any?(["foo", "bar", "hello"], fn(s) -> String.length(s) == 5 end)
true
chunk_every
اگر نیاز دارید مجموعهای را به گروههای کوچکتر تقسیم کنید، احتمالا در پی تابع chunk_every/2
هستید:
iex> Enum.chunk_every([1, 2, 3, 4, 5, 6], 2)
[[1, 2], [3, 4], [5, 6]]
چند گزینه برای chunk_every/4
وجود دارد اما به آنها نمیپردازیم، مستندات رسمی تابع را بررسی کنید تا بیشتر در این مورد بدانید.
chunk_by
اگر نیاز به گروهبندی مجموعه بر اساس معیاری جز اندازه داشته باشیم، میتوانیم از تابع chunk_by/2
استفاده کنیم.
این تابع یک شمارشی و یک تابع را به عنوان ورودی دریافت میکند، و هنگامی که مقدار بازگشتی آن تابع تغییر کند گروه جدیدی آغاز میشود و ساخت گروه بعدی شروع میشود.
در مثال پایین، رشتههای هم اندازه در یک گروه قرار میگیرند تا هنگامی که به رشتهای با طول جدید برخورد کنیم:
iex> Enum.chunk_by(["one", "two", "three", "four", "five"], fn(x) -> String.length(x) end)
[["one", "two"], ["three"], ["four", "five"]]
iex> Enum.chunk_by(["one", "two", "three", "four", "five", "six"], fn(x) -> String.length(x) end)
[["one", "two"], ["three"], ["four", "five"], ["six"]]
map_every
گاهی تکه کردن مجموعه برای کاری که نیاز داریم کافی نیست.
در این صورت، map_every/3
ممکن برای انجام کاری روی nth
عنصر مفید باشد، همواره شامل اولین عضو هم میشود:
# Apply function every three items
iex> Enum.map_every([1, 2, 3, 4, 5, 6, 7, 8], 3, fn x -> x + 1000 end)
[1001, 2, 3, 1004, 5, 6, 1007, 8]
each
ممکن است نیاز به پیمایش یک مجموعه باشد اما بدون تولید کردن مقداری جدید، برای این کار از each/2
استفاده میکنیم:
iex> Enum.each(["one", "two", "three"], fn(s) -> IO.puts(s) end)
one
two
three
:ok
نکته: تابع each/2
اتم :ok
را باز میگرداند.
map
برای اعمال تابع خود به همهی عناصر یک مجموعه و تولید مجموعهای جدید از تابع map/2
استفاده میکنیم:
iex> Enum.map([0, 1, 2, 3], fn(x) -> x - 1 end)
[-1, 0, 1, 2]
min
تابع min/1
کمترین مقدار مجموعه را مییابد:
iex> Enum.min([5, 3, 0, -1])
-1
تابع min/2
همان کار را می کند، اما اجازه میدهد تابعی را مشخص کنیم تا در صورت خالی بودن مجموعه از آن برای تولید مقدار کمینه استفاده شود.
iex> Enum.min([], fn -> :foo end)
:foo
max
تابع max/1
مقدار بیشینهی مجموعه را بازمیگرداند:
iex> Enum.max([5, 3, 0, -1])
5
تابع max/2
برای max/1
همان نقشی را دارد که min/2
برای min/1
دارد:
iex> Enum.max([], fn -> :bar end)
:bar
filter
تابع filter/2
این امکان را به ما میدهد که مجموعه را پالایش کنیم تا تنها شامل عناصری شود که تابعی که به عنوان ورودی به آن دادهایم آنها را به true
ارزیابی کند.
iex> Enum.filter([1, 2, 3, 4], fn(x) -> rem(x, 2) == 0 end)
[2, 4]
reduce
با استفاده از reduce/3
میتوانیم یک مجموعه را به یک مقدار خلاصه کنیم.
برای این کار یک انباشتگر اختیاری (10
در این مثال) به تابع خود میفرستیم؛ اگر انباشتگری تعیین نشود اولین عنصر نوعدادهی شمارشی استفاده خواهد شد:
iex> Enum.reduce([1, 2, 3], 10, fn(x, acc) -> x + acc end)
16
iex> Enum.reduce([1, 2, 3], fn(x, acc) -> x + acc end)
6
iex> Enum.reduce(["a","b","c"], "1", fn(x,acc)-> x <> acc end)
"cba1"
sort
وجود دو تابع مرتبسازی مجموعهها را ساده کرده است.
تابع sort/1
از مرتبسازی عبارت ارلنگ برای تعیین توالی ترتیب استفاده میکند:
iex> Enum.sort([5, 6, 1, 3, -1, 4])
[-1, 1, 3, 4, 5, 6]
iex> Enum.sort([:foo, "bar", Enum, -1, 4])
[-1, 4, Enum, :foo, "bar"]
برای سادهتر کردن کار، در حالی که تابع sort/2
به ما اجازه میدهد تابع مرتبسازی خودمان را استفاده کنیم:
# with our function
iex> Enum.sort([%{:val => 4}, %{:val => 1}], fn(x, y) -> x[:val] > y[:val] end)
[%{val: 4}, %{val: 1}]
# without
iex> Enum.sort([%{:count => 4}, %{:count => 1}])
[%{count: 1}, %{count: 4}]
تابع sort/2
این امکان را میدهد که :asc
یا :desc
را به عنوان تابع مرتبسازی استفاده کنیم:
Enum.sort([2, 3, 1], :desc)
[3, 2, 1]
uniq
میتوانیم از uniq/1
برای حذف موارد تکراری از انواع دادهی شمارشی استفاده کنیم:
iex> Enum.uniq([1, 2, 3, 2, 1, 1, 1, 1, 1])
[1, 2, 3]
uniq_by
تابع uniq_by/2
هم موارد تکراری را حذف میکند، اما اجازه میدهد تابعی را برای مقایسهی یکتایی به آن معرفی کنیم:
iex> Enum.uniq_by([%{x: 1, y: 1}, %{x: 2, y: 1}, %{x: 3, y: 3}], fn coord -> coord.y end)
[%{x: 1, y: 1}, %{x: 3, y: 3}]
استفاده از عملگر ضبط (&) با Enum
بسیاری از توابع در ماژول Enum الکسیر توابع ناشناس را به عنوان نشانوند^3 میپذیرند تا روی هر عضو نوع دادهی شمارشی کار کنند.
این توابع ناشناس معمولا به صورت کوتاه با استفاده از عملگر ضبط^4 (&) نوشته میشوند.
اکنون چندین نمونه از شیوهی کاربرد عملگر ضبط و ماژول Enum را خواهیم دید. نسخههای مختلف از لحاظ عملکرد معادل هستند.
استفاده از عملگر ضبط با یک تابع ناشناس
در زیر مثالی از قواعد دستوری ارسال یک تابع ناشناس به Enum.map/2
آمده است.
iex> Enum.map([1,2,3], fn number -> number + 3 end)
[4, 5, 6]
حال از عملگر ضبط (&) استفاده میکنیم؛ هر عضو از لیست اعداد ([1,2,3]) ضبط میشود و در هنگام ارسال به تابع نگاشت، به متغیر &1
تخصیص مییابد.
iex> Enum.map([1,2,3], &(&1 + 3))
[4, 5, 6]
میتوان بازطراحی را ادامه داد و تابع ناشناس استفاده کننده از عملگر ضبط را به یک متغیر تخصیص داد و سپس آن را در تابع Enum.map/2
استفاده کرد.
iex> plus_three = &(&1 + 3)
iex> Enum.map([1,2,3], plus_three)
[4, 5, 6]
استفاده از عملگر ضبط به همراه یک تابع نامیده شده
نخست یک تابع نامیده شده میسازیم و آن را درون تابع ناشناس تعریف شده در Enum.map/2
فراخوانی میکنیم.
defmodule Adding do
def plus_three(number), do: number + 3
end
iex> Enum.map([1,2,3], fn number -> Adding.plus_three(number) end)
[4, 5, 6]
سپس میتوانیم با استفاده از عملگر ضبط، بازطراحی کنیم.
iex> Enum.map([1,2,3], &Adding.plus_three(&1))
[4, 5, 6]
برای کوتاهسازی بیشتر، میتوانیم تابع نامیده شده را مستقیما فراخوانی کنیم بی آن که به صورت صریح متغیر را ضبط کنیم.
iex> Enum.map([1,2,3], &Adding.plus_three/1)
[4, 5, 6]
[^2]: Lazy Enumeration
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!