तकनीकी रूप से, Higher Order Function वह फंक्शन होता है जो इनमें से कम से कम एक काम करता है:
फंक्शनल प्रोग्रामिंग की दुनिया में इसका इस्तेमाल आम तौर पर सीमित होता है।
यह शब्द आम तौर पर filter, map और reduce जैसे फंक्शनों के लिए इस्तेमाल होता है, जो दिए गए फंक्शन को कलेक्शन के एलिमेंट पर लागू करते हैं।
सिलेबस में अब तक हम किसी इटरेबल कलेक्शन, जैसे Vector, के सभी एलिमेंट पर कोई ऑपरेशन लागू करने के कई तरीके देख चुके हैं:
यह कॉन्सेप्ट हायर-ऑर्डर फंक्शन पर ध्यान केंद्रित करेगा (जो किसी भी फंक्शनल भाषा, जैसे Haskell या F#, से परिचित हैं)।
और भी तरीके हो सकते हैं:
filter() फंक्शन एक ऐसा फंक्शन लेता है जिसकी रिटर्न वैल्यू बूलियन होती है, और उसे पूरे कलेक्शन पर लागू करता है।
रिटर्न वैल्यू में सिर्फ वे एलिमेंट शामिल होते हैं जो true लौटाते हैं। यह रिटर्न वैल्यू इनपुट के समान मूल टाइप की ही होती है (नीचे देखिए)।
julia> filter(iseven, 1:6)
3-element Vector{Int64}:
2
4
6
# String is a collection of Chars, so String in -> String out
julia> filter(!isascii, "Hrōðgār")
"ōðā"
# tuple input -> tuple output
julia> filter(iseven, (1, 2, 3, 4, 5))
(2, 4)
बहु-आयामी ऐरे के साथ filter इनपुट के आयाम चपटा कर देता है और एक Vector लौटाता है: आउटपुट टाइप इनपुट टाइप से मेल खाए, इस नियम का यह सबसे बड़ा अपवाद है।
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> filter(isodd, m)
3-element Vector{Int64}:
1
5
3
ऊपर के उदाहरण बिल्ट-इन फंक्शन इस्तेमाल करते हैं, लेकिन ऐसे मौकों पर अनाम फंक्शन का इस्तेमाल बहुत आम है।
julia> filter(x -> x % 3 == 0, 1:20)
6-element Vector{Int64}:
3
6
9
12
15
18
इस कॉन्सेप्ट के कई फंक्शनों की तरह इसका भी एक इन-प्लेस वर्शन है, filter!()।
map() फंक्शन हर एलिमेंट पर एक फंक्शन लागू करके कलेक्शन को बदल देता है।
आसान मामलों में यह ब्रॉडकास्टिंग जैसा लग सकता है, जहाँ आउटपुट का आकार इनपुट से मेल खाता है।
julia> map(√, [1, 4, 9])
3-element Vector{Float64}:
1.0
2.0
3.0
julia> map(x -> x^2 + 1, 1:4)
4-element Vector{Int64}:
2
5
10
17
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> map(√, m)
2×3 Matrix{Float64}:
1.0 1.41421 1.73205
2.0 2.23607 2.44949
map() कई कलेक्शन पर एक-एक एलिमेंट के हिसाब से भी काम करता है।
julia> map(*, [1, 2], [3, 4])
2-element Vector{Int64}:
3
8
सोचने के तरीके से, इसे ऐसे समझ सकते हैं: पहले कई इनपुट कलेक्शन पर zip() चलता है, फिर बीच के परिणाम के हर एलिमेंट पर map() चलता है।
यह सिर्फ एक मोटी तुलना है, इससे इम्प्लीमेंटेशन के बारे में कुछ नहीं कहा जा रहा!
zip() की तरह, अलग-अलग आकार के कलेक्शन सबसे छोटे वाले के आयामों तक काट दिए जाते हैं।
कभी-कभी दिए गए फंक्शन के सिर्फ साइड इफेक्ट की ज़रूरत होती है, जैसे डेटाबेस में लिखना या ऐरे में push! करना।
ऐसे में हायर-ऑर्डर फंक्शन foreach() काम आता है, जो हमेशा nothing लौटाता है।
reduce() फंक्शन दो आर्गुमेंट वाला एक फंक्शन लेता है और उसे पूरे कलेक्शन पर लागू करता है, जिससे आयाम घट जाते हैं।
सुनने में यह उलझा हुआ लग सकता है, पर sum() या prod() जैसे फंक्शन सोचिए, जो एक कलेक्शन लेते हैं और एक ही वैल्यू लौटाते हैं।
julia> sum(1:4) # add
10
julia> prod(1:4) # multiply
24
ये खास फंक्शन बहुत अच्छी तरह ऑप्टिमाइज़ किए गए हैं, इसलिए जब भी उपलब्ध हों, इन्हीं का इस्तेमाल कीजिए।
और उदाहरण हैं maximum() और minimum(), तार्किक फंक्शन all() और any(), तथा कई सांख्यिकीय फंक्शन।
सिर्फ समझाने के लिए, उसी काम को ज़्यादा सामान्य reduce() से करने पर गौर कीजिए (याद रहे कि इन्फिक्स ऑपरेटर + और * अंदर से असल में फंक्शन ही हैं)।
julia> reduce(+, 1:4) # add
10
julia> reduce(*, 1:4) # multiply
24
sum() और दूसरे एग्रीगेशन फंक्शन की तरह reduce() भी एक वैकल्पिक कीवर्ड आर्गुमेंट dims ले सकता है, जिससे तय होता है कि कौन-से आयाम घटाने हैं।
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> reduce(+, m; dims=1)
1×3 Matrix{Int64}:
5 7 9
ये आसान उदाहरण हैं, क्योंकि जोड़ना और गुणा, दोनों क्रमविनिमेय (1+2 == 2+1) और साहचर्य ( (1+2)+3 == 1+(2+3) ) हैं।
यह हर जगह लागू नहीं होता! घटाना और भाग जैसे आम ऑपरेशन भी साहचर्य नहीं होते।
एक और दिक्कत यह है कि बड़े कलेक्शन में फ्लोटिंग-पॉइंट की त्रुटियाँ जमा हो सकती हैं, इसलिए बाएँ से दाएँ चलने वाला reduce दाएँ से बाएँ चलने वाले से थोड़ा अलग नतीजा दे सकता है।
Julia के reduce फंक्शन की दिशा इम्प्लीमेंटेशन पर निर्भर है और इसकी कोई गारंटी नहीं है।
दिशा को साफ-साफ तय करने के लिए foldl() और foldr() फंक्शन हैं, जो सैद्धांतिक रूप से क्रमशः "बाएँ" और "दाएँ" से शुरू होते हैं (Vector के मामले में असल में ऊपर और नीचे से)।
julia> foldl(-, 1:3) # (1 - 2) - 3
-4
julia> foldr(-, 1:3) # 1 - (2 - 3)
2
ध्यान रखिए, ये ऐसे कलेक्शन के लिए हैं जिन्हें एक-आयामी माना जा सकता है, और ये एक स्केलर परिणाम लौटाते हैं।
foldl और foldr में dims आर्गुमेंट का इस्तेमाल नहीं हो सकता, यह सिर्फ reduce में काम करता है।
कई प्रोग्रामिंग क्षेत्रों में map ऑपरेशन को reduce के साथ जोड़ना बहुत आम है।
हम पहले map चला सकते हैं, फिर बीच के कलेक्शन पर reduce चला सकते हैं।
लेकिन यह कम से कम कहें तो कम कुशल है, और कलेक्शन बड़ा होने पर इसका स्केल बहुत खराब हो जाता है।
इसकी जगह संयुक्त mapreduce() फंक्शन इस्तेमाल करने की पूरी सलाह दी जाती है।
यह कहीं ज़्यादा तेज़ एल्गोरिदम लागू कर सकता है, जो map और reduce के ऑपरेशन को एक साथ गूँथ देता है।
पहला आर्गुमेंट वह फंक्शन है जिससे मैप करना है, और दूसरा आर्गुमेंट reduce ऑपरेटर है।
julia> mapreduce(x -> x^2 + 1, +, 1:3)
17
# equivalent to (2 + 5 + 10)
julia> sum(map(x -> x^2 + 1, 1:3))
17
जैसा कि हम उम्मीद करेंगे, Julia में mapfoldl() और mapfoldr() फंक्शन भी हैं, ऐसी जगहों के लिए जहाँ दिशा मायने रखती है।
हम एक चीज़ क्लब शुरू कर रहे हैं। यह क्लब एमएल यानी मशीन लर्निंग की मदद से हमारे चीज़-प्रेमी ग्राहकों के इतिहास और पसंद के आधार पर उनके लिए नए चीज़ चुनेगा।
नए सदस्यों को शुरू में एक सर्वे भरना होता है, ताकि हम शुरू करने के लिए कुछ बुनियादी डेटा इकट्ठा कर सकें।
इससे पता चला है कि प्रबल ग्राहकों का एक ऐसा वर्ग है जिनकी समीक्षाओं में बारीकी नहीं होती।
चूँकि इससे एक अधिक बारीकी वाले एल्गोरिदम में हमेशा के लिए पूर्वाग्रह पैदा हो सकता है, इसलिए उनकी ज़रूरतों को संभालने के लिए एक अलग एल्गोरिदम बनाया गया है।
आपसे कहा गया है कि आप कुछ हेल्पर फंक्शन बनाइए जो उनके डेटा को सँभालें।
हालाँकि नीचे दिए गए कार्यों को हल करने के अलग-अलग तरीके हो सकते हैं, लेकिन हर कार्य को एक ही हायर ऑर्डर फंक्शन से हल किया जा सकता है, और हर कार्य का फंक्शन अलग होगा।
रेटिंग प्रणाली पाँच सितारों पर आधारित है, जो बस पूर्णांकों 1:5 से बनी है।
प्रबल ग्राहक केवल 1 या 5 की रेटिंग देते हैं, और हम जानना चाहते हैं कि कोई ग्राहक यह व्यवहार दिखाता है या नहीं।
all_15() लागू कीजिए। यह रेटिंग का एक वेक्टर लेता है और अगर सभी रेटिंग 1 या 5 हों तो true लौटाता है, वरना false.
julia> ratings = [2, 3, 4, 4, 1];
julia> all_15(ratings)
false
julia> ratings = [1, 5, 5, 1, 5];
julia> all_15(ratings)
true
हमें प्रबल ग्राहकों को बाकी सब से अलग करना है।
emphatics() लागू कीजिए। यह ग्राहकों और रेटिंग की एक डिक्शनरी लेता है।
यह वैसी ही एक डिक्शनरी लौटाता है, जिसमें केवल 1 या 5 सितारों की रेटिंग देने वाले ग्राहक होते हैं।
julia> ratings = ([2, 3, 5, 1, 1], [1, 1, 5, 5, 1], [4, 5, 5, 3, 2], [5, 5, 1, 1, 5]);
julia> names = ("c1", "c2", "c3", "c4");
julia> customers = Dict(zip(names, ratings))
Dict{String, Vector{Int64}} with 4 entries:
"c2" => [1, 1, 5, 5, 1]
"c1" => [2, 3, 5, 1, 1]
"c3" => [4, 5, 5, 3, 2]
"c4" => [5, 5, 1, 1, 5]
julia> emphatics(customers)
Dict{String, Vector{Int64}} with 2 entries:
"c2" => [1, 1, 5, 5, 1]
"c4" => [5, 5, 1, 1, 5]
चूँकि प्रबल ग्राहक केवल 1 और 5 की रेटिंग देते हैं, इसलिए इन्हें 0 और 1 में बदलना गणना के लिहाज से ज़्यादा सुविधाजनक होगा।
tobinary() लागू कीजिए। यह प्रबल रेटिंग का एक वेक्टर लेता है।
यह बाइनरी रेटिंग लौटाता है, जिसमें 1 को 0 में और 5 को 1 में बदला गया होता है।
julia> ratings = [1, 1, 5, 5, 1];
julia> tobinary(ratings)
5-element Vector{Int64}:
0
0
1
1
0
हमारे एल्गोरिदम Matrix इनपुट इस्तेमाल करते हैं, इसलिए हमें डेटा को एक मैट्रिक्स में बदलना होगा।
tobinarymatrix() लागू कीजिए। यह प्रबल रेटिंग वेक्टरों का एक वेक्टर लेता है।
यह बदले हुए डेटा की एक Matrix लौटाता है, जिसमें हर रेटिंग वेक्टर मैट्रिक्स की एक पंक्ति होती है।
julia> customersratings = [[1, 1, 5, 5, 1],[5, 5, 1, 1, 5]];
julia> tobinarymatrix(customersratings)
2×5 Matrix{Int64}:
0 0 1 1 0
1 1 0 0 1
Exercism पर साइन अप कीजिए और Julia को 35 कॉन्सेप्ट128 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।