Uploaded avatar of sdball

Szökőévek keresése a cal segítségével

@sdball
Több, mint 2 éve ezelőtt

Ez a bejegyzés eredetileg Stephen weboldalán jelent meg, és itt újra megjelenik (kissé átírva), engedéllyel

Bevezetés a cal használatába

Tudtad, hogy a macOS és a Linux parancssorában van naptár?

$ 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

Egy gyors pillantás a tldr-be megmutatja, milyen hasznos lehet ez a segédprogram

$ 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

De van egy dolog, ami hiányzik belőle. A szökőévek!

A cal persze tud a szökőévekről, különben nem lenne valami jó naptár. Arra viszont nincs módja, hogy kilistázza őket.

Szökőévek felismerése hekkelős módon

Építsük meg mi magunk! Persze használhatnánk egy rendes programozási nyelvet naptárkönyvtárral, de szórakoztató dolgokat építeni parancssori összetevőkből, és úgysem fogjuk éles környezetben használni ezt a számítást.

Először is egyezzünk meg abban, hogy a február jelzi a szökőévet.

$ 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

Megkereshetnénk a „29”-et, és abból felismerné a szökőéveket? Talán!

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

LEAP

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

normal

Eddig jó? De fogadok, hogy látod a problémát.

$ 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

A 2029 NEM szökőév. Csak azért illeszkedik, mert a 2029-ben szerepel a 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

Ezt a kellemetlen találatot úgy szüntethetnénk meg, hogy a "February" szót tartalmazó egész sort kivesszük a vizsgálatból.

$ 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

Most azt állítom, hogy ha találunk egy „29”-et az eredményben, akkor az TÉNYLEG szökőév.

$ 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

Ez megegyezik a tényleges szökőévekkel?

A Wolfram Research Leap Year oldalán ez a lista szerepel a 21. század első felének szökőéveiként.

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

Lássuk, hogy teljesítünk

$ 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

Kész!

De ez a grep-elés a 29-re túl egyszerű. Legyünk még furcsábbak!

Szökőévek felismerése, még hekkelősebb módon

A szökőév és a normál év között a különbség az a plusz nap, a 29-e. Felismerhetnénk a szökőéveket a cal kimenetében lévő karakterek megszámolásával?

Igen, megtehetjük. De előbb van egy buktató, amin túl kell jutni. Megmutatom, mire gondolok.

Tudjuk, hogy a 2020 szökőév, a 2021 pedig nem. Hasonlítsuk össze, hány karaktert ad ki a február ezekben az években. Ehhez a hasznos wc segédprogramot használjuk, pontosabban a wc -c-t, ami a bájtokat számolja.

$ printf "hello" | wc -c

5

5 karakter a „hello”-ban (azért printf, nem echo, mert különben egy sortörés karakter is lenne). Nagyszerű!

$ cal 02 2020 | wc -c

184
$ cal 02 2021 | wc -c

184

Itt a buktató. Minden hónap kimenete más (a 2020-nál ott van a 29. nap), de mindkettő ugyanannyi karakterből áll, mert a cal szóközöket szúr be a következetes formázás érdekében. A szóközök ugyanúgy megszámolható karakterek, mint a számok.

Mit tehetünk?

Eltávolíthatjuk az összes szóközt! Így csak azok a karakterek maradnak, amiket meg akarunk számolni.

A karakterek eltávolítására nagyszerű segédprogram a tr parancs. A tr parancs string adatokat alakít át. Például az összes a karaktert A karakterré változtathatja.

$ echo "aardvark" | tr a A

AArdvArk

Az összes megadott karaktert is törölheti, ami pontosan az, amit szeretnénk.

A mi esetünkben az összes whitespace karaktert törölni akarjuk, bármi is legyen az (szóközök, tabulátorok, sortörések stb.). Van egy hasznos csoportosítás, a karakterosztály, amivel pontosan ezt tehetjük meg. A [:space:] karakterosztály mindezeket a különböző whitespace karaktereket célozza.

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

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

February2021SuMoTuWeThFrSa12345678910111213141516171819202122232425262728

Nagyszerű! Megszámolhatjuk most a karaktereket, hogy megtaláljuk a szökőéveket? Hát persze!

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

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

73

Most pedig írjunk ki néhány szökőévet!

$ 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

Hackek hátán hackek, és működik!

Szerinted melyik a gyorsabb? Hát nekem sem sok fogalmam van róla. A „February eltávolítása” útvonal talán gyorsabb? De meglepődnék, ha nagy lenne a különbség.

Ne lepődjünk meg. Nézzük meg!

Van egy nagyszerű segédprogram, a hyperfine, amivel több parancssori hívás teljesítményét lehet kiértékelni

Melyik a gyorsabb? A Hyperfine megmondja

Íme a kapcsolók, amiket a hyperfine-nak adok a két paranccsal együtt.

--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

Minket csak egy év kiszámításának teljesítménymérése érdekel, nem az, mennyi idő az évek ciklusának előállítása. Ezért csak egy adott év szökőév-ellenőrzését mérem le mindkét megközelítéssel.

$ 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'
Parancs Átlag [ms] Min [ms] Max [ms] Relatív
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

Ahogy vártuk, gyakorlatilag nincs különbség köztük a valós időt tekintve, DE a „February eltávolítása” útvonal talán valamivel gyorsabb.

Ezzel véget is ér ez a parancssori eszköz-összeállítási utazás.

Összegzés

  • Új parancssori funkciókat építhetsz úgy, hogy meglévő eszközöket egy adatcsővezetékbe fűzöl
  • Egy kis kreativitással meglepően összetett eredményeket hozhatsz ki egyszerű darabokból
  • A Hyperfine hasznos eszköz parancssori parancsok teljesítménymérésére

Következő lépések

  • Fedezd fel a cal parancs használatát.
  • Próbálj építeni egy csővezetéket, amely megszámolja egy egész év összes nem szóköz karakterét: cal 2020
  • Próbáld lefuttatni ezt az egész éves számláló csővezetéket az 1750 és 1759 közötti évekre: feltűnik valami furcsa?
30. Jan 2024 · Hasznosnak találtad?