Julia-তে একটি String হলো নির্দিষ্ট ক্রমে সাজানো ক্যারেক্টারের একটি সিকোয়েন্স। ম্যানুয়ালে যেমন বলা হয়েছে: "অবশ্যই, আসল ঝামেলা শুরু হয় যখন কেউ জিজ্ঞেস করে ক্যারেক্টার আসলে কী।"
এই জটিলতার অনেকটাই আরও বিস্তারিতভাবে আলোচনা করা হবে Chars ধারণায়, তবে সংক্ষেপে:
সাধারণভাবে স্ট্রিং দুটি ডাবল-কোট " " এর ভেতরে রাখা হয়।
একক কোট ' ' শুধু Chars-এর জন্য ব্যবহৃত হয়।
তিনটি ডাবল-কোট """ """-ও ব্যবহার করা যায়, যার ফলে স্ট্রিংয়ের ভেতরে " ব্যবহার করতে এস্কেপ করার দরকার পড়ে না।
আরও গুরুত্বপূর্ণ হলো, মাল্টিলাইন স্ট্রিং থেকে অবাঞ্ছিত অতিরিক্ত ইন্ডেন্টেশন বাদ দেওয়া হয়।
julia> """
Multiline
"""
"Multiline\nString\n"
C থেকে শুরু করে পরের বেশিরভাগ ভাষার মতো Julia-ও এস্কেপ করার জন্য ব্যাকস্ল্যাশ \ ব্যবহার করে:
\n।\$ (নিচে দেখুন)।নীতিগতভাবে স্ট্রিং-কে ভেক্টরের মতোই ইনডেক্স করা যায়:
julia> s[1], s[5]
('J', 'a')
এটা "Julia"-র মতো একটি ASCII স্ট্রিং-এর ক্ষেত্রে পরিষ্কারভাবে কাজ করে, কিন্তু তাতে বিশ্বের বেশিরভাগ ভাষা বাদ পড়ে যায়।
প্রায় ১৫ শতাব্দী আগে প্রাচীন ইংরেজিতে লেখা Beowulf-এর একটি ক্যারেক্টার ভেবে দেখুন।
"Hrōðgār" স্পষ্টতই ৭টি ক্যারেক্টার, কিন্তু এদের সবগুলো ASCII নয়।
julia> king = "Hrōðgār"
"Hrōðgār"
julia> length(king)
7
julia> isascii(king)
false
# collect() converts to a Char vector:
julia> collect(king)
7-element Vector{Char}:
'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)
'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
'ō': Unicode U+014D (category Ll: Letter, lowercase)
'ð': Unicode U+00F0 (category Ll: Letter, lowercase)
'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)
'ā': Unicode U+0101 (category Ll: Letter, lowercase)
'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
এভাবে একটি স্ট্রিং-এ ইনডেক্স করা শুরুতে ভালোই চলে, কিন্তু একপর্যায়ে ভেঙে পড়ে:
julia> king[3]
'ō': Unicode U+014D (category Ll: Letter, lowercase)
julia> king[4]
ERROR: StringIndexError: invalid index [4], valid nearby indices [3]=>'ō', [5]=>'ð'
সমস্যা হলো, এভাবে ইনডেক্স করলে বাইট গোনা হয়, আর নন-ASCII স্ট্রিং-এর ক্ষেত্রে বাইটের সাথে ক্যারেক্টারের সম্পর্ক ১:১ নয়।
একটি Unicode/UTF-8 ক্যারেক্টার প্রকাশ করতে ৪টি বাইট পর্যন্ত লাগতে পারে, আর Chars ধারণায় এটা আরও ভালোভাবে দেখা হবে।
অনেক ভাষায় স্ট্রিং কনক্যাটেনেশনের অপারেটর হিসেবে + ব্যবহার করা হয়, কিন্তু Julia-তে নয়।
অন্তত এরর মেসেজটা সহায়ক।
julia> "s1 " + "s2"
ERROR: MethodError: no method matching +(::String, ::String)
The function `+` exists, but no method is defined for this combination of argument types.
String concatenation is performed with *
julia> "s1 " * "s2"
"s1 s2"
string() ফাংশন যত খুশি তত স্ট্রিং কনক্যাটেনেট করতে পারে।
julia> string("lots ", "of ", "bits")
"lots of bits"
join() ফাংশনও একই কাজ করে, আর এটি স্ট্রিং-এর একটি ভেক্টর নিয়েও সেগুলোকে একটি নির্দিষ্ট সেপারেটর দিয়ে কনক্যাটেনেট করতে পারে (ডিফল্ট সেপারেটর "")।
julia> join(["lots", "of", "bits"], "_")
"lots_of_bits"
মনে রাখবেন, স্ট্রিং অপরিবর্তনযোগ্য, আর কনক্যাটেনেশনে কপি করা জড়িত। তাই একটি লুপের ভেতরে বারবার কনক্যাটেনেশন করা অদক্ষ; বরং খণ্ডগুলো একটি ভেক্টরে জমিয়ে শেষে সবগুলোকে একসাথে join করা ভালো।
সহজ:
julia> repeat("Abc", 5)
"AbcAbcAbcAbcAbc"
অনেক ভাষাতেই স্ট্রিং-এর ভেতরে হিসাব করা মান বসানোর একটি উপায় আছে, যদিও কোন সিনট্যাক্সটা সবচেয়ে ভালো তা নিয়ে দারুণ মতভেদ রয়েছে।
Julia-তে ইন্টারপোলেট করা মানের আগে $ বসে।
julia> myvar = 42
42
julia> "myvar evaluates to $myvar"
"myvar evaluates to 42"
julia> r = 5.3
5.3
julia> "A circle of radius $r has area $(π * r^2)"
"A circle of radius 5.3 has area 88.24733763933729"
বাদ দিলে অস্পষ্টতা তৈরি হতে পারে এমন ক্ষেত্রে বন্ধনী অবশ্যই দিতে হয়, তবে শুধু একটি আইডেন্টিফায়ার আর তার পরে হোয়াইটস্পেস থাকলে বন্ধনী না দিলেও চলে।
স্ট্রিং-এর ভেতরে একটি $ রাখতে চাইলে এটিকে \$ হিসেবে এস্কেপ করুন।
লক্ষ্য করুন, এখানে একটি সীমাবদ্ধতা আছে: ইন্টারপোলেশনে এখনও ফরম্যাটিং করা যায় না, যেমন কত দশমিক স্থান দেখানো হবে তা ঠিক করা।
@sprintf()স্ট্রিং সাজানোর উপর আরও সূক্ষ্ম নিয়ন্ত্রণের জন্য Julia, C থেকে sprintf ফাংশনটি নিয়েছে (এবং পরের আরও কয়েকটি ভাষাও নিয়েছে: উইকিপিডিয়ায় প্রায় ৩০টির তালিকা আছে)।
Julia-তে এটি Printf মডিউলের ভেতরে একটি macro হিসেবে বাস্তবায়িত, তাই @ প্রিফিক্স।
julia> using Printf
julia> r = 5.3
5.3
julia> @sprintf("A circle of radius %.1f has area %.2f", r, π * r^2)
"A circle of radius 5.3 has area 88.25"
সহজ রাখতে, এই অংশে আমরা এমন ফাংশন নিয়ে আলোচনা করব যেগুলো একটি নতুন স্ট্রিং রিটার্ন করে আর ইনপুট অপরিবর্তিত রাখে।
এগুলোর অনেকগুলোরই একটি সমতুল্য রূপ আছে, যার নামের শেষে ! থাকে, আর সেগুলো ইনপুটকে জায়গাতেই পরিবর্তন করে।
একটি স্ট্রিং কত লম্বা? কখনও কখনও তা নির্ভর করে আপনি কীভাবে গুনছেন তার উপর।
সহজ ক্ষেত্রটি হলো ASCII স্ট্রিং, যেখানে প্রতিটি ক্যারেক্টার ১ বাইট জায়গা নেয়।
julia> string_asc = "Julia"
"Julia"
julia> length(string_asc)
5
julia> sizeof(string_asc)
5
length() ফাংশন ক্যারেক্টারের সংখ্যা রিটার্ন করে, আর sizeof() ফাংশন বাইটের সংখ্যা।
অন্য ক্যারেক্টার সেটের ক্ষেত্রে এই পার্থক্যটা গুরুত্বপূর্ণ হয়ে ওঠে:
julia> king = "Hrōðgār"
"Hrōðgār"
julia> length(king)
7
julia> sizeof(king)
10
প্রোগ্রামিংয়ে একটি কমন কাজ হলো একটি লম্বা স্ট্রিং নিয়ে এক বা একাধিক ক্যারেক্টারের একটি সেপারেটর স্ট্রিং-এর ভিত্তিতে সেটিকে টুকরো টুকরো ভাগ করা।
সাধারণভাবে আমরা split() ফাংশন ব্যবহার করি।
ডিফল্ট সেপারেটর হলো (যেকোনো) হোয়াইটস্পেস, তবে অন্য সেপারেটরও উল্লেখ করা যায়।
julia> split("my little string")
3-element Vector{SubString{String}}:
"my"
"little"
"string"
julia> split("My_snake_case_string", "_")
4-element Vector{SubString{String}}:
"My"
"snake"
"case"
"string"
এই উদাহরণগুলো একটি স্ট্রিং-কে ছোট ছোট স্ট্রিং-এ ভাগ করে। স্ট্রিং-কে ক্যারেক্টারে ভাগ করা একটি আলাদা কাজ।
একটি স্ট্রিং-এ নির্দিষ্ট কোনো সাবস্ট্রিং আছে কি?
অদ্ভুতভাবে, এটা পরীক্ষা করার জন্য দুটি ফাংশন আছে: occursin() আর contains() শুধু তাদের আর্গুমেন্টের ক্রমে আলাদা।
julia> needle = "Julia"
"Julia"
julia> haystack = "Python, Julia and R"
"Python, Julia and R"
julia> occursin(needle, haystack)
true
julia> contains(haystack, needle)
true
অবস্থান সম্পর্কে আরও নির্দিষ্ট হতে চাইলে:
julia> startswith(haystack, "Python")
true
julia> endswith(haystack, "Python")
false
একটি সাবস্ট্রিং-এর শুরুর ও শেষ ইনডেক্স পেতে আমাদের হাতে কয়েকটি ফাংশন আছে:
findfirst()findlast()findprev()findnext()julia> findfirst(needle, haystack)
9:13
সাবস্ট্রিং প্রতিস্থাপন করতে x => y সিনট্যাক্স ব্যবহার করে পরিবর্তনগুলোর তালিকা দিন।
julia> replace("abc", "a"=>"b", "b"=>"c", "c"=>"a")
"bca"
Julia 1.7 থেকে replace ফাংশন যত খুশি তত পরিবর্তন নিতে পারে, আর এতে নিশ্চয়তা থাকে যে প্রতিস্থাপনের সময় কোনো ক্যারেক্টার একবারের বেশি পরিবর্তিত হবে না।
x => y সিনট্যাক্সকে বলা হয় pair, যা Julia-র একটি গুরুত্বপূর্ণ টাইপ, আর এটা নিয়ে আরও বিস্তারিত আলোচনা আছে Dicts and Pairs ধারণায়।
আমাদের প্রায়ই একটি স্ট্রিং-এর শুরু এবং/অথবা শেষ থেকে হোয়াইটস্পেস সরানোর দরকার হয়।
এ কাজের ফাংশনগুলো হলো lstrip() (বাম দিক), rstrip() (ডান দিক), strip() (দুই দিক)।
julia> strip("\n\t useful_bit\n\n")
"useful_bit"
এই ফাংশনগুলোর নাম শুনেই কাজ বোঝা যায়:
julia> s = repeat("aBcD ", 5)
"aBcD aBcD aBcD aBcD aBcD "
julia> uppercase(s)
"ABCD ABCD ABCD ABCD ABCD "
julia> lowercase(s)
"abcd abcd abcd abcd abcd "
julia> titlecase(s) # Initial letter of each word uppercase
"Abcd Abcd Abcd Abcd Abcd "
এই অনুশীলনীতে আপনি লগ-লাইন প্রসেস করবেন।
প্রতিটি লগ-লাইন একটি স্ট্রিং, যার ফরম্যাট এরকম: "[<LEVEL>]: <MESSAGE>"।
তিনটি ভিন্ন লগ লেভেল আছে:
INFOWARNINGERRORআপনার সামনে তিনটি কাজ আছে, প্রতিটিতে একটি লগ-লাইন নিয়ে সেটি দিয়ে কিছু একটা করতে বলা হবে।
লগ-লাইনের মেসেজ রিটার্ন করার জন্য message ফাংশনটি ইমপ্লিমেন্ট করুন:
julia> message("[ERROR]: Invalid operation")
"Invalid operation"
শুরুতে বা শেষে থাকা যেকোনো হোয়াইট স্পেস সরিয়ে ফেলতে হবে:
julia> message("[WARNING]: Disk almost full\r\n")
"Disk almost full"
লগ-লাইনের লগ লেভেল রিটার্ন করার জন্য log_level ফাংশনটি ইমপ্লিমেন্ট করুন, যেটি ছোট হাতের অক্ষরে রিটার্ন করতে হবে:
julia> log_level("[ERROR]: Invalid operation")
"error"
লগ-লাইনটি নতুন করে সাজানোর জন্য reformat ফাংশনটি ইমপ্লিমেন্ট করুন, যেখানে মেসেজটি সবার আগে থাকবে আর তার পরে বন্ধনীর ভেতরে লগ লেভেলটি:
julia> reformat("[INFO]: Operation completed")
"Operation completed (info)"
নোট: এই অনুশীলনীর সব স্ট্রিং ইংরেজিতে আছে এবং ASCII ক্যারেক্টার সেটের মধ্যে সীমাবদ্ধ। পরবর্তী কনসেপ্টগুলোতে ইউনিকোড ক্যারেক্টার নিয়ে কাজ করার সুযোগ পাবেন।
Exercism-এ সাইন আপ করুন, Julia ট্র্যাকের 35টি কনসেপ্ট128টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।