-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfeed.xml
1766 lines (1610 loc) · 128 KB
/
feed.xml
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.5">Jekyll</generator><link href="https://csirmazbendeguz.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://csirmazbendeguz.net/" rel="alternate" type="text/html" /><updated>2021-06-20T01:35:08+01:00</updated><id>https://csirmazbendeguz.net/feed.xml</id><entry><title type="html">How to convert a matrix to a table in Google Sheets</title><link href="https://csirmazbendeguz.net/2021/06/19/how-to-convert-a-matrix-to-a-table-in-google-sheets.html" rel="alternate" type="text/html" title="How to convert a matrix to a table in Google Sheets" /><published>2021-06-19T00:00:00+01:00</published><updated>2021-06-19T00:00:00+01:00</updated><id>https://csirmazbendeguz.net/2021/06/19/how-to-convert-a-matrix-to-a-table-in-google-sheets</id><content type="html" xml:base="https://csirmazbendeguz.net/2021/06/19/how-to-convert-a-matrix-to-a-table-in-google-sheets.html"><section class="section">
<h3>Motivation</h3>
<p>
I came across an interesting problem while performing my data engineering duties at work.
</p>
<p>
I had to import a spreadsheet's content into a relational database.
It may sound like a trivial task, but there was a catch: the data was defined in a matrix format.
</p>
<p>
It looked something like this:
</p>
<p style="text-align:center;margin:18px 0;">
<img
src="/assets/images/how-to-convert-a-matrix-to-a-table-in-google-sheets/matrix.png"
alt="The matrix"
title="The matrix"
style="max-width:900px;"
/>
</p>
<p>
It's a matrix that defines the monthly budget of each department for the first half of 2021.
</p>
<p>
To import this data into an RDBMS, we need to convert it to a table first.
</p>
<p style="text-align:center;margin:18px 0;">
<img
src="/assets/images/how-to-convert-a-matrix-to-a-table-in-google-sheets/expected.png"
alt="The table"
title="The table"
style="max-width:900px;max-height:500px;"
/>
</p>
<p>
The transformation should be dynamic, so when the matrix is updated,
the changes are automatically reflected in the table.
</p>
<p>
I haven't found any articles on the subject, so I think my solution is worth sharing.
</p>
</section>
<section class="section">
<h3>Helper tables</h3>
<p>
To make our lives easier, let's define two helper tables - one for the matrix rows and one for the matrix columns.
</p>
<p style="text-align:center;margin:18px 0;">
<img
src="/assets/images/how-to-convert-a-matrix-to-a-table-in-google-sheets/matrix-rows-table.png"
alt="The matrix rows table"
title="The matrix rows table"
style="max-width:400px;max-height:300px;margin: 12px;"
/>
<img
src="/assets/images/how-to-convert-a-matrix-to-a-table-in-google-sheets/matrix-columns-table.png"
alt="The matrix columns table"
title="The matrix columns table"
style="max-width:300px;max-height:300px;margin: 12px;"
/>
</p>
<p>
Let's make them truly dynamic, so whenever a new row or column is added to the matrix, it automatically appears in
the helper table.
</p>
</section>
<section class="section">
<h3>Counting the matrix rows/columns</h3>
<p>First, we need a way to count the number of rows/columns in the matrix.</p>
<p>
Luckily for us, the <a href="https://support.google.com/docs/answer/3093991?hl=en-GB">COUNTA</a> function
will cover 100% of our counting needs.
</p>
<p>
It's a function that counts all non-empty cells inside a given range:
</p>
<ul>
<li>
<code class="highlighter-rouge">counta(matrix!$A:$A)</code> - count the number of rows (i.e. non-empty cells in <em>column A</em>)
</li>
<li>
<code class="highlighter-rouge">counta(matrix!$1:$1)</code> - count the number of columns (i.e. non-empty cells in <em>row 1</em>)
</li>
</ul>
<p style="text-align:center;margin:18px 0;">
<img
src="/assets/images/how-to-convert-a-matrix-to-a-table-in-google-sheets/count-items.png"
alt="Counting the matrix rows/columns"
title="Counting the matrix rows/columns"
style="max-width:600px;max-height:300px;"
/>
</p>
<p>In its current state, the matrix has 9 rows and 6 columns.</p>
</section>
<section class="section">
<h3>Copying the matrix rows</h3>
<p>
To copy the matrix rows, we can use the
<a href="https://support.google.com/docs/answer/3093275?hl=en-GB">ARRAYFORMULA</a> function.
</p>
<p style="text-align:center;margin:18px 0;">
<img
src="/assets/images/how-to-convert-a-matrix-to-a-table-in-google-sheets/copy-matrix-rows.png"
alt="Copying the matrix rows"
title="Copying the matrix rows"
style="max-width:600px;max-height:300px;"
/>
</p>
<figure class="highlight"><pre><code class="language-escape" data-lang="escape">ARRAYFORMULA(INDIRECT("matrix!A2:A" &amp; (COUNTA(matrix!$A:$A) + 1)))</code></pre></figure>
<p>
This formula:
<ul>
<li>
Builds the string <code class="highlighter-rouge">"matrix!A2:A10"</code>.
This string represents the cell range of the matrix rows.
</li>
<li>
If a new row is added to the matrix, the range is automatically increased by one row (because of the
<code class="highlighter-rouge">COUNTA</code> function).
</li>
<li>
Then, it converts the <em>cell range string</em> to an actual <em>cell range reference</em> with the
<a href="https://support.google.com/docs/answer/3093377?hl=en-GB">INDIRECT</a> function.
</li>
<li>
Finally, it copies the cell range reference to the current position with the <em>ARRAYFORMULA</em> function.
</li>
</ul>
</p>
</section>
<section class="section">
<h3>Generating ID numbers</h3>
<p>
To generate ID numbers, we can use <em>ARRAYFORMULA</em> combined with the
<a href="https://support.google.com/docs/answer/3093316?hl=en-GB">ROW</a> function.
</p>
<p style="text-align:center;margin:18px 0;">
<img
src="/assets/images/how-to-convert-a-matrix-to-a-table-in-google-sheets/generating-id-numbers.png"
alt="Generating ID numbers"
title="Generating ID numbers"
style="max-width:600px;max-height:300px;"
/>
</p>
<figure class="highlight"><pre><code class="language-escape" data-lang="escape">ARRAYFORMULA(ROW(INDIRECT("B2:B" &amp; (COUNTA($B:$B)))) - 2)</code></pre></figure>
<p>
The idea is similar to copying cells.
<ul>
<li>
First, it builds the <em>cell range string</em> <code class="highlighter-rouge">"B2:B10"</code>.
If a row is added/deleted, this range is automatically adjusted.
</li>
<li>
Then, it converts the string to a <em>cell range reference</em> with <em>INDIRECT</em>.
</li>
<li>
Next, it takes the row number of each cell (with <em>ROW</em>) and subtracts two from all of them, so the
resulting sequence is zero-based. We'll see why this is important later.
</li>
<li>
Finally, it copies the sequence to the given position with <em>ARRAYFORMULA</em>.
</li>
</ul>
</p>
</section>
<section class="section">
<h3>Calculating the last matrix column</h3>
<p>
To calculate the last matrix column, we can use the
<a href="https://support.google.com/docs/answer/3093308?hl=en-GB">ADDRESS</a> function.
</p>
<p style="text-align:center;margin:18px 0;">
<img
src="/assets/images/how-to-convert-a-matrix-to-a-table-in-google-sheets/getting-the-last-matrix-column.png"
alt="Calculating the last matrix column"
title="Calculating the last matrix column"
style="max-width:450px;max-height:250px;"
/>
</p>
<figure class="highlight"><pre><code class="language-escape" data-lang="escape">address(1, counta(matrix!$1:$1) + 1, 4)</code></pre></figure>
<p>
The <em>ADDRESS</em> function takes a row and a column number, and returns a cell reference string.
</p>
<p>
So, in the formula above:
<ul>
<li>
<code class="highlighter-rouge">1</code> is the row number.
</li>
<li>
<code class="highlighter-rouge">counta(matrix!$1:$1) + 1</code> is the column number.
It will evaluate to <code class="highlighter-rouge">7</code> in this example.
Column 7 is G.
</li>
<li>
<code class="highlighter-rouge">4</code> is the flag for returning the address in the format
<code class="highlighter-rouge">"G1"</code>.
</li>
</ul>
</p>
</section>
<section class="section">
<h3>Copying the matrix columns</h3>
<p>
Copying the matrix columns is pretty similar to copying the rows.
</p>
<p style="text-align:center;margin:18px 0;">
<img
src="/assets/images/how-to-convert-a-matrix-to-a-table-in-google-sheets/copy-matrix-columns.png"
alt="Copying the matrix columns"
title="Copying the matrix columns"
style="max-width:700px;max-height:400px;"
/>
</p>
<figure class="highlight"><pre><code class="language-escape" data-lang="escape">transpose(arrayformula(to_text(indirect("matrix!B1:" &amp; address(1, counta(matrix!$1:$1) + 1, 4)))))</code></pre></figure>
<p>That's quite a mouthful! Here's what it does:</p>
<p>
<ul>
<li>
First, it builds the <code class="highlighter-rouge">"matrix!B1:G1"</code> cell range string with the help
of the <em>ADDRESS</em> function.
</li>
<li>
If a new column is added, the range is automatically adjusted.
For example, if we added the column <code class="highlighter-rouge">Jul-21</code>, the cell range string would be
<code class="highlighter-rouge">"matrix!B1:H1"</code>
</li>
<li>
Then, it transforms the <em>cell range string</em> to a <em>cell range reference</em>
with <em>INDIRECT</em>, and copies the values to the position with
<em>ARRAYFORMULA</em> (as discussed above).
</li>
<li>
To make sure Google Sheets handles the values as texts (not as dates), all values are
converted to text with the
<a href="https://support.google.com/docs/answer/3094285?hl=en-GB">TO_TEXT</a> function.
</li>
<li>
Note that <em>ARRAYFORMULA</em> will output the cell range horizontally.
To change it to vertical, we need to use the
<a href="https://support.google.com/docs/answer/3094262?hl=en-GB">TRANSPOSE</a> function.
</li>
</ul>
</p>
</section>
<section class="section">
<h3>Flattening the matrix values</h3>
<p>Now that both helper tables are ready, we can start adding columns to the main table.</p>
<p>
To make things easier, let's use the
<a href="https://support.google.com/docs/answer/10307761?hl=en-GB">FLATTEN</a> function.
</p>
<p>
The <em>FLATTEN</em> function takes a range of values and <em>flattens</em> them into a single column.
</p>
<p>
For example:
</p>
<div style="text-align: center;">
<table style="display: inline-block; vertical-align: middle;">
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
</tr>
<tr>
<td>8</td>
<td>9</td>
<td>10</td>
<td>11</td>
</tr>
</table>
<div style="display: inline-block; vertical-align: middle;">&#8594;</div>
<table style="display: inline-block; vertical-align: middle;">
<tr><td>0</td></tr>
<tr><td>1</td></tr>
<tr><td>...</td></tr>
<tr><td>11</td></tr>
</table>
</div>
<p>
We can use it to extract the matrix values into the main table:
</p>
<p style="text-align:center;margin:18px 0;">
<img
src="/assets/images/how-to-convert-a-matrix-to-a-table-in-google-sheets/flattening-matrix-values.png"
alt="Flattening the matrix values"
title="Flattening the matrix values"
style="max-width:700px;max-height:400px;"
/>
</p>
<figure class="highlight"><pre><code class="language-escape" data-lang="escape">flatten(indirect("matrix!B2:" &amp; address(counta(matrix!$A:$A) + 1, counta(matrix!$1:$1) + 1, 4)))</code></pre></figure>
<p>
Conceptually, this formula is pretty similar to the previous ones:
</p>
<p>
<ul>
<li>
First, it builds the <code class="highlighter-rouge">"matrix!B2:G10"</code> cell range string with the
help of the <em>ADDRESS</em> function.
This range includes all matrix values (but not the row/column names).
If someone adds a new column or row to the matrix, it is automatically adjusted.
</li>
<li>
Then, the <em>cell range string</em> is transformed to a <em>cell range reference</em> with <em>INDIRECT</em>.
</li>
<li>
Finally, <em>FLATTEN</em> flattens the matrix values into a column and outputs them
(similarly to <em>ARRAYFORMULA</em>).
</li>
</ul>
</p>
<aside class="comment">
<p>
Note that <em>FLATTEN</em> transforms the values in a <strong>left-to-right then top-to-bottom order</strong>.
</p>
<p>
This is good news - <u>we can exploit the ordering to calculate the corresponding rows/columns</u>.
</p>
</aside>
</section>
<section class="section">
<h3>Calculating the row IDs</h3>
<p>
To calculate which matrix row a given item belongs to, divide the ID with the width of the matrix
and discard the remainder (keep the integer part only).
</p>
<p>
For example:
</p>
<table>
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>&#8594;</td>
<td>0</td>
</tr>
<tr>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>&#8594;</td>
<td>1</td>
</tr>
<tr>
<td>8</td>
<td>9</td>
<td>10</td>
<td>11</td>
<td>&#8594;</td>
<td>2</td>
</tr>
</table>
<p>
That is,
<ul>
<li>0, 1, 2 and 3 divided by 4 is 0</li>
<li>4, 5, 6 and 7 divided by 4 is 1</li>
<li>8, 9, 10 and 11 divided by 4 is 2</li>
</ul>
</p>
<p>
The
<a href="https://support.google.com/docs/answer/3093436?hl=en-GB">QUOTIENT</a>
function is the natural choice for this calculation.
</p>
<p style="text-align:center;margin:18px 0;">
<img
src="/assets/images/how-to-convert-a-matrix-to-a-table-in-google-sheets/calculating-row-ids.png"
alt="Calculating the row IDs"
title="Calculating the row IDs"
style="max-width:700px;max-height:400px;"
/>
</p>
<figure class="highlight"><pre><code class="language-escape" data-lang="escape">ARRAYFORMULA(QUOTIENT(INDIRECT("A2:A" &amp; COUNTA($A:$A)), COUNTA(matrix!$1:$1)))</code></pre></figure>
<p>The formula is pretty straightforward:</p>
<p>
<ul>
<li>
First, it builds the <code class="highlighter-rouge">"A2:A55"</code> cell range string (referring to
the <strong>id</strong> column) and
creates a cell range reference with the <em>INDIRECT</em> function.
</li>
<li>
Then, the values are transformed with the <em>QUOTIENT</em> function
(according to the rules above).
</li>
<li>
Finally, the values are copied to the given cell with <em>ARRAYFORMULA</em>.
</li>
</ul>
</p>
</section>
<section class="section">
<h3>Calculating the column IDs</h3>
<p>
To calculate which matrix column a given item belongs to, divide the ID with the width of the matrix
and discard the integer part (keep the remainder only).
</p>
<p>
For example:
</p>
<table>
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
</tr>
<tr>
<td>8</td>
<td>9</td>
<td>10</td>
<td>11</td>
</tr>
<tr>
<td>&#8595;</td>
<td>&#8595;</td>
<td>&#8595;</td>
<td>&#8595;</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>
<p>
That is,
<ul>
<li>0, 4 and 8 modulo 4 is 0</li>
<li>1, 5 and 9 modulo 4 is 1</li>
<li>2, 6 and 10 modulo 4 is 2</li>
<li>3, 7 and 11 modulo 4 is 3</li>
</ul>
</p>
<p>
For this calculation,
the appropriate function is
<a href="https://support.google.com/docs/answer/3093497?hl=en-GB">MOD</a>.
</p>
<p style="text-align:center;margin:18px 0;">
<img
src="/assets/images/how-to-convert-a-matrix-to-a-table-in-google-sheets/calculating-column-ids.png"
alt="Calculating the column IDs"
title="Calculating the column IDs"
style="max-width:700px;max-height:400px;"
/>
</p>
<figure class="highlight"><pre><code class="language-escape" data-lang="escape">ARRAYFORMULA(MOD(INDIRECT("A2:A" &amp; COUNTA($A:$A)), COUNTA(matrix!$1:$1)))</code></pre></figure>
</section>
<section class="section">
<h3>Looking up the rows/columns by their IDs</h3>
<p>
We're almost done!
</p>
<p>
We already have the row and column IDs.
All that's left to do is to <em>look up</em> the actual values from the helper tables...
and when it comes to looking up things, the best in the business is:
<a href="https://support.google.com/docs/answer/3093318?hl=en-GB">VLOOKUP</a>.
</p>
<p>
To put it simply, it's a function for performing a linear search in a table.
For example:
</p>
<figure class="highlight"><pre><code class="language-escape" data-lang="escape">VLOOKUP(0, rows!A2:B10, 2) --&gt; "Business Development"</code></pre></figure>
<p>Translates to: "find the row name where the id is 0".</p>
<p>
The function takes 3 parameters:
</p>
<ul>
<li><code class="highlighter-rouge">0</code> - the id to search for</li>
<li><code class="highlighter-rouge">rows!A2:B10</code> - the table to search in</li>
<li><code class="highlighter-rouge">2</code> - the column to return</li>
</ul>
<p>And it can also be combined with <em>ARRAYFORMULA</em>:</p>
<p style="text-align:center;margin:18px 0;">
<img
src="/assets/images/how-to-convert-a-matrix-to-a-table-in-google-sheets/looking-up-rows.png"
alt="Looking up rows"
title="Looking up rows"
style="max-width:700px;max-height:400px;"
/>
</p>
<figure class="highlight"><pre><code class="language-escape" data-lang="escape">ARRAYFORMULA(VLOOKUP(INDIRECT("C2:C" &amp; COUNTA($C:$C)), INDIRECT("rows!A2:B" &amp; COUNTA(rows!$B:$B)), 2))</code></pre></figure>
<p>
It looks a bit complicated, but there's actually nothing new here:
</p>
<ul>
<li>
First, it builds the <code class="highlighter-rouge">"C2:C55"</code> and
<code class="highlighter-rouge">"rows!A2:B10"</code> cell range references.
The first one refers to the <strong>row id</strong> column, the second one refers to the rows helper table.
</li>
<li>
We pass 3 arguments to <em>VLOOKUP</em> - the ids to search for, the table to search
in and the column to return.
</li>
<li>
Finally, <em>VLOOKUP</em> finds the row name for each id and the results are displayed with <em>ARRAYFORMULA</em>.
</li>
</ul>
<p style="text-align:center;margin:18px 0;">
<img
src="/assets/images/how-to-convert-a-matrix-to-a-table-in-google-sheets/looking-up-columns.png"
alt="Looking up columns"
title="Looking up columns"
style="max-width:700px;max-height:400px;"
/>
</p>
<figure class="highlight"><pre><code class="language-escape" data-lang="escape">ARRAYFORMULA(VLOOKUP(INDIRECT("D2:D" &amp; COUNTA($D:$D)), INDIRECT("columns!A2:B" &amp; COUNTA(columns!$B:$B)), 2))</code></pre></figure>
</section>
<section class="section">
<h3>Conclusion</h3>
<p>
That's it!
</p>
<p>
We transformed a matrix into a table using nothing but Google Sheets formulas.
Not as easy as it looks!
</p>
<p>
I hope this post was helpful.
</p>
</section></content><author><name></name></author></entry><entry><title type="html">I wanna be the very best</title><link href="https://csirmazbendeguz.net/2020/12/21/i-wanna-be-the-very-best.html" rel="alternate" type="text/html" title="I wanna be the very best" /><published>2020-12-21T00:00:00+00:00</published><updated>2020-12-21T00:00:00+00:00</updated><id>https://csirmazbendeguz.net/2020/12/21/i-wanna-be-the-very-best</id><content type="html" xml:base="https://csirmazbendeguz.net/2020/12/21/i-wanna-be-the-very-best.html"><section class="section">
<p style="text-align:center;">
<img
src="/assets/images/i-wanna-be-the-very-best/memgame.png"
alt="A screenshot of the game"
title="A screenshot of the game"/>
</p>
</section>
<section class="section">
<h3>Motivation</h3>
<p>
My favorite course in school was Java development. It opened a whole new world for me.
</p>
<p>
I have learned other programming languages before, but Java was the first one I actually <s>mastered</s> tried to master.
It solidifed my programming skills, and more importantly, it enabled me to build applications for virtually any platform.
</p>
<p>
Yes! I'm talking about <strong>video games</strong>, for desktop. Go figure!
</p>
<p>
Luckily for me, the curriculum included GUI programming. They taught us <em>Java Swing</em>, an old graphics library from the 90s.
I particularly enjoyed one of the assignments: we had to build a memory game (also called "<em>concentration</em>" - as I've learned lately).
</p>
<p>
Apart from a few loose requirements, we were given the freedom to implement the game in whatever way we saw fit.
As a recovering Pokémon-addict (grew up watching the series), there was no question about what theme I will choose.
</p>
<p>
I built the game as well as I could. I even photoshopped artwork (that I got from the internet) to make it as perfect as possible.
I received the best possible grade and was so proud I eventually also uploaded it to GitHub - so the whole world can see my fine work.
</p>
<p>This was in 2016...</p>
<p>
In 2019 I decided to revisit my old GitHub repositories. Why?
Because for software developers, GitHub is like a portfolio.
And you don't want shitty projects on your portfolio.
</p>
<p>
What I found there was staggering: past me had no idea how to develop good software.
Despite all the love I put into this game, it was a dumpster fire.
</p>
<p>
I ended up making most of my old repositories private, except this one. I decided to save it.
</p>
</section>
<section class="section">
<h3>The bane of GUI programming</h3>
<p>
The codebase was a textbook example of bad practices.
</p>
<p>
It was full of duplications, everything was hardcoded, and for some reason I thought committing executable files to Git was a good idea.
Fortunately, I managed to fix the obvious problems quite quickly.
</p>
<p>
The the challenging part was not replacing tabs with spaces or renaming variables.
It was the architecture.
</p>
<p>
Java Swing is a GUI library with basic GUI components like buttons, labels, panels, etc.
</p>
<p style="text-align:center;">
<img src="/assets/images/i-wanna-be-the-very-best/memgame-components.png"
alt="A screenshot with the game's graphical components highlighted"
title="A screenshot with the game's graphical components highlighted"/>
</p>
<p>
There aren't many restrictions on how to organize code. The library doesn't care how you wire the components together.
It is the programmer's job to come up with a sufficiently decoupled and modularized architecture.
</p>
<p>
In my opinion, anyone building an app with this toolkit will face the following two problems:
<ul>
<li>How do I make components talk to each other?</li>
<li>How do I avoid tight coupling?</li>
</ul>
</p>
<p>
These are not easy problems. No wonder I couldn't tackle them when I was a beginner.
After doing some research I decided on the following solutions...
</p>
</section>
<section class="section">
<h3>Inversion of Control</h3>
<p>
Let's say, the user restarts the game by clicking the restart button.
</p>
<p>
We expect the following things to happen:
<ul>
<li>The board resets the memory cards.</li>
<li>The timer resets to zero.</li>
<li>The move counter resets to zero.</li>
</ul>
</p>
<p>That's <strong>a lot</strong> of responsibilities for a single button!</p>
<p>A naive solution would look something like this:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">RestartButton</span> <span class="kd">extends</span> <span class="nc">JButton</span> <span class="kd">implements</span> <span class="nc">ActionListener</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">BoardPanel</span> <span class="n">boardPanel</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">TimerLabel</span> <span class="n">timerLabel</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">TriesLabel</span> <span class="n">triesLabel</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">RestartButton</span><span class="o">(</span><span class="nc">BoardPanel</span> <span class="n">boardPanel</span><span class="o">,</span> <span class="nc">TimerLabel</span> <span class="n">timerLabel</span><span class="o">,</span> <span class="nc">TriesLabel</span> <span class="n">triesLabel</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">(</span><span class="s">"Restart"</span><span class="o">);</span>
<span class="k">this</span><span class="o">.</span><span class="na">boardPanel</span> <span class="o">=</span> <span class="n">boardPanel</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">timerLabel</span> <span class="o">=</span> <span class="n">timerLabel</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">triesLabel</span> <span class="o">=</span> <span class="n">triesLabel</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">actionPerformed</span><span class="o">(</span><span class="nc">ActionEvent</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">boardPanel</span><span class="o">.</span><span class="na">reset</span><span class="o">();</span>
<span class="n">timerLabel</span><span class="o">.</span><span class="na">reset</span><span class="o">();</span>
<span class="n">triesLabel</span><span class="o">.</span><span class="na">reset</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>The problem with this approach is, the restart button should not communicate with other components directly.</p>
<p>
It should not keep track of components handling game restarts, and it should not know how to call them.
That's not the button's responsibility.
</p>
<p>
In other words, we should be able to add new objects handling game restarts without modifying the restart button itself.
</p>
<p>To achieve this, we need to introduce a new layer of abstraction. We need <strong>inversion of control</strong>.</p>
</section>
<section class="section">
<h3>Event Bus</h3>
<p>An event bus is a design pattern for building event-driven systems. It's pretty simple.</p>
<p style="text-align:center;">
<img src="/assets/images/i-wanna-be-the-very-best/crazybus.jpg"
alt="A flying bus"
title="A flying bus"/>
</p>
<p>
An event bus is a central object where you register all your event handlers (e.g. the timer component).
Then, when an event is created (e.g. the restart button is clicked), it is broadcasted to all the registered handlers.
</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// RestartButton.java</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RestartButton</span> <span class="kd">extends</span> <span class="nc">JButton</span> <span class="kd">implements</span> <span class="nc">ActionListener</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">EventBus</span> <span class="n">eventBus</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">RestartButton</span><span class="o">(</span><span class="nc">EventBus</span> <span class="n">eventBus</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">(</span><span class="s">"Restart"</span><span class="o">);</span>
<span class="k">this</span><span class="o">.</span><span class="na">eventBus</span> <span class="o">=</span> <span class="n">eventBus</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">actionPerformed</span><span class="o">(</span><span class="nc">ActionEvent</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">eventBus</span><span class="o">.</span><span class="na">dispatch</span><span class="o">(</span><span class="k">new</span> <span class="nc">RestartEvent</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// TimerLabel.java</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TimerLabel</span> <span class="kd">extends</span> <span class="nc">JLabel</span> <span class="kd">implements</span> <span class="nc">RestartListener</span> <span class="o">{</span>
<span class="kd">public</span> <span class="nf">TimerLabel</span><span class="o">(</span><span class="nc">EventBus</span> <span class="n">eventBus</span><span class="o">)</span> <span class="o">{</span>
<span class="n">eventBus</span><span class="o">.</span><span class="na">register</span><span class="o">(</span><span class="nc">RestartListener</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="k">this</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">restarted</span><span class="o">(</span><span class="nc">RestartEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// Reset timer.</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>
Much better! The components do not depend on each other anymore.
They communicate via the event bus.
</p>
<p>
How can the event bus figure out where to send the events?
I'll spare you the details, but the answer is: <em>reflection</em> - if you're interested in the inner workings of the event system (I'm simplifying things a bit here), feel free to check out the <a href="https://github.com/csirmazbendeguz/pokemon-memory-game">source code</a>.
</p>
</section>
<section class="section">
<h3>Decoupling with Dependency Injection</h3>
<p>
In Java Swing, GUI components are defined in Java code, forming a tree-like structure.
</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">class</span> <span class="nc">Buttons</span> <span class="kd">extends</span> <span class="nc">JPanel</span> <span class="o">{</span>
<span class="nc">Buttons</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// ...</span>
<span class="c1">// Add children.</span>
<span class="n">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">RestartButton</span><span class="o">());</span>
<span class="n">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">ChangeDimensionButton</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>
The problem is, what if we have to change something at the bottom of the tree?
Let's say, add the event bus to the restart button?
</p>
<p>
Well... we would have to pass the event bus to every component in the hierarchy.
Even where it's not needed.
</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">class</span> <span class="nc">Buttons</span> <span class="kd">extends</span> <span class="nc">JPanel</span> <span class="o">{</span>
<span class="nc">Buttons</span><span class="o">(</span><span class="nc">EventBus</span> <span class="n">eventBus</span><span class="o">)</span> <span class="o">{</span>
<span class="n">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">RestartButton</span><span class="o">(</span><span class="n">eventBus</span><span class="o">));</span>
<span class="n">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">ChangeDimensionButton</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>
It's a bit inconvenient. Also, it'll be hard to test (we want to write tests... right?).
</p>
<p>
In this case, the panel container doesn't need to know how to instantiate its child objects.
Let's make our lives a little easier and use <strong>dependency injection</strong>.
</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">class</span> <span class="nc">Buttons</span> <span class="kd">extends</span> <span class="nc">JPanel</span> <span class="o">{</span>
<span class="nc">Buttons</span><span class="o">(</span><span class="nc">RestartButton</span> <span class="n">restartButton</span><span class="o">,</span> <span class="nc">ChangeDimensionButton</span> <span class="n">changeDimensionButton</span><span class="o">)</span> <span class="o">{</span>
<span class="n">add</span><span class="o">(</span><span class="n">restartButton</span><span class="o">);</span>
<span class="n">add</span><span class="o">(</span><span class="n">changeDimensionButton</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>
Great! We have pushed the responsibility of instantiating the child components one layer up in the hierarchy.
</p>
<p>Like squeezing a toothpaste. We are making files slimmer by moving the complexity somewhere else.</p>
<p style="text-align:center;">
<img src="/assets/images/i-wanna-be-the-very-best/toothpaste.jpg"
alt="A squeezed toothpaste"
title="A squeezed toothpaste"/>
</p>
<p>
That sounds fantastic, but... when does it ever end? Will we have to instantiate everything at startup time? Yes. Kind of.
</p>
</section>
<section class="section">
<h3>Enter dependency injection frameworks!</h3>
<p>To make the project as over-engineered as possible (gotta show off those skills!), I decided to use a framework that automatically instantiates and injects objects on demand.</p>
<p>I picked <a href="https://github.com/google/guice">Guice</a>.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// EventBus.java</span>
<span class="nd">@Singleton</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">EventBus</span> <span class="o">{</span>
<span class="c1">// ...</span>
<span class="o">}</span>
<span class="c1">// RestartButton.java</span>
<span class="nd">@Singleton</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RestartButton</span> <span class="kd">extends</span> <span class="nc">JButton</span> <span class="kd">implements</span> <span class="nc">ActionListener</span> <span class="o">{</span>
<span class="nd">@Inject</span>
<span class="kd">public</span> <span class="nf">RestartButton</span><span class="o">(</span><span class="nc">EventBus</span> <span class="n">eventBus</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// ...</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// Buttons.java</span>
<span class="nd">@Singleton</span>
<span class="kd">class</span> <span class="nc">Buttons</span> <span class="kd">extends</span> <span class="nc">JPanel</span> <span class="o">{</span>
<span class="nd">@Inject</span>
<span class="nc">Buttons</span><span class="o">(</span><span class="nc">RestartButton</span> <span class="n">restartButton</span><span class="o">,</span> <span class="nc">ChangeDimensionButton</span> <span class="n">changeDimensionButton</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// ...</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>
Marvelous! Guice can handle the creation and wiring of most GUI components out of the box. All you have to do is define some annotations.
Naturally, for dynamically generated components (e.g. the memory cards), we'll have to keep using "new".
</p>
<p>
It's also possible to go a bit further with reflection, and auto-discover/auto-register event handlers on startup.
In hindsight, I'm not sure it was my brightest idea - but it's definitely possible! The sky is the limit.
</p>
</section>
<section class="section">
<h3>Conclusion</h3>
<p>
We have covered a lot of ground.
There's certainly a difference between understanding event-driven systems and dependency injection and making them work.
But, when it comes to these kind of apps, I believe it's the way to go.
</p>
<p>I implemented the game in 1-2 weeks. Then I spent 1-2 months refactoring and polishing it.</p>
<p>I'm not complaining, it was a fun intellectual exercise and learning experience.</p>
<p>The game can be downloaded from the <a href="https://github.com/csirmazbendeguz/pokemon-memory-game/releases">releases page</a> (requires Java 8).</p>
<p>Thanks for reading!</p>
</section></content><author><name></name></author><summary type="html">What I learned from (re)writing a Pokémon-themed memory game</summary></entry><entry><title type="html">What programming language should I learn?</title><link href="https://csirmazbendeguz.net/2020/12/12/what-programming-language-should-i-learn.html" rel="alternate" type="text/html" title="What programming language should I learn?" /><published>2020-12-12T00:00:00+00:00</published><updated>2020-12-12T00:00:00+00:00</updated><id>https://csirmazbendeguz.net/2020/12/12/what-programming-language-should-i-learn</id><content type="html" xml:base="https://csirmazbendeguz.net/2020/12/12/what-programming-language-should-i-learn.html"><section class="section">
<p>The long answer is: it depends.</p>
<p>The short answer is: JavaScript.</p>
</section>
<section class="section">
<h3>Let me explain...</h3>
<p>
I think most developers are familiar with the long answer: it depends on what you want to build.
Obviously. If the person has an interest in a specific platform the answer is trivial.
Unfortunately, that's usually not the case.
</p>
<p>The question is not: <em>"What programming language should I learn to build apps for Android?"</em>.</p>
<p>The question is (rephrased): <em>"What programming language should I learn to get a job in software development?"</em>.</p>
<p>And I think that's an entirely valid question to ask! And maybe more relevant than ever.</p>
<p>In my opinion, the best way to switch careers into IT is with <strong>front-end web development</strong>.</p>
<aside class="comment">
<p>Disclaimer: I've never actually worked as a front-end developer, nor have I switched careers, so you should take my advice with a grain of salt.</p>
</aside>
</section>
<section class="section">
<h3>Why JavaScript?</h3>
<ul>
<li>It's ubiquitous. JavaScript has a monopoly over the browsers, it's pretty much impossible to avoid. Every dev knows at least a little JS (even me!). Give in! :D</li>
<li>Career opportunities. From small agencies to Big Tech. There's also freelancing. A person who can build beautiful web user interfaces is an asset to any software company.</li>
<li>Lenient requirements. A lot of front-end professionals are self-taught, and I think, in general, the job requirements for front-end positions are a bit more lenient when it comes to formal education. Be prepared to get grilled on your portfolio instead!</li>
<li>It's visual. It means you get quick feedback if you're doing something right or wrong. Also, it can be a lot of fun.</li>
</ul>
<p>Are there any cons? Yes.</p>
<p>JavaScript (and scripting languages in general) might not be the best for learning programming fundamentals.</p>
<p>When I started out with PHP, object-oriented programming was a confusing mystery to me. It only started to make sense when I learned Java.</p>
<p>Still, if you're looking for a solid career path, I think JavaScript development is a great one.</p>
</section></content><author><name></name></author><summary type="html">The age old question. I've been asked this multiple times recently, here are my thoughts.</summary></entry><entry><title type="html">Requests Session for OAuth 2 Client Credentials Flow with Authlib</title><link href="https://csirmazbendeguz.net/2020/04/04/oauth2-client-credentials-with-authlib.html" rel="alternate" type="text/html" title="Requests Session for OAuth 2 Client Credentials Flow with Authlib" /><published>2020-04-04T00:00:00+01:00</published><updated>2020-04-04T00:00:00+01:00</updated><id>https://csirmazbendeguz.net/2020/04/04/oauth2-client-credentials-with-authlib</id><content type="html" xml:base="https://csirmazbendeguz.net/2020/04/04/oauth2-client-credentials-with-authlib.html"><section class="section">
<h3>Session</h3>
<p>The trick is using a Session object.</p>
<p>In Requests, Session objects allow you to abstract away things that are common to your API calls (such as authorization).</p>
<p>Luckily for us, <a href="https://docs.authlib.org">Authlib</a> comes with its own OAuth2Session class included.</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">authlib.integrations.requests_client</span> <span class="kn">import</span> <span class="n">OAuth2Session</span>
<span class="n">session</span> <span class="o">=</span> <span class="n">OAuth2Session</span><span class="p">(</span>
<span class="n">client_id</span><span class="o">=</span><span class="s">"&lt;client id&gt;"</span><span class="p">,</span>
<span class="n">client_secret</span><span class="o">=</span><span class="s">"&lt;client secret&gt;"</span><span class="p">,</span>
<span class="n">token_endpoint_auth_method</span><span class="o">=</span><span class="s">"client_secret_post"</span><span class="p">,</span>
<span class="n">scope</span><span class="o">=</span><span class="s">"&lt;scope 1&gt; &lt;scope 2&gt; etc."</span><span class="p">,</span>
<span class="n">token_endpoint</span><span class="o">=</span><span class="s">"&lt;token endpoint&gt;"</span><span class="p">,</span>
<span class="n">grant_type</span><span class="o">=</span><span class="s">"client_credentials"</span><span class="p">,</span>
<span class="n">token</span><span class="o">=</span><span class="p">{</span><span class="s">"access_token"</span><span class="p">:</span> <span class="bp">None</span><span class="p">,</span> <span class="s">"expires_in"</span><span class="p">:</span> <span class="o">-</span><span class="mi">100</span><span class="p">},</span>
<span class="p">)</span></code></pre></figure>
<p>This session object can be used the same way as the main Requests API:</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">"https://example.com/Users"</span><span class="p">)</span></code></pre></figure>
<p>The difference is, the session will automatically fetch the access token and attach it to every subsequent request.</p>
<p>No need to add an Authorization header to every single API call anymore.</p>
</section></content><author><name></name></author><summary type="html">Lately I've been writing a lot of background jobs that need to interact with different OAuth 2 service providers. Not knowing any better, I coded my own authorization logic using the Requests library. This approach works, but there's a better solution.</summary></entry><entry><title type="html">PHP quiz #3 - operator associativity</title><link href="https://csirmazbendeguz.net/2019/06/16/php-quiz-part-3-associativity.html" rel="alternate" type="text/html" title="PHP quiz #3 - operator associativity" /><published>2019-06-16T00:00:00+01:00</published><updated>2019-06-16T00:00:00+01:00</updated><id>https://csirmazbendeguz.net/2019/06/16/php-quiz-part-3-associativity</id><content type="html" xml:base="https://csirmazbendeguz.net/2019/06/16/php-quiz-part-3-associativity.html"><section class="section">
<h2>Question</h2>
<p>What will this code output?</p>
<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span>
<span class="nv">$x</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="nv">$y</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
<span class="nv">$z</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$z</span> <span class="o">&gt;</span> <span class="nv">$y</span> <span class="o">&gt;</span> <span class="nv">$x</span><span class="p">)</span> <span class="p">{</span>
<span class="k">echo</span> <span class="s1">'true'</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">echo</span> <span class="s1">'false'</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<ul class="answers">
<li class="answer">
<span class="answer-letter">A</span>
<pre>Syntax error</pre>
</li>
<li class="answer">
<span class="answer-letter">B</span>
<pre>true</pre>
</li>
<li class="answer">
<span class="answer-letter">C</span>
<pre>false</pre>
</li>
</ul>
</section>
<section class="section">
<h2>Answer</h2>
<a id="show-answer" href="#">Show the answer</a>
<div id="answer">
<div class="answer">
<span class="answer-letter">A</span>
<pre>Syntax error</pre>
</div>
</div>
</section>
<section class="section">
<h2>Explanation</h2>
<h3>Operator associativity</h3>
<p>Operator associativity decides how operators of equal precedence are grouped.</p>
<table>
<thead>
<tr>
<th>operator</th>
<th>associativity</th>
<th>example</th>
<th>result</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="highlighter-rouge">+</code> (addition)</td>
<td>left</td>
<td><code class="highlighter-rouge">1 + 2 + 3</code></td>
<td><code class="highlighter-rouge">(1 + 2) + 3</code></td>
</tr>
<tr>
<td><code class="highlighter-rouge">=</code> (assignment)</td>
<td>right</td>
<td><code class="highlighter-rouge">$a = $b = $c</code></td>
<td><code class="highlighter-rouge">$a = ($b = $c)</code></td>
</tr>
</tbody>
</table>
</section>
<section class="section">
<h3>Non-associative operators</h3>
<p>
In PHP, however, comparison operators are <em>non-associative</em>.
They cannot be used next to each other.
</p>
<p>The expression <code class="highlighter-rouge">$z > $y > $x</code> is illegal.</p>
<table>
<thead>
<tr>
<th>operator</th>
<th>associativity</th>
<th>example</th>
<th>result</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="highlighter-rouge">&gt;</code> (greater-than)</td>
<td>non-associative</td>
<td><code class="highlighter-rouge">$z &gt; $y &gt; $x</code></td>
<td>syntax error</td>
</tr>
</tbody>
</table>
</section>