هر کسی که از Python استفاده کرده باشد، تقریباً بهطور قطع از «درک فهرست» هم استفاده کرده است؛ این ساختار از همان نسخههای اولیه بخشی اصلی از نحوهی نگارش Python بوده است.
امکانی که اینقدر راحت و کارآمد است، بهتدریج به زبانهای دیگر هم راه پیدا میکند، از جمله Julia.
برای برنامهنویسان Julia، درکها یک گزینهاند نه یک ضرورت، چون معمولاً جایگزینهایی هم وجود دارد (پخشسازی، توابع مرتبهبالا و غیره).
با این حال، یک درک اغلب روشی ساده، خوانا و کارآمد برای ساختن یک آرایه فراهم میکند. در نهایت، استفاده از آن به سلیقهی شخصی بستگی دارد و به اینکه نسبت به Python در مقابل زبانهای تابعی چه حسی دارید.
نحوهی نگارش آن عمدتاً کپی مستقیمی از Python است، اما با گسترشهایی برای آرایههای چندبعدی.
بسیار پایتونیک، همراه با بند اختیاری if.
julia> [x^2 for x in 1:3]
3-element Vector{Int64}:
1
4
9
julia> [x^2 for x in 1:3 if isodd(x^2)]
2-element Vector{Int64}:
1
9
شکل خروجی به جزئیات درک بستگی دارد: در موارد ساده معمولاً همان شکل مجموعهی ورودی است، اما با یک بند if به یک Vector مسطح میشود.
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> [x^2 for x in m]
2×3 Matrix{Int64}:
1 4 9
16 25 36
julia> [x^2 for x in m if isodd(x^2)]
3-element Vector{Int64}:
1
25
9
مثل Python، میتوانیم چند بند for با متغیرهای متفاوت داشته باشیم، با مجموعههای ورودی یکسان یا متفاوت.
julia> [x * y for x in 1:3 for y in 4:6]
9-element Vector{Int64}:
4
5
6
8
10
12
12
15
18
julia> [x * y for x in 1:3 for y in 4:6 if isodd(x * y)]
2-element Vector{Int64}:
5
15
این معادل حلقههای تودرتو است. خروجی یکبعدی است، حتی وقتی ورودی یک ماتریس باشد.
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> [x*y for x in m for y in m]
36-element Vector{Int64}:
1
4
2
5
(truncated output...)
بخش قبلی چند بند for را توصیف کرد که فقط با فاصله از هم جدا شده بودند.
در این بخش، تنها یک for وجود دارد و متغیرها با کاما از هم جدا شدهاند.
julia> [(x, y) for x in 1:3, y in 4:6]
3×3 Matrix{Tuple{Int64, Int64}}:
(1, 4) (1, 5) (1, 6)
(2, 4) (2, 5) (2, 6)
(3, 4) (3, 5) (3, 6)
هر متغیر در درک، یک بُعد تازه در خروجی میسازد، با یک عنصر برای هر ترکیب ممکن از متغیرها.
در مثال بالا، x در جهت سطرها افزایش مییابد و y در جهت ستونها.
ابعاد بالاتر هم ممکن است، با همان هشدارهای همیشگی دربارهی خوانایی خروجی.
بخشهای قبلی بر خروجی آرایه تمرکز داشتند، با درکی که داخل کروشه [ ... ] قرار میگیرد.
وقتی این کروشهها با پرانتز ( ... ) جایگزین شوند، اتفاق متفاوتی میافتد: به یک generator expression میرسیم: یک تکرارگر با ارزیابی تنبل که میتواند مقدار بعدی را در صورت نیاز تولید کند.
julia> g = ((x, y) for x in 1:3, y in 4:6)
Base.Generator{Base.Iterators.ProductIterator{Tuple{UnitRange{Int64}, UnitRange{Int64}}}, var"#9#10"}(var"#9#10"(), Base.Iterators.ProductIterator{Tuple{UnitRange{Int64}, UnitRange{Int64}}}((1:3, 4:6)))
# Indexing fails with a generator, the entries don't exist yet
julia> g[1, 2]
ERROR: MethodError: no method matching getindex(::Base.Generator{Base.Iterators.ProductIterator{Tuple{UnitRange{…}, UnitRange{…}}}, var"#9#10"}, ::Int64, ::Int64)
# conversion to array
julia> collect(g)
3×3 Matrix{Tuple{Int64, Int64}}:
(1, 4) (1, 5) (1, 6)
(2, 4) (2, 5) (2, 6)
(3, 4) (3, 5) (3, 6)
# generators are mainly designed for iteration
julia> [i * j for (i, j) in g]
3×3 Matrix{Int64}:
4 5 6
8 10 12
12 15 18
وقتی نتیجهی یک درک بیدرنگ برای پردازش بیشتر استفاده میشود، یک مولد میتواند از نظر حافظه بهصرفه باشد و نیاز به ذخیرهی یک آرایهی واسط بزرگ را از بین ببرد.
مولدی که بهعنوان آرگومان تابع استفاده میشود، به پرانتز اضافی نیاز ندارد.
# inefficient
julia> v = [x^2 for x in 1:1e6];
julia> sum(v)
3.333338333335e17
# better - use a generator
julia> sum(x^2 for x in 1:1e6)
3.3333383333312755e17
نحوهی نگارش مولدها همانند درکها است، بهجز کروشههای اطراف.
مولدها در سازندههای دیکشنری هم به کار میآیند.
julia> Dict(x => x^2 for x in 1:5)
Dict{Int64, Int64} with 5 entries:
5 => 25
4 => 16
2 => 4
3 => 9
1 => 1
شما در یک فروشگاه اینترنتی پوشاک مد کار میکنید. ایدهی یک قابلیت برای وبسایت به ذهنتان میرسد که در آن ستهای لباس به کاربر پیشنهاد میشود. آنچه برایتان مهم است، ستهای بالقوه، قیمت آنها و حذف گزینههای ناهماهنگ است.
هرچند ممکن است راههای متفاوتی برای حل مسائل زیر وجود داشته باشد، همه را میتوان با یک comprehension حل کرد.
تابع clothingitem() را پیادهسازی کنید تا دو Tuple بگیرد، یکی از categories و دیگری از qualities متناظرشان.
تابع باید یک Dict برگرداند که categories کلیدهای آن و qualities مقادیر آن هستند.
julia> categories = ("item_name", "price", "color", "base_color");
julia> qualities = ("Descriptive Name", 99.00, "Ochre Red", "red");
julia> clothingitem(categories, qualities)
Dict{String, Any} with 4 entries:
"price" => 99.0
"item_name" => "Descriptive Name"
"base_color" => "red"
"color" => "Ochre Red"
تابع شما باید بتواند هر تعداد categories را مدیریت کند.
تابع get_combinations() را پیادهسازی کنید تا یک Tuple از بالاتنهها و یک Tuple از پایینتنهها بگیرد.
تابع باید cartesian product این Tupleها را بهصورت یک آرایهی دوبعدی (یعنی Matrix) برگرداند.
هر عنصر یک ترکیب (top, bottom) از نوع Tuple است.
بالاتنهها در سطرها و پایینتنهها در ستونها تغییر میکنند.
julia> tops = (
Dict("item_name" => "Dress shirt"),
Dict("item_name" => "Casual shirt")
);
julia> bottoms = (
Dict("item_name" => "Jeans"),
Dict("item_name" => "Dress trousers")
);
julia> get_combinations(tops, bottoms)
2×2 Matrix{Tuple{Dict{String, String}, Dict{String, String}}}:
(Dict("item_name"=>"Dress shirt"), Dict("item_name"=>"Jeans")) (Dict("item_name"=>"Dress shirt"), Dict("item_name"=>"Dress trousers"))
(Dict("item_name"=>"Casual shirt"), Dict("item_name"=>"Jeans")) (Dict("item_name"=>"Casual shirt"), Dict("item_name"=>"Dress trousers"))
هر قطعهی لباس یک فیلد price دارد، بنابراین ممکن است مفید باشد که بتوان قیمت کامل ستها را با هم مقایسه کرد.
تابع get_prices() را پیادهسازی کنید که یک آرایه از ترکیبهای لباس میگیرد.
تابع باید آرایهای با همان شکل برگرداند که قیمت هر ست در جایگاه مربوط به خودش قرار دارد.
julia> tops = (
Dict("item_name" => "Dress shirt", "base_color" => "blue", "price" => 35),
Dict("item_name" => "Casual shirt", "base_color" => "black", "price" => 20)
);
julia> bottoms = (
Dict("item_name" => "Jeans", "base_color" => "blue", "price" => 30),
Dict("item_name" => "Dress trousers", "base_color" => "black", "price" => 75)
);
julia> combomatrix = get_combinations(tops, bottoms);
julia> get_prices(combomatrix)
2×2 Matrix{Int}::
65 110
50 95
julia> filteredmatrix = filter_clashing(combomatrix)
1-element Vector{Tuple{Dict{String, Any}, Dict{String, Any}}}:
(Dict("price" => 20, "item_name" => "Casual shirt", "base_color" => "black"), Dict("price" => 30, "item_name" => "Jeans", "base_color" => "blue"))
julia> get_prices(filteredmatrix)
1-element Vector{Int}:
50
هر قطعهی لباس یک فیلد base_color دارد.
از این فیلد استفاده کنید تا همهی ترکیبهایی را حذف کنید که بالاتنه و پایینتنهی آنها رنگ پایهی یکسان دارند.
تابع filter_clashing() را پیادهسازی کنید تا یک آرایه از ترکیبهای لباس بگیرد.
یک آرایهی یکبعدی (یعنی Vector) از ترکیبهای منطبق برگردانید.
julia> tops = (
Dict("item_name" => "Dress shirt", "base_color" => "blue", "price" => 35),
Dict("item_name" => "Casual shirt", "base_color" => "black", "price" => 20)
);
julia> bottoms = (
Dict("item_name" => "Jeans", "base_color" => "blue", "price" => 30),
Dict("item_name" => "Dress trousers", "base_color" => "black", "price" => 75)
);
julia> combos = get_combinations(tops, bottoms);
julia> filter_clashing(combos)
1-element Vector{Tuple{Dict{String, Any}, Dict{String, Any}}}:
(Dict("price" => 20, "item_name" => "Casual shirt", "base_color" => "black"), Dict("price" => 30, "item_name" => "Jeans", "base_color" => "blue"))