Julia, বেশিরভাগ প্রোগ্রামিং ভাষার মতোই, নানা রকমের কালেকশন টাইপ সরবরাহ করে।
কিছু কালেকশন স্বভাবতই ক্রমবদ্ধ (Vectors এবং উচ্চমাত্রার Arrays সহ), তাই আমরা অবস্থান দিয়ে এলিমেন্ট অ্যাক্সেস করতে পারি।
আবার অন্য কালেকশনগুলো ক্রমহীন (Sets, Dicts), আর সেখানে এলিমেন্টে ঢুকতে হয় কী (key) বা মান দিয়ে।
সবচেয়ে সহজ ক্ষেত্রে ধরা যাক, আমাদের কাছে Ints-এর একটি Vector আছে, আর আমরা এটিকে ঊর্ধ্বক্রমে সাজাতে চাই।
julia> v = [3, 2, 4, 1]
4-element Vector{Int64}:
3
2
4
1
julia> sort(v)
4-element Vector{Int64}:
1
2
3
4
# sort descending
julia> sort(v, rev=true)
4-element Vector{Int64}:
4
3
2
1
সহজ ব্যাপার!
sort() ফাংশনের নামটাই স্পষ্ট, আর দেখতে গেলে এটি সাজানো Vector-টিই রিটার্ন করে।
কেবল মজা করলাম! সিলেবাসের এই পর্যায়ে এসে আপনি বোধহয় (সঠিকভাবেই) সন্দেহ করছেন যে কিছু সূক্ষ্ম বিষয় নিয়ে আমাদের আলোচনা করা দরকার।
sort(v) চালানোর পর v আসলে কী?
julia> v
4-element Vector{Int64}:
3
2
4
1
julia> issorted(v)
false
মূল ভেক্টরটি অপরিবর্তিত থাকে।
sort() একটি নন-মিউটেটিং ফাংশন, যা তার ইনপুটের একটি কপি রিটার্ন করে।
অনেক প্রেক্ষাপটেই এটি ভালো ডিজাইন: আমাদের হাতে একটি পিউর ফাংশন থাকে, যার আচরণ সম্পর্কে সহজেই ধারণা করা যায়, এমনকি প্যারালাল, ডিস্ট্রিবিউটেড কোডেও।
অন্যদিকে ধরা যাক, আমাদের কাছে একশ কোটি স্ট্রিংয়ের একটি অ্যারে আছে, আর আমরা এটিকে সাজাতে চাই।
সাজানোর কাজটা সম্ভবত ধীর হবে। তার উপর, আমাদের সবকিছু কপি করতে হবে (ধীর!), আর কপিটা জমা রাখতে যথেষ্ট মেমরি বরাদ্দ করতে হবে (কয়েক গিগাবাইট!)।
একটু মিল আছে এমন এই কোডটির সাথে তুলনা করুন:
# v is as previously
julia> issorted(v)
false
julia> sort!(v)
4-element Vector{Int64}:
1
2
3
4
julia> v
4-element Vector{Int64}:
1
2
3
4
julia> issorted(v)
true
এখন ইনপুট Vector-টি জায়গাতেই সাজানো হয়, কোনো কপি ছাড়াই, আর আগের ক্রমটি স্থায়ীভাবে হারিয়ে যায়।
এটি কার্যকর হতে পারে, আবার বাগে ভরা হতে পারে আর অপ্রত্যাশিত সাইড-এফেক্ট ডেকে আনতে পারে, আবার আপনার কোড ব্যবহার করা অন্য সবার জন্য বিভ্রান্তিকরও হতে পারে। সাবধানে বেছে নিন!
sort!() হলো একটি "মিউটেটিং ফাংশন", আর (খুব জোরালো) কনভেনশন অনুযায়ী, যে ফাংশন তার ইনপুট মিউটেট করে তার নামের শেষে ! যোগ করা হয়, প্রোগ্রামারদের জন্য এক ধরনের সতর্কবার্তা হিসেবে।
যদি সাজানো হচ্ছে এমন এলিমেন্টগুলো বড় অবজেক্ট হয়, তাহলে তাদের অবস্থানভিত্তিক ইনডেক্স নিয়ে কাজ করা সহজতর হতে পারে।
julia> langs = ["Julia", "C++", "Python", "Elixir"]
4-element Vector{String}:
"Julia"
"C++"
"Python"
"Elixir"
julia> inxs = sortperm(langs) # => Integers
4-element Vector{Int64}:
2
4
1
3
julia> langs[inxs] # sort the entire vector
4-element Vector{String}:
"C++"
"Elixir"
"Julia"
"Python"
julia> langs[inxs[1:2]] # slice the indices
2-element Vector{String}:
"C++"
"Elixir"
যেহেতু langs একটি Vector, প্রতিটি ইনডেক্স 1:length(langs) রেঞ্জের একটি ইন্টিজার।
sortperm()-এর আউটপুট হলো এই ইন্টিজারগুলোর একটি পারমিউটেশন, যা ইনপুট ভেক্টরের একটি সাজানো ক্রমের সাথে মিলে যায়।
আপনি এমন একটি কোম্পানিতে সহকারী উৎপাদন প্রকৌশলী হিসেবে নিয়োগ পেয়েছেন, যে কোম্পানি কচ্ছপের জন্য কুইল্ট বানায়।
দিনভর অর্ডার আসতে থাকে, যা থাকে একটি কাস্টমারের নাম বা নম্বর আর একটি অর্ডারের পরিমাণ। এরপর দিনের উৎপাদন শুরু হওয়ার আগে সকালে এগুলো প্রক্রিয়া করা হয়।
দিনের সব অর্ডার পূরণ হয়ে গেলেই আপনি পুরো বেতন নিয়ে বাড়ি ফিরতে পারেন, তাই কাজের দিন যতটা সম্ভব ছোট করার প্রণোদনা রয়েছে। যেহেতু প্রতিটি অর্ডার একটি মাত্র মেশিনে সম্পন্ন হয়, সবচেয়ে বড় অর্ডারগুলো আগে শিডিউল করলে একটি কাজ শুরু করার ওভারহেড ছোট অর্ডারগুলোর ওপর চাপানো যায়, ফলে মোট উৎপাদনের সময় কমে আসে। তাই আপনি এই শিডিউলিং দ্রুততর করতে সহায়ক কিছু সাজানোর ফাংশন বানানোর সিদ্ধান্ত নিয়েছেন, যাতে বাড়ি ফিরে আপনার কচ্ছপের জন্য একটি সোয়েটার বুনতে পারেন।
যেমন বলা হয়েছে, পরিমাণের অ্যারেটি বড় থেকে ছোট ক্রমে সাজানো দরকার। এই অ্যারেগুলো সম্ভবত অনেক বড় হতে পারে, তাই এগুলোকে ইন-প্লেস সাজানো উচিত। সবকিছু ঠিকভাবে ট্র্যাক রাখতে, পরিমাণের সাথে মিল রেখে কাস্টমারদের অ্যারেও সাজাতে পারা দরকার, তাই পরিমাণগুলোর একটি সাজানোর পারমুটেশন লাগবে।
একটি ফাংশন sortquantity!(qty) লিখুন, যা Integer পরিমাণের একটি Vector নেয়। এই ফাংশনটি ইনপুট qty-কে ইন-প্লেস অবরোহী ক্রমে সাজায় এবং এই ক্রমের একটি সাজানোর পারমুটেশন রিটার্ন করে।
julia> qty = [8, 9, 5, 6]
4-element Vector{Int64}:
8
9
5
6
julia> sortquantity!(qty)
4-element Vector{Int64}:
2
1
4
3
julia> qty # original vector is sorted in-place
4-element Vector{Int64}:
9
8
6
5
উপরে বলা হয়েছে, কাস্টমারদের একটি নির্দিষ্ট ক্রমে ট্র্যাক রাখা দরকার।
একটি ফাংশন sortcustomer(cust, srtperm) লিখুন, যা কাস্টমারদের একটি Vector cust এবং সাজানোর পারমুটেশনের একটি Vector srtperm নেয়। এই ফাংশনটি এমন একটি নতুন অ্যারে রিটার্ন করে, যেখানে কাস্টমাররা সাজানোর পারমুটেশনে নির্দিষ্ট ক্রম অনুযায়ী থাকে।
julia> cust = ["a", "b", "c"]
3-element Vector{String}:
"a"
"b"
"c"
julia> sortcustomer(cust, [2, 3, 1])
3-element Vector{String}:
"b"
"c"
"a"
julia> cust # original vector remains unsorted
3-element Vector{String}:
"a"
"b"
"c"
এখন আপনি সবকিছু একসাথে জুড়ে দিতে পারেন, যাতে ওই কচ্ছপগুলো উষ্ণ থাকে! এখন আপনি পরিমাণ আর কাস্টমার ঠিকভাবে সাজাতে পারেন, কিন্তু শিপিংয়ের জন্য মূল ক্রমটি ফিরিয়ে আনতে পারা দরকার। এটি একটি ইনভার্স সাজানোর পারমুটেশন দিয়ে করা যায়।
একটি ফাংশন production_schedule!(cust, qty) লিখুন, যা কাস্টমারদের একটি Vector এবং পরিমাণের একটি Vector নেয়। এই ফাংশনটি পরিমাণগুলো ইন-প্লেস অবরোহী ক্রমে সাজায়। এটি পরিমাণের মতো একই ক্রমে সাজানো কাস্টমারদের একটি নতুন Vector এবং একটি ইনভার্স সাজানোর পারমুটেশন রিটার্ন করে, যা দিয়ে সাজানো ভেক্টরগুলোকে তাদের মূল ক্রমে ফেরানো যায়।
julia> cust, qty = ["a", "b", "c"], [2, 3, 1]
(["a", "b", "c"], [2, 3, 1])
julia> orderedcust, invperm = production_schedule!(cust, qty)
(["b", "a", "c"], [2, 1, 3])
julia> qty # has been sorted in-place
3-element Vector{Int64}:
3
2
1
julia> orderedcust[invperm], qty[invperm] # inverse permutation gives original ordering
(["a", "b", "c"], [2, 3, 1])
Exercism-এ সাইন আপ করুন, Julia ট্র্যাকের 35টি কনসেপ্ট128টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।