Exercism पर अभ्यास छोटे, कृत्रिम और अक्सर देखने में मामूली लगते हैं। यह सोचना आसान है कि अनुभवी डेवलपरों के लिए इनसे सीखने को कुछ नहीं होगा। लेकिन इन कृत्रिम समस्याओं को हल करना आपको अपनी भाषा के उन हिस्सों को सीखने और लागू करने के लिए प्रेरित कर सकता है, जिन्हें आपने शायद पहले नहीं खोजा हो। यह नई सीख आपको वास्तविक दुनिया की समस्याओं को अधिक कुशलता से या अधिक अभिव्यक्तिपूर्ण ढंग से हल करने की ओर ले जा सकती है।
समानांतर अक्षर आवृत्ति, Exercism के Elixir ट्रैक पर एक मध्यम कठिनाई का अभ्यास है जो कई आश्चर्यजनक रूप से रोचक सबक सामने रखता है। इस अभ्यास को हल करने की एक मुख्य चुनौती कई भाषाओं के अक्षरों को संभालना है, क्योंकि टेस्ट केस में से एक जर्मन में है और उसमें अंग्रेज़ी वर्णमाला से बाहर के अक्षर हैं। अगर आप अपना ज़्यादातर समय अंग्रेज़ी बोलने वालों के लिए ऐप्लिकेशन बनाने में बिताते हैं, तो शायद यह पहली बार है कि आपको ऐसी आवश्यकता से निपटना पड़ा है। इस अभ्यास से मिली सीख का साफ़ फ़ायदा हर उस व्यक्ति को है जो बहुभाषी या गैर-अंग्रेज़ी ऐप्लिकेशन लिखता है, लेकिन यह कई अन्य क्षेत्रों में भी मदद कर सकती है, जैसे उपयोगकर्ता नाम और पासवर्ड की अधिक मज़बूत जाँच।
इस अभ्यास को सफलतापूर्वक हल करने के लिए आपको एक फंक्शन लागू करना है, Frequency.frequency/2, जो किसी भी भाषा में हो सकती स्ट्रिंग के ऐरे में अक्षरों की आवृत्ति निर्धारित करता है:
iex> Frequency.frequency(["Freude", "schöner", "Götterfunken"], workers)
%{
"c" => 1,
"d" => 1,
"e" => 5,
...
"ö" => 2
}
चलिए उस मूलभूत समस्या से शुरू करते हैं जिसे इस फंक्शन को हल करना है, और फिर पूरी इम्प्लीमेंटेशन तक पहुँचते हैं।
Elixir में यह पता कीजिए कि कोई अक्षर वर्णमाला का अक्षर है या नहीं
आप Elixir का उपयोग करके कैसे पता करेंगे कि "a" एक अक्षर है या नहीं?
मुझे लगता है कि ज़्यादातर लोग /[a-z]/ जैसा रेगुलर एक्सप्रेशन लगाएँगे:
iex> String.match?("a", ~r/^[a-z]$/)
true
"A" के बारे में क्या?
i (केस-रहित) मॉडिफायर जोड़ना शायद सबसे आसान तरीका होगा:
iex> String.match?("A", ~r/^[a-z]$/i)
true
ठीक है, अब "ö" के बारे में क्या?
जब मैंने पहली बार इस समस्या पर काम किया, तो मुझे सबसे अच्छा तरीका नहीं पता था; /[a-z]/i निश्चित रूप से काम नहीं करेगा:
iex> String.match?("ö", ~r/^[a-z]$/i)
false
यह पता करना कि "ö" एक अक्षर है या नहीं, इस Exercism समस्या को हल करने का एक मुख्य हिस्सा है, क्योंकि टेस्ट में एक टेक्स्ट जर्मन में है:
# parallel_letter_frequency_test.exs
...
# Poem by Friedrich Schiller. The corresponding music is the European Anthem.
@ode_an_die_freude """
Freude schöner Götterfunken
...
"""
हो सकता है आप यह जाँचने के लिए रेगुलर एक्सप्रेशन इस्तेमाल कर सकें कि कोई अक्षर विशेष अक्षर नहीं है, लेकिन यह लंबा, भद्दा और नाज़ुक होने की संभावना है। आप कितने निश्चित हो सकते हैं कि आपने हर संभावित विशेष अक्षर को शामिल कर लिया है जो आपके फंक्शन को इनपुट के रूप में दिया जा सकता है? मेरा मानना है कि इसका एक बेहतर तरीका है।
Elixir में Unicode रेगुलर एक्सप्रेशन
इस समस्या का एक बेहतर तरीका है Elixir के Regex मॉड्यूल में u मॉडिफायर का उपयोग करना:
Unicode (
u) ऐसे Unicode-विशिष्ट पैटर्न सक्षम करता है जैसे\p, और\w,\W,\sजैसे मॉडिफायर को भी Unicode पर मैच करने के लिए बदल देता है।
पता चलता है कि u मॉडिफायर, और खास तौर पर \p पैटर्न, वाकई एक बहुत ही शानदार हल है। \p पैटर्न आपको किसी भी Unicode अक्षर श्रेणी में एक ग्राफीम (एक ही Unicode अक्षर का दूसरा नाम) मैच करने देता है। इसमें Ll (अक्षर, लोअरकेस) और Sc (प्रतीक, मुद्रा) जैसी विशिष्ट श्रेणियाँ ही नहीं, बल्कि L (अक्षर) और S (प्रतीक) जैसी मूल श्रेणियाँ भी शामिल हैं।
आप \p{L} पैटर्न से किसी भी Unicode में शामिल मानव भाषा के किसी भी केस के किसी भी अक्षर को मैच कर सकते हैं। यह कुछ बहुत शक्तिशाली मैचिंग की सुविधा देता है।
अंग्रेज़ी के बेसिक Latin अक्षर हमेशा की तरह काम करते हैं:
iex> String.match?("a", ~r/^\p{L}$/u)
true
iex> String.match?("A", ~r/^\p{L}$/u)
true
उमलाउट और एक्यूट एक्सेंट वाले Latin अक्षर वेरिएंट भी कोई समस्या नहीं हैं:
iex> String.match?("ö", ~r/^\p{L}$/u)
true
iex> String.match?("Á", ~r/^\p{L}$/u)
true
चलिए सुनिश्चित करते हैं कि यह किसी भी अक्षर के लिए मैच नहीं लौटा रहा है। कुछ ऐसे अक्षरों के बारे में क्या जो अक्षरों जैसे दिखते हैं लेकिन हैं नहीं:
iex> String.match?("$", ~r/^\p{L}$/u)
false
iex> String.match?("@", ~r/^\p{L}$/u)
false
बहुत बढ़िया, लेकिन याद है मैंने कहा था किसी भी भाषा? कोई दिक्कत नहीं:
# Chinese character for "you"
iex> String.match?("你", ~r/^\p{L}$/u)
true
# Cyrillic capital letter "zhe"
iex> String.match?("Ж", ~r/^\p{L}$/u)
true
इस समस्या पर Unicode मैचिंग लागू करना
अब जब हमारे पास एक ऐसा टूल है जो यह पता लगाने में मदद कर सकता है कि कोई ग्राफीम अक्षर है या नहीं, तो हम इसे समस्या हल करने के लिए लागू कर सकते हैं। Frequency.frequency/2 फंक्शन का शुरुआती इम्प्लीमेंटेशन कुछ ऐसा दिख सकता है:
def frequency(texts, _workers) do
texts
|> get_all_graphemes()
|> count_letters()
end
defp get_all_graphemes(texts) do
texts
|> Enum.join()
|> String.graphemes()
end
count_letters/1 को बस ऊपर पहचाने गए String.match?(grapheme, ~r/^\p{L}$/u) पैटर्न को लगाकर graphemes के ऐरे में हर अक्षर की गिनती बढ़ानी है। यहाँ इस Exercism समस्या के मेरे हल से लिया गया एक उदाहरण इम्प्लीमेंटेशन है:
defp count_letters(graphemes) do
Enum.reduce(graphemes, %{}, fn grapheme, acc ->
if String.match?(grapheme, ~r/^\p{L}$/u) do
downcased_letter = String.downcase(grapheme)
Map.update(acc, downcased_letter, 1, fn count -> count + 1 end)
else
acc
end
end)
end
यह फंक्शन ग्राफीमों का एक ऐरे लेता है, जैसे ["a", "A", "ö", "$"], और एक मैप लौटाता है जो केस को नज़रअंदाज़ करके सिर्फ अक्षरों की गिनती करता है, जैसे %{"a" => 2, "ö" => 1}। यह देखते हुए कि यह फंक्शन किसी भी भाषा से इनपुट संभाल सकता है, मैं कहूँगा कि यह 9 लाइनों का काफी शक्तिशाली कोड है।
निष्कर्ष
पता चलता है कि जब आप Unicode मैचिंग के बारे में जानते हैं, तो गैर-अंग्रेज़ी अक्षरों को मैच करना काफी आसान हो जाता है, और सौभाग्य से यह Elixir के Regex मॉड्यूल की एक मुख्य विशेषता है। इस Exercism समस्या को हल करने से पहले मैं इस फीचर के बारे में बहुत कम जानता था, लेकिन अब मैं इसे अपने Elixir टूलबॉक्स का एक अनिवार्य हिस्सा मानता हूँ।
आप इस नए टूल का उपयोग कई तरह से कर सकते हैं, और मेरे दिमाग में जो कुछ तरीके आते हैं वे हैं पासवर्ड और उपयोगकर्ता नामों की अधिक मज़बूत जाँच, या यह पता लगाना कि कोई इनपुट स्ट्रिंग मान्य मुद्रा स्ट्रिंग है या नहीं, बिना सभी संभावित मुद्रा प्रतीकों को हाथ से सूचीबद्ध किए:
iex> currency_string_regex = ~r/\p{Sc}\d+\.\d{2}/u
~r/\p{Sc}\d+\.\d{2}/u
iex> ["$1.00", "£1.00", "¥1.00", "€1.00", "&1.00"] \
...> |> Enum.filter(&String.match?(&1, currency_string_regex))
["$1.00", "£1.00", "¥1.00", "€1.00"]