Każdy z nas ma różne spojrzenie na świat, na politykę, na społeczeństwo, na miłość, przyjaźń, rodzinę …. można by wymieniać tak długo. Czasami spotkamy się w punkcie wspólnym z innymi, czasem się miniemy.
W zależności od zdobytych doświadczeń i założonego celu stosujemy też różne strategie. A w kodowaniu jest jak w życiu
Posłużę się tutaj prostym przykładem.
Załóżmy, że chcemy na konsoli wypisać 'a'
jeśli b > 0
lub 'c'
jeśli jest mniejsza.
Najprostszym sposobem na to zadanie jest zrobić prostego if’a
1
2
if(b > 0) console.log('a')
else console.log('c')
Drugim podejściem jest zastosowanie operatora warunkowego ternary operator (ang) np.:
1
console.log(b > 0 ? 'a' : 'c')
Możemy też jednak użyć zupełnie innej strategii
1
console.log('ca'[(b > 0) * 1])
Wygląda jak by ktoś coś pokręcił? Nic bardziej mylnego.
- ciąg
'ca'
traktujemy jako listę gdzie na pozycji0
jest c, a na pozycji1
jest a - warunek
b > 0
da nam w wynikutrue
lubfalse
- przemnożenie typu
boolean
przez1
jest najprostszym sposobem na uzyskanie wjs
z tego typu liczby, co adekwatnie da nam1
dlatrue
i0
dlafalse
Rozwiążmy powyższe dla b = 10
:
1
2
3
4
5
6
b = 10
// b > 0 => true
// true * 1 = 1
'ca'[( 10 > 0 ) * 1] /* == */ 'ca'[1 * 1] /* == */ 'ca'[1]
// OUTPUT => a, będące na pozycji 1 naszej listy
Można stwierdzić: wszystko pięknie, tylko po co takie udziwnianie, przecież to czyste popisywanie się jak w przypadku
5 * 2
== 5 << 1
. W dodatku może być zaprzeczeniem jednej z zasad programowania KISS [Keep it simple stupid], co w wolnym tłumaczeniu znaczy, aby nie udziwniać, czegoś co może być proste.
Oczywiście w powyższym taki kod faktycznie mija się z celem. Najbardziej optymalnym jest ternary, ale czy zawsze tak będzie?
Przykład zastosowania
Otrzymujemy do naszego programu jako dane wejściowe 4 liczby, załóżmy 8, 23, 18, 25
, przypiszmy je sobie do zmiennych:
1
2
3
let [a, b, c, d] = [8, 23, 18, 25];
// przy okazji, tak też można
// a = 8, b = 23, c = 18, d = 25
Naszym zadaniem jest napisanie jak najkrótszego kodu, który będzie sprawdzał warunki (poniżej) i w zależności od wyniku poda odpowiednią wartość S
, N
, E
, W
, SE
, SW
, NE
i NW
. Gwoli ścisłości, nie ja to wymyśliłam. Jest to jedno z rozwiązań zadania Power of Thor na codingames.
I tak:
- jeśli wartość
b
>d
wypisz literęS
i zwiększ wartośćd
o1
, - jeśli wartość
b
<d
wypisz literęN
i zmniejsz wartośćd
o1
, - jeśli wartość
a
>c
wypisz (lub dodaj) literęE
i zwiększ wartośćc
o1
, - jeśli wartość
a
<c
wypisz (lub dodaj) literęW
i zmniejsz wartośćc
o1
W standardowym, nieudziwnionym kodzie mielibyśmy mniej więcej taki zapis:
1
2
3
4
5
6
7
8
9
10
let [a, b, c, d] = [8, 23, 18, 25];
let m = '';
if (b > d) m = 'S', d++;
if (b < d) m = 'N', d--;
if (a > c) m += 'E', c++;
if (a < c) m += 'W', c--;
console.log(m)
// OUTPUT => NW
// bo d = 25 > b = 23 i a = 8 < c = 18
To teraz rozważmy taki zapis powyższego [uwaga, bo będzie trochę komentarzy w kodzie]:
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
let [a, b, c, d] = [8, 23, 18, 25];
let diff_bd = Math.sign(b - d), // 23 - 25 = Math.sign(-2) => -1
diff_ac = Math.sign(a - c); // 8 - 18 = Math.sign(-10) => -1
// Math.sign zwraca wartość:
// -1 dla wartości ujemnych
// 0 dla wartości 0
// 1 dla wartości dodatnich
// a w sumie o taką informację nam głównie chodzi
m = ( 'N S'[diff_bd + 1] + 'W E'[diff_ac + 1] ).trim();
// jeśli diff_bc == -1 na konsoli otrzymamy wartość "N"
// bo -1 + 1 = 0 => 'N S'[0] => "N"
// jeśli diff_bd == 0 w wyniku otrzymamy " "
// bo 0 + 1 = 1 => 'N S'[1] => " ".trim() => '' więc na konsoli nie będzie niczego z tej części
// jeśli diff_bd == 1 na konsoli otrzymamy wartość "S"
// bo 1 + 1 = 2 => 'N S'[2] => "S"
d += diff_bd; // gdzie d+-1 => d--
c += diff_ac; // gdzie c+-1 => c--
console.log(m)
// OUTPUT => NW
Porównajmy oba kody:
1
2
3
4
5
6
7
8
9
10
11
12
13
let [a, b, c, d] = [8, 23, 18, 25],
m = '';
if (b > d) m = 'S', d++;
if (b < d) m = 'N', d--;
if (a > c) m += 'E', c++;
if (a < c) m += 'W', c--;
console.log(m)
// OUTPUT => NW
1
2
3
4
5
6
7
8
9
10
11
12
13
let [a, b, c, d] = [8, 23, 18, 25],
l = Math.sign,
j = l(b - d),
k = l(a - c);
m = ( 'N S'[j + 1] +
'W E'[k + 1] ).trim();
d += j;
c += k;
console.log(m)
// OUTPUT => NW
Nie dość, że po prawej kod wydaje się dłuższy, to w dodatku jest mniej czytelny. Jest jedno ale, wydaje się nam.
Jeśli idzie o ilość znaków użytych do naszego zadania to:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
`[a, b, c, d] = [8, 23, 18, 25],
m = '';
if (b > d) m = 'S', d++;
if (b < d) m = 'N', d--;
if (a > c) m += 'E', c++;
if (a < c) m += 'W', c--;
console.log(m)`.
replace(/\r\n|\n|\r|\t|\s/gm,'').length
// da nam 112 znaków
1
2
3
4
5
6
7
8
9
10
11
12
13
14
`[a, b, c, d] = [8, 23, 18, 25],
l = Math.sign,
j = l(b - d),
k = l(a - c);
m = ( 'N S'[j + 1] +
'W E'[k + 1] ).trim();
d += j;
c += k;
console.log(m)`.
replace(/\r\n|\n|\r|\t|\s/gm,'').length
// da nam 108 znaków
Ponadto:
-
Nie powtarzamy 4 razy warunku if, pomimo, że de facto jest on sprawdzany.
Jeśli liczbab > d
ich różnica będzie ujemna. Ta sama reguła tyczy sięa
ic
. - Zmiennej
l
przypisujemy funkcjęMath.sign
, bo użyjemy jej co najmniej dwukrotnie, stąd w naszym kodziel(a-c)
il(b-d)
. - Przypisanie wartości dla zmiennej
m
jest tylko raz. - A na koniec w jednej linijce zmieniamy (czyli podwyższamy lub zmniejszamy o 1) wartość zmiennych
c
id
.
I jak to w życiu, tak w programowaniu sami musimy odpowiedzieć sobie na pytanie. Która strategia jest odpowiednia dla nas i naszego celu