Uploaded avatar of sdball

cal দিয়ে অধিবর্ষ খোঁজা

@sdball
2 বছরের বেশি আগে

এই পোস্টটি প্রথমে Stephen-এর ওয়েবসাইটে প্রকাশিত হয়েছিল এবং অনুমতি নিয়ে (সামান্য সম্পাদনা করে) এখানে পুনঃপ্রকাশ করা হলো

cal পরিচিতি

আপনি কি জানেন, macOS আর Linux-এর কমান্ড লাইনে একটি ক্যালেন্ডার আছে?

$ cal

     July 2021
Su Mo Tu We Th Fr Sa
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

tldr দিয়ে একবার দেখলেই বোঝা যায়, এই ইউটিলিটি কত কাজের

$ tldr cal

cal

Prints calendar information.

- Display a calendar for the current month:
    cal

- Display previous, current and next month:
    cal -3

- Display a calendar for a specific month (1-12 or name):
    cal -m month

- Display a calendar for the current year:
    cal -y

- Display a calendar for a specific year (4 digits):
    cal year

- Display a calendar for a specific month and year:
    cal month year

- Display date of Easter (Western Christian churches) in a given year:
    ncal -e year

কিন্তু একটা জিনিস এর নেই। লিপ ইয়ার!

অবশ্যই cal লিপ ইয়ার সম্পর্কে জানে, নইলে তো আর ক্যালেন্ডারই হয় না। কিন্তু সেগুলো তালিকা করে দেখানোর কোনো উপায় এর নেই।

হ্যাকের ভঙ্গিতে লিপ ইয়ার চেনা

চলুন নিজেরাই এটা বানাই! নিশ্চয়ই ক্যালেন্ডার লাইব্রেরিসহ একটা সত্যিকারের প্রোগ্রামিং ভাষা ব্যবহার করতে পারতাম, কিন্তু কমান্ড লাইনের অংশগুলো জোড়া লাগিয়ে জিনিস বানানোটা মজার, আর এই হিসাবটা তো আমরা প্রোডাকশনে দিতে যাচ্ছি না।

প্রথমেই ধরে নিই, ফেব্রুয়ারিই লিপ ইয়ার হওয়ার সংকেত।

$ cal 02 2021

   February 2021
Su Mo Tu We Th Fr Sa
    1  2  3  4  5  6
 7  8  9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28

$ cal 02 2020

   February 2020
Su Mo Tu We Th Fr Sa
                   1
 2  3  4  5  6  7  8
 9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29

"29" খুঁজে কি সেটা দিয়ে লিপ ইয়ার চেনা যাবে? হয়তো!

$ cal 02 2020 | grep -q 29 && echo "LEAP" || echo "normal"

LEAP

$ cal 02 2021 | grep -q 29 && echo "LEAP" || echo "normal"

normal

এত পর্যন্ত তো ঠিকই আছে? কিন্তু সমস্যাটা নিশ্চয়ই চোখে পড়েছে।

$ for year in {2020..2030}; do
  printf "$year ";
  cal 02 "$year" | grep -q 29 && echo "LEAP" || echo "year";
done

2020 LEAP
2021 year
2022 year
2023 year
2024 LEAP
2025 year
2026 year
2027 year
2028 LEAP
2029 LEAP
2030 year

2029 কোনোভাবেই লিপ ইয়ার নয়। এটা মিলছে কেবল 2029-এর ভেতরের 29-এর কারণে।

$ cal 02 2029

   February 2029
Su Mo Tu We Th Fr Sa
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28

ফেব্রুয়ারি লেখা লাইনটা বাদ দিলেই আমরা ওই ঝামেলার ফলাফলটা সরিয়ে ফেলতে পারি।

$ cal 02 2029 | grep -v Feb

Su Mo Tu We Th Fr Sa
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28

এখন আমার ধারণা, ফলাফলে যদি "29" পাওয়া যায়, তাহলে সেটা সত্যিই লিপ ইয়ার।

$ for year in {2020..2030}; do
  printf "$year ";
  cal 02 "$year" | grep -v Feb | grep -q 29 && echo "LEAP" || echo "year";
done

2020 LEAP
2021 year
2022 year
2023 year
2024 LEAP
2025 year
2026 year
2027 year
2028 LEAP
2029 year

এটা কি আসল লিপ ইয়ারগুলোর সাথে মিলে?

একবিংশ শতাব্দীর প্রথম ভাগের লিপ ইয়ারগুলোর তালিকা আছে Wolfram Research-এর লিপ ইয়ার পেজে।

2000
2004
2008
2012
2016
2020
2024
2028
2032
2036
2040
2044
2048

চলুন দেখি আমরা কেমন করি

$ for year in {2000..2051}; do cal 02 "$year" | grep -v Feb | grep -q 29 && echo $year; done
2000
2004
2008
2012
2016
2020
2024
2028
2032
2036
2040
2044
2048

হয়ে গেল!

কিন্তু 29 নিয়ে grep করা খুব সহজ ব্যাপার। চলুন আরও অদ্ভুত কিছু করি!

আরও বেশি হ্যাকের ভঙ্গিতে লিপ ইয়ার চেনা

লিপ ইয়ার আর সাধারণ বছরের তফাতটা হলো ২৯ তারিখের ওই বাড়তি দিনটা। cal-এর আউটপুটে ক্যারেক্টারের সংখ্যা দেখে কি আমরা লিপ ইয়ার চিনতে পারি?

হ্যাঁ, পারি। কিন্তু আগে একটা ফাঁদ পেরোতে হবে। কী বোঝাচ্ছি, দেখাচ্ছি।

আমরা জানি 2020 লিপ ইয়ার আর 2021 লিপ ইয়ার নয়। এই দুই বছরের ফেব্রুয়ারির আউটপুটে ক্যারেক্টারের সংখ্যার তফাতটা দেখে নিই। কাজটার জন্য কাজের মতো wc ইউটিলিটি ব্যবহার করব, বিশেষ করে বাইট ক্যারেক্টার গোনার জন্য wc -c।

$ printf "hello" | wc -c

5

"hello"-তে ৫টি ক্যারেক্টার (echo-র বদলে printf ব্যবহার করেছি, নইলে একটা নিউলাইন ক্যারেক্টারও যোগ হতো)। দারুণ!

$ cal 02 2020 | wc -c

184
$ cal 02 2021 | wc -c

184

এই তো সেই বাধা। প্রতি মাসের আউটপুট আলাদা (2020-এ ওই ২৯ তারিখটা আছে), কিন্তু দুটোতেই ক্যারেক্টারের সংখ্যা একই, কারণ cal একই রকম ফরম্যাট রাখতে স্পেস যোগ করে। স্পেসও সংখ্যার মতোই গোনার মতো ক্যারেক্টার।

তাহলে কী করা যায়?

আমরা সব স্পেস মুছে ফেলতে পারি! তাহলে শুধু যেসব ক্যারেক্টার গুনতে চাই, সেগুলোই থাকবে।

ক্যারেক্টার মুছে ফেলার জন্য দারুণ একটা ইউটিলিটি হলো tr কমান্ড। tr কমান্ড স্ট্রিং ডেটা রূপান্তর করে। যেমন, এটা সব a ক্যারেক্টারকে A ক্যারেক্টারে বদলে দিতে পারে।

$ echo "aardvark" | tr a A

AArdvArk

এটা নির্দিষ্ট করা সব ক্যারেক্টার মুছেও ফেলতে পারে, আর আমরা ঠিক সেটাই চাই।

আমাদের ক্ষেত্রে সব হোয়াইটস্পেস ক্যারেক্টার মুছতে চাই, সেগুলো যা-ই হোক (স্পেস, ট্যাব, নিউলাইন ইত্যাদি)। ক্যারেক্টার ক্লাস নামে একটা কাজের গ্রুপিং আছে, যা দিয়ে ঠিক সেটাই করা যায়। [:space:] ক্যারেক্টার ক্লাস ওই সব রকমের হোয়াইটস্পেস ক্যারেক্টারকে টার্গেট করে।

$ cal 02 2020 | tr -d '[:space:]'

February2020SuMoTuWeThFrSa1234567891011121314151617181920212223242526272829
$ cal 02 2021 | tr -d '[:space:]'

February2021SuMoTuWeThFrSa12345678910111213141516171819202122232425262728

দারুণ! এখন ক্যারেক্টারের সংখ্যা গুনে কি লিপ ইয়ার বের করতে পারি? অবশ্যই পারি!

$ cal 02 2020 | tr -d '[:space:]' | wc -c

75
$ cal 02 2021 | tr -d '[:space:]' | wc -c

73

এখন চলুন কিছু লিপ ইয়ার আউটপুট করি!

$ for year in {2000..2051}; do
  cal 02 "$year" |
    tr -d '[:space:]' |
    wc -c |
    grep -q 75 && echo "$year";
done

2000
2004
2008
2012
2016
2020
2024
2028
2032
2036
2040
2044
2048

হ্যাকের পর হ্যাক, আর এটা কাজ করে!

আপনার মতে কোনটা দ্রুত? হ্যাঁ, আমারও কোনো ধারণা নেই। "ফেব্রুয়ারি বাদ দেওয়ার" পথটা হয়তো একটু দ্রুত? কিন্তু বিশাল কোনো তফাত থাকলে অবাক হব।

চলুন অবাক না হই। চলুন খুঁজে দেখি!

hyperfine নামে দারুণ একটা ইউটিলিটি আছে, যা দিয়ে একাধিক কমান্ড লাইন কমান্ডের পারফরম্যান্স মাপা যায়।

কোনটা দ্রুত? Hyperfine বলে দেবে

দুইটা কমান্ডের সাথে hyperfine-কে যে ফ্ল্যাগগুলো দিচ্ছি, সেগুলো এই:

--style basic
    Plain output styling
--export-markdown hyperfine.md
    Export results as markdown
--warmup 5
     Do five runs before benchmarking
--ignore-failure
     Ignore non-zero exits

আমাদের আগ্রহ শুধু একটা বছর হিসাব করার বেঞ্চমার্কে, বছরগুলোর লুপ বানাতে যত সময় লাগে তাতে নয়। তাই প্রতি পদ্ধতিতেই একটা নির্দিষ্ট বছরের লিপ ইয়ার যাচাইটা কেবল বেঞ্চমার্ক করব।

$ hyperfine --style basic --export-markdown hyperfine.md --warmup 5 --ignore-failure "cal 02 2050 | grep -v Feb | grep -q 29" "cal 02 2050 | tr -d '[:space:]' | wc -c | grep -q 75"

Benchmark #1: cal 02 2050 | grep -v Feb | grep -q 29
  Time (mean ± σ):       1.8 ms ±   0.3 ms    [User: 0.7 ms, System: 1.9 ms]
  Range (min … max):     1.0 ms …   3.6 ms    493 runs

  Warning: Command took less than 5 ms to complete. Results might be inaccurate.
  Warning: Ignoring non-zero exit code.

Benchmark #2: cal 02 2050 | tr -d '[:space:]' | wc -c | grep -q 75
  Time (mean ± σ):       1.9 ms ±   0.4 ms    [User: 0.9 ms, System: 2.7 ms]
  Range (min … max):     0.7 ms …   5.9 ms    811 runs

  Warning: Command took less than 5 ms to complete. Results might be inaccurate.
  Warning: Ignoring non-zero exit code.

Summary
  'cal 02 2050 | grep -v Feb | grep -q 29' ran
    1.07 ± 0.28 times faster than 'cal 02 2050 | tr -d '[:space:]' | wc -c | grep -q 75'
কমান্ড গড় [ms] সর্বনিম্ন [ms] সর্বোচ্চ [ms] আপেক্ষিক
cal 02 2050 | grep -v Feb | grep -q 29 1.8 ± 0.3 1.0 3.6 1.00
cal 02 2050 | tr -d '[:space:]' | wc -c | grep -q 75 1.9 ± 0.4 0.7 5.9 1.07 ± 0.28

যেমনটা আশা করেছিলাম, ওয়াল ক্লক টাইমের হিসাবে দুটোর মধ্যে কার্যত কোনো তফাত নেই, তবে "ফেব্রুয়ারি বাদ দেওয়ার" পথটা হয়তো একটু দ্রুত।

এই নিয়ে কমান্ড লাইন টুল জোড়া লাগানোর যাত্রা শেষ হলো।

শেষ কথা

  • আগে থেকে থাকা টুলগুলোকে একটি ডেটা পাইপলাইনে জোড়া লাগিয়ে আপনি নতুন কমান্ড লাইন ফিচার বানাতে পারেন
  • সামান্য সৃজনশীলতা দিয়ে সাধারণ সাধারণ অংশ থেকেই অবাক করার মতো জটিল ফলাফল পাওয়া যায়
  • কমান্ড লাইন কমান্ডের বেঞ্চমার্ক করার জন্য Hyperfine একটা কাজের টুল

পরের ধাপ

  • cal কমান্ড ব্যবহার করে ঘুরে দেখুন।
  • একটা পুরো বছরের সব নন-স্পেস ক্যারেক্টার গোনে এমন একটা পাইপলাইন বানিয়ে দেখুন: cal 2020
  • 1750 থেকে 1759 পর্যন্ত প্রতিটি বছরের জন্য ওই পুরো বছর গোনার পাইপলাইনটা চালিয়ে দেখুন: কিছু অদ্ভুত লাগছে?
Translation missing: bn.number.nth.ordinalized Jan 2024 · কাজে লেগেছে?