Vectors कॉन्सेप्ट में हमने बहुत पहले देखा था कि "ऐरे का आकार कुछ भी हो सकता है (केवल आपके हार्डवेयर की मेमोरी की सीमाओं तक), और उसमें मनमाने ढंग से कितने भी आयाम हो सकते हैं।"
तब से, चीज़ों को आसान रखने के लिए हमने एक से ज़्यादा आयाम वाले ऐरे को लगभग अनदेखा ही किया है। अगर आप Julia के संदर्भ दस्तावेज़ों को उनकी पूरी जटिलता में पढ़ने की कोशिश करें, तो यह फैसला ज़्यादा समझ में आएगा।
लेकिन वैज्ञानिक कंप्यूटिंग में ज़्यादा आयाम वाले ऐरे बहुत, बहुत ज़रूरी हैं, इसलिए हमें उन्हें समझना ज़रूरी है।
नामकरण: गणित की सदियों पुरानी परंपरा के अनुसार, हम 1-D ऐरे को Vectors और 2-D ऐरे को Matrices कहते हैं।
इस दस्तावेज़ के ज़्यादातर उदाहरण मैट्रिक्स के होंगे। 3 या उससे ज़्यादा आयामों के साथ काम करना सिंटैक्स के हिसाब से लगभग एक जैसा है, लेकिन (2-D स्क्रीन पर) उसका आउटपुट पढ़ना मुश्किल और उलझाने वाला होता है।
हमने चौकोर ब्रैकेट के अंदर कॉमा से अलग की गई सूची डालकर बहुत सारे वेक्टर बनाए हैं। सेपरेटर के रूप में सेमीकोलन भी इस्तेमाल किया जा सकता है।
julia> v = [1, 2, 3]
3-element Vector{Int64}:
1
2
3
julia> w = [1; 2; 3]
3-element Vector{Int64}:
1
2
3
julia> v == w
true
अगर हम सेपरेटर के रूप में स्पेस (या टैब) इस्तेमाल करें, तो नतीजा अलग होता है।
julia> u = [1 2 3]
1×3 Matrix{Int64}:
1 2 3
अब Julia इसे 1×3 मैट्रिक्स मानता है (दूसरे संदर्भों में हम इसे row vector कहते हैं)।
सामान्य तौर पर, स्पेस चीज़ों को आड़ी दिशा में जोड़ता है और सेमीकोलन (या नई लाइनें) उन्हें खड़ी दिशा में जोड़ते हैं।
"चीज़ों" का उल्लेख जानबूझकर अस्पष्ट है, क्योंकि Julia जो कुछ भी आप देते हैं, उसके साथ काम करने की कोशिश करता है।
julia> [v 2v]
3×2 Matrix{Int64}:
1 2
2 4
3 6
julia> [v; 2v]
6-element Vector{Int64}:
1
2
3
2
4
6
hcat() और vcat() फंक्शन भी वही काम करते हैं, और यह और स्पष्ट कर देते हैं कि ये आड़ी और खड़ी दिशाओं में जोड़ रहे हैं।
इसका ज़्यादा आयामों वाला रूप cat() फंक्शन है।
jjulia> hcat(v, 2v)
3×2 Matrix{Int64}:
1 2
2 4
3 6
julia> vcat(v, 2v)
6-element Vector{Int64}:
1
2
3
2
4
6
स्पष्ट मैट्रिक्स टाइप करने का सुविधाजनक तरीका रो-मेजर क्रम है, क्योंकि यह मनुष्य की सहज समझ से मेल खाता है (आड़ा टेक्स्ट लिखने वाली संस्कृतियों के लिए देखने में आसान)।
julia> m = [1 2 3; 4 5 6]
2×3 Matrix{Int64}:
1 2 3
4 5 6
लेकिन ध्यान रखिए कि Julia (Fortran, R और Matlab की तरह, पर C/C++ या NumPy के विपरीत) N-आयाम वाले ऐरे को कॉलम-मेजर क्रम में संग्रहीत करता है, और अगर आप एलिमेंट पर लूप चलाते हैं तो इससे परफॉर्मेंस में बहुत बड़ा अंतर पड़ सकता है। अपने CPU कैश की मदद कीजिए, ताकि वह आपकी मदद कर सके!
# put these integers in 2 rows and 3 columns
julia> reshape(collect(1:6), 2, 3)
2×3 Matrix{Int64}:
1 3 5
2 4 6
ऊपर का उदाहरण 1 से 6 तक के पूर्णांक लेता है और उनसे 2×3 मैट्रिक्स को कॉलम के हिसाब से भरता है।
आम तरह के ऐरे (एक समान या रैंडम) बनाने के लिए कई सहायक फंक्शन हैं।
julia> zeros(2, 3) # see also ones()
2×3 Matrix{Float64}:
0.0 0.0 0.0
0.0 0.0 0.0
julia> falses(2, 2) # booleans, see also trues()
2×2 BitMatrix:
0 0
0 0
julia> rand(Float32, 2, 3) # random numbers in the interval [0, 1)
2×3 Matrix{Float32}:
0.768823 0.169633 0.632565
0.388451 0.109176 0.850381
2-D ऐरे के लिए हम आम तौर पर [row, col] क्रम में दो इंडेक्स इस्तेमाल करते हैं।
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> m[1, 2] # row 1, col 2
2
# Stay within bounds! There is no row 3.
julia> m[3, 1]
ERROR: BoundsError: attempt to access 2×3 Matrix{Int64} at index [3, 1]
julia> m[3]
2
अंतिम उदाहरण शायद चौंकाने वाला है: एक अकेला इंडेक्स एरर नहीं है, और एक ही एलिमेंट लौटाता है।
इसकी वजह कॉलम-मेजर क्रम वाली टिप्पणी में ही है: Julia पहले कॉलम 1 में नीचे जाता है, फिर कॉलम 2 में, जब तक कि उसे मेमोरी में तीसरा एलिमेंट न मिल जाए।
सावधान रहिए: सामान्य उद्देश्य वाली लाइब्रेरी लिखते समय यह कभी-कभी काम आता है, लेकिन इससे उलझन होने की आशंका ज़्यादा रहती है!
ऐरे का आकार जानने पर भी ऐसी ही दिक्कत आती है।
length() एलिमेंट की कुल संख्या देता है, और size() ndims() एलिमेंट वाला टपल देता है जिसमें हर आयाम की लंबाई होती है।
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> length(m)
6
julia> size(m) # 2 rows, 3 cols
(2, 3)
julia> ndims(m) # how many dimensions? Like `m |> size |> length`
2
हम आसानी से एक सब-मैट्रिक्स कॉपी कर सकते हैं।
नीचे के उदाहरण में reshape() फंक्शन ऐरे को कॉलम के हिसाब से भरकर उसे दिए गए आयामों में ढाल देता है, और फिर हम उसका एक हिस्सा काट लेते हैं।
julia> m12 = reshape(collect(1:12), 4, 3)
4×3 Matrix{Int64}:
1 5 9
2 6 10
3 7 11
4 8 12
julia> m12[2:4, 1:2]
3×2 Matrix{Int64}:
2 6
3 7
4 8
# some rows, all columns
julia> m12[2:4, :]
3×3 Matrix{Int64}:
2 6 10
3 7 11
4 8 12
अकेला : का मतलब है "इस आयाम की हर चीज़ कॉपी करो"।
लगातार न होने वाली पंक्तियों/कॉलमों के लिए वेक्टर इस्तेमाल कीजिए:
julia> m12[[1, 3], :] # rows 1 and 3
2×3 Matrix{Int64}:
1 5 9
3 7 11
हम इससे पहले 1-D कलेक्शन पर लागू sum() और maximum() जैसे एग्रीगेशन फंक्शन देख चुके हैं, जो सारे एलिमेंट पर काम करते हैं और एक स्केलर नतीजा लौटाते हैं।
यह ज़्यादा आयामों में भी काम करता है।
लेकिन कभी-कभी हमें फंक्शन सिर्फ एक आयाम पर लागू करना होता है, जैसे नीचे की ओर या आड़े जोड़कर 1 आकार वाले singleton dimension वाला ऐरे लौटाना।
इसके लिए एक वैकल्पिक dims कीवर्ड आर्गुमेंट मौजूद है।
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> sum(m) # sum everything
21
julia> sum(m; dims=1) # sum down
1×3 Matrix{Int64}:
5 7 9
julia> sum(m; dims=2) # sum across
2×1 Matrix{Int64}:
6
15
# other aggregation functions are similar
julia> maximum(m; dims=2)
2×1 Matrix{Int64}:
3
6
dims की वैल्यू वह आयाम होती है जो सिंगलटन बन जाता है, इसलिए ऊपर के उदाहरणों में dims=1 => 1×3 और dims=2 => 2×1 मिलता है।
इसी तरह, अगर dims को किसी ऐरे या रेंज पर सेट किया जाए, तो ज़्यादा आयाम वाले ऐरे एक साथ कई आयाम घटा सकते हैं (फिर से, आउटपुट समझने के लिए थोड़ा सोचना पड़ सकता है!)
dims कीवर्ड आर्गुमेंट sum() जैसे बिल्ट-इन फंक्शन में आम है, लेकिन अपने कोड में इसके बराबर कुछ कैसे लिखें?
एक अच्छा जवाब है reduce() जैसे हायर-ऑर्डर फंक्शन इस्तेमाल करना, और इस पर आगे किसी कॉन्सेप्ट में थोड़ा विस्तार से बात होगी।
या फिर, Julia कई ऐसे फंक्शन देता है जिनकी मदद से हम N-आयाम वाले ऐरे को वेक्टर के अंदर वेक्टर (नेस्टेड) की तरह मान सकते हैं।
मैट्रिक्स के लिए eachrow() और eachcol() सुविधाजनक हैं, लेकिन ज़्यादा सामान्य फंक्शन eachslice() है, जो किसी भी आयाम को संभाल सकता है।
# m is as in the previous examples
julia> eachrow(m)
2-element RowSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}:
[1, 2, 3]
[4, 5, 6]
julia> eachcol(m)
3-element ColumnSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}:
[1, 4]
[2, 5]
[3, 6]
इसका टाइप देखकर थोड़ा डर लगता है, लेकिन ऐसा सिर्फ इसलिए है क्योंकि यह मूल ऐरे का एक व्यू है, जिस पर हम बिना कॉपी बनाए काम कर सकते हैं। Julia के ऐरे का आकार टेराबाइट तक हो सकता है, इसलिए कॉपी बनाना परफॉर्मेंस के लिए बड़ा सिरदर्द हो सकता है!
ऐसे व्यू का इस्तेमाल लूप चलाने, ब्रॉडकास्टिंग, या अब तक सिलेबस में देखे गए बाकी सारे ऑपरेशन के लिए किया जा सकता है।
साथ ही, ऐरे इनपुट के साथ कॉम्प्रिहेंशन काफी शक्तिशाली और बहुमुखी हो सकते हैं।
सरल मामलों का उल्लेख Loops कॉन्सेप्ट में किया गया था, लेकिन आगे किसी कॉन्सेप्ट में इस पर विस्तार से चर्चा होगी।
1-D मामले पर हमने Vector Operations कॉन्सेप्ट में चर्चा की थी।
julia> v = [1.2, 1.5, 1.7]
3-element Vector{Float64}:
1.2
1.5
1.7
julia> v .- 0.5
3-element Vector{Float64}:
0.7
1.0
1.2
डॉट वाला ऑपरेटर .- सिंगलटन 0.5 को आयाम मिलाने के लिए broadcasting करके [0.5, 0.5, 0.5] के बराबर मान लेता है, और फिर घटाव हर एलिमेंट पर अलग-अलग करता है।
इसे ज़्यादा आयामों तक बढ़ाना वास्तव में इसी का और विस्तार भर है।
उदाहरण के लिए, 2x1 रो वेक्टर [1.0 1.5 2.0] को 2x3 मैट्रिक्स [1 2 3; 4 5 6] से गुणा करते समय ब्रॉडकास्ट करना, [1.0 1.5 2.0] को [1.0 1.5 2.0; 1.0 1.5 2.0] तक ब्रॉडकास्ट करके हर एलिमेंट पर गुणा करने के बराबर है।
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> m .* [1.0 1.5 2.0] # broadcast a 1x3 row vector down
2×3 Matrix{Float64}:
1.0 3.0 6.0
4.0 7.5 12.0
julia> m .* [1.0 1.5 2.0; 1.0 1.5 2.0] # 2x3 elementwise multiplication
2×3 Matrix{Float64}:
1.0 3.0 6.0
4.0 7.5 12.0
julia> m ./ [1, 2] # broadcast a 2x1 column vector across
2×3 Matrix{Float64}:
1.0 2.0 3.0
2.0 2.5 3.0
julia> m ./ [1 1 1; 2 2 2] # 2x3 elementwise division
2×3 Matrix{Float64}:
1.0 2.0 3.0
2.0 2.5 3.0
ध्यान दें: ब्रॉडकास्टिंग के लिए सिंगलटन न होने वाले आयामों का आकार मेल खाना चाहिए (जैसे 2x3 Matrix .* 2x1 Matrix).
इसके अलावा, किसी फंक्शन को Matrix के हर एलिमेंट पर ठीक वैसे ही ब्रॉडकास्ट किया जा सकता है जैसे Vector के साथ किया जाता है।
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> (x -> x^2).(m) # broadcast a function to all elements
2×3 Matrix{Int64}:
1 4 9
16 25 36
शुरू में शायद लगे कि प्रोग्रामर के उलझने की गुंजाइश आयामों की संख्या के साथ तेज़ी से बढ़ती जाती है, लेकिन अभ्यास से बहुत मदद मिलती है। साथ ही, अलग सिंटैक्स होने के बावजूद NumPy ऐरे की जानकारी Julia पर काफी अच्छी तरह लागू होती है।
डॉट मैट्रिक्स एक द्वि-आयामी चित्र होता है, जो डॉट और व्हाइटस्पेस से मिलकर बनता है। जो चित्र बनाना है, वह व्हाइटस्पेस की पृष्ठभूमि पर डॉट से बनता है।
डॉट मैट्रिक्स वाले चित्र को मेमोरी में द्वि-आयामी Matrix के रूप में रखा जा सकता है और उस पर काम किया जा सकता है। आगे जो भी आएगा, उसमें मैट्रिक्स के "व्हाइटस्पेस" को 0 से और "डॉट" को शून्य के अलावा किसी संख्या से दिखाया जाएगा।
Matrix बनाइएMatrix इस तरह दिखता है, जिसमें 0 व्हाइटस्पेस है और 1 डॉट:
[
0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0;
0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0;
0 1 0 0 1 0 1 0 0 0 0 1 0 1 0 0 1 0;
0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1;
0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0;
0 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 0;
0 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 0;
0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0;
]
अगर आप इसे "रेंडर" करें, तो यह ऐसा दिखता है:
XX XX
X X
X X X X
X X X X X X
X X
X X
X X X X
X X X X
X XX X
X X
XX XX
frown!() और frown() फंक्शन बनाइए, जो Exercism लोगो वाला Matrix लेते हैं। ऐसा Matrix लौटाइए, जिसमें मुस्कुराता हुआ मुँह बदलकर उदास मुँह बन गया हो।
नतीजे में बनने वाला Matrix ऐसा रेंडर होगा:
XX XX
X X
X X X X
X X X X X X
X X
X X
X XX X
X X X X
X X X X
X X
XX XX
stickerwall() फंक्शन बनाइए, जो इनपुट में Exercism का मैट्रिक्स लेता है। ऐसा डॉट मैट्रिक्स वाला Matrix लौटाइए, जो रेंडर करने पर ऐसा दिखे:
XX XX XX XX
X X X X
X X X X X X X X
X X X X X X X X X X X X
X X X X
X XX X
X X X X X XX X
X X X X X X X X
X XX X X X X X
X X X X
XX XX XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX XX XX XX
X X X X
X X X X X X X X
X X X X X X X X X X X X
X X X X
X XX X
X XX X X X X X
X X X X X X X X
X X X X X XX X
X X X X
XX XX XX XX
डॉट के लिए सिर्फ 1 इस्तेमाल करने तक हम सीमित नहीं हैं, इसलिए चाहें तो हम इसमें और भी काम की जानकारी भर सकते हैं। colpixelcount() फंक्शन बनाइए, जो इनपुट में 1 डॉट वाला कोई भी डॉट मैट्रिक्स लेता है। उसी आकार का डॉट मैट्रिक्स लौटाइए, जिसमें हर कॉलम के डॉट उस कॉलम में मौजूद डॉट की संख्या हों।
इनपुट में Exercism लोगो वाला Matrix देने पर आउटपुट में ऐसा Matrix मिलता है:
[
0 0 2 2 0 0 0 0 0 0 0 0 0 0 2 2 0 0;
0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
0 8 0 0 0 1 0 0 0 0 0 0 1 0 0 0 8 0;
0 8 0 0 1 0 2 0 0 0 0 2 0 1 0 0 8 0;
0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1;
0 8 0 0 0 0 2 0 0 0 0 2 0 0 0 0 8 0;
0 8 0 0 0 0 0 1 0 0 1 0 0 0 0 0 8 0;
0 8 0 0 0 0 0 0 1 1 0 0 0 0 0 0 8 0;
0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
0 0 2 2 0 0 0 0 0 0 0 0 0 0 2 2 0 0;
]
हम अपनी बनाई चीज़ों को रेंडर करने की सिर्फ बातें करते रहें, यह तो ठीक नहीं है। हम चाहेंगे कि आसानी से देख पाने के लिए हम यह काम वास्तव में कर सकें। render() फंक्शन बनाइए, जो इनपुट में डॉट मैट्रिक्स लेता है। ऐसी स्ट्रिंग लौटाइए, जिसमें डॉट 'X' के रूप में, 0 ' ' के रूप में, और हर पंक्ति को जोड़ने के लिए न्यूलाइन रेंडर हों।
julia> render(E)
" XX XX \n X X \n X X X X \n X X X X X X \n X X \nX X\n X X X X \n X X X X \n X XX X \n X X \n XX XX "
प्रिंट करने पर यह Exercism मैट्रिक्स को अपेक्षा के मुताबिक रेंडर करेगा।
julia> print(render(E))
XX XX
X X
X X X X
X X X X X X
X X
X X
X X X X
X X X X
X XX X
X X
XX XX
यह उन "डॉट" के साथ भी काम करेगा, जो 1 से अलग हैं:
julia> print(render(colpixelcount(E)))
XX XX
X X
X X X X
X X X X X X
X X
X X
X X X X
X X X X
X XX X
X X
XX XX
Exercism पर साइन अप कीजिए और Julia को 35 कॉन्सेप्ट128 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।