-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlessons_multi.py
More file actions
2869 lines (2317 loc) · 109 KB
/
Copy pathlessons_multi.py
File metadata and controls
2869 lines (2317 loc) · 109 KB
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
"""Short courses for the non-Python languages.
Four lessons each: the syntax you need to read anything, the collection type
you will reach for constantly, the language's own defining idea, and the string
handling that interview questions actually demand.
These are deliberately not a replacement for learning the language properly —
they are the shortest path to being able to solve the Interview problems in it.
Theory is written in English and German; French and Spanish fall back to
English, same as anywhere else in the app.
"""
from __future__ import annotations
from dataclasses import dataclass, field
import i18n
import languages as LG
from languages import BOOL, INT, L, STR, Sig
from tasks import Task, case
REGISTRY: list["LangLesson"] = []
@dataclass
class LangLesson:
id: str
language: str
section: dict[str, str]
title: dict[str, str]
theory: dict[str, str]
takeaway: dict[str, str]
example: str
func: str
sig: Sig
statement: dict[str, str]
cases: list[dict]
solution: str
hints: dict[str, list[str]] = field(default_factory=dict)
def build(self) -> Task:
backend = LG.get(self.language)
return Task(
id=self.id,
title=i18n.pick(self.title, self.id),
func=self.func,
statement=i18n.pick(self.statement, "").strip(),
starter=backend.starter(self.func, self.sig),
cases=list(self.cases),
hints=list(i18n.pick(self.hints, []) or []),
solution=self.solution,
difficulty="Easy",
topic="Basics",
source="lesson",
sig=self.sig,
language=self.language,
)
def L_(lesson_id, language, section, title, theory, takeaway, example,
func, sig, statement, cases, solution, hints=None) -> LangLesson:
lesson = LangLesson(
id=f"lesson_{language}_{lesson_id}", language=language, section=section,
title=title, theory=theory, takeaway=takeaway, example=example,
func=func, sig=sig, statement=statement, cases=cases,
solution=solution, hints=hints or {})
REGISTRY.append(lesson)
return lesson
def for_language(language_id: str) -> list[LangLesson]:
return [lesson for lesson in REGISTRY if lesson.language == language_id]
SEC_SYNTAX = {"en": "Syntax", "de": "Syntax", "fr": "Syntaxe", "es": "Sintaxis"}
SEC_DATA = {"en": "Collections", "de": "Sammlungen", "fr": "Collections",
"es": "Colecciones"}
SEC_CORE = {"en": "What makes it different", "de": "Was es besonders macht",
"fr": "Ce qui le distingue", "es": "Lo que lo distingue"}
SEC_STRINGS = {"en": "Strings & interviews", "de": "Strings & Interviews",
"fr": "Chaînes et entretiens", "es": "Cadenas y entrevistas"}
# ===========================================================================
# JAVASCRIPT
# ===========================================================================
L_("syntax", "javascript", SEC_SYNTAX,
{"en": "Values and let/const", "de": "Werte und let/const"},
{"en": """JavaScript has one number type. 7 / 2 is 3.5, and there is no separate integer,
so use Math.floor or the bitwise | 0 when you want truncation.
let count = 0; // can be reassigned
const name = "Ada"; // cannot be reassigned
var old = 1; // function-scoped, from before 2015 — avoid it
`const` does not freeze the contents: const nums = [1]; nums.push(2) is legal.
It only stops you from pointing the name at something else.
Equality has two forms and you want the strict one:
1 == "1" true // converts before comparing — a source of real bugs
1 === "1" false // no conversion; use this one
Values that count as false: false, 0, "", null, undefined, NaN. Everything else
is true, including [] and {} — an empty array is truthy, so check .length.
Template literals build strings with backticks:
`${name} is ${age}`""",
"de": """JavaScript hat nur einen Zahlentyp. 7 / 2 ergibt 3.5, es gibt keine eigene
Ganzzahl — für Abschneiden nimmst du Math.floor oder das bitweise | 0.
let count = 0; // kann neu zugewiesen werden
const name = "Ada"; // kann nicht neu zugewiesen werden
var old = 1; // funktionsweit, von vor 2015 — meide es
`const` friert den Inhalt nicht ein: const nums = [1]; nums.push(2) ist erlaubt.
Es verhindert nur, dass der Name auf etwas anderes zeigt.
Gleichheit gibt es zweimal, und du willst die strikte:
1 == "1" true // wandelt vorher um — eine echte Fehlerquelle
1 === "1" false // keine Umwandlung; nimm diese
Als falsch gelten: false, 0, "", null, undefined, NaN. Alles andere ist wahr,
auch [] und {} — ein leeres Array ist truthy, prüf also .length.
Template-Literale bauen Strings mit Backticks:
`${name} ist ${age}`"""},
{"en": "Use === and const by default. An empty array is truthy — check .length.",
"de": "Nimm standardmäßig === und const. Ein leeres Array ist truthy — prüf .length."},
'''const name = "Ada";
let age = 36;
console.log(`${name} is ${age}`);
console.log("7 / 2 =", 7 / 2, "| floored:", Math.floor(7 / 2));
console.log("7 % 2 =", 7 % 2);
console.log("1 == '1' ->", 1 == "1");
console.log("1 === '1' ->", 1 === "1");
const nums = [3, 1, 4];
nums.push(1); // allowed: const pins the name, not the contents
console.log("nums:", nums, "length:", nums.length);
console.log("[] is truthy:", Boolean([]));
console.log("but [].length is", [].length);
const [first, ...rest] = nums;
console.log("first:", first, "rest:", rest);
''',
"clock", Sig([("seconds", INT)], STR),
{"en": """Write clock(seconds) turning a number of seconds into "H:MM:SS".
clock(3661) -> "1:01:01"
clock(59) -> "0:00:59"
Hours have no leading zero; minutes and seconds always have two digits.
Math.floor gives you whole units and % gives the remainder.""",
"de": """Schreib clock(seconds), das Sekunden in "H:MM:SS" verwandelt.
clock(3661) -> "1:01:01"
clock(59) -> "0:00:59"
Stunden ohne führende Null, Minuten und Sekunden immer zweistellig.
Math.floor liefert ganze Einheiten, % den Rest."""},
[case(3661, "1:01:01"), case(59, "0:00:59"), case(0, "0:00:00"),
case(86399, "23:59:59", hidden=True), case(600, "0:10:00", hidden=True)],
'''function clock(seconds) {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
}
''',
{"en": ["Math.floor(seconds / 3600) gives the hours",
"String(m).padStart(2, \"0\") pads a number to two digits",
"Build the result with a template literal"],
"de": ["Math.floor(seconds / 3600) ergibt die Stunden",
"String(m).padStart(2, \"0\") füllt eine Zahl auf zwei Stellen auf",
"Bau das Ergebnis mit einem Template-Literal"]})
L_("collections", "javascript", SEC_DATA,
{"en": "Arrays, Map and Set", "de": "Arrays, Map und Set"},
{"en": """An array is the workhorse. The methods you will actually use:
nums.push(x) / nums.pop() add / remove at the end O(1)
nums.shift() remove from the front O(n)
nums.slice(1, 3) a copy of part of it
nums.map(f) .filter(f) .reduce(f, start)
nums.includes(x) O(n)
[...nums] a shallow copy
Sorting is the trap. Array.sort compares as TEXT by default:
[10, 9, 1].sort() -> [1, 10, 9] wrong
[10, 9, 1].sort((a, b) => a - b) -> [1, 9, 10] right
For lookups use Set and Map — both O(1), both keep insertion order:
const seen = new Set([1, 2]); seen.has(1) seen.add(3) seen.size
const counts = new Map(); counts.get(k) ?? 0 counts.set(k, v)
A plain object works as a map too, but its keys are always strings, so
obj[1] and obj["1"] are the same entry. Map does not do that.""",
"de": """Das Array ist das Arbeitspferd. Die Methoden, die du wirklich brauchst:
nums.push(x) / nums.pop() hinten anfügen / entfernen O(1)
nums.shift() vorne entfernen O(n)
nums.slice(1, 3) eine Teilkopie
nums.map(f) .filter(f) .reduce(f, start)
nums.includes(x) O(n)
[...nums] flache Kopie
Sortieren ist die Falle. Array.sort vergleicht standardmäßig als TEXT:
[10, 9, 1].sort() -> [1, 10, 9] falsch
[10, 9, 1].sort((a, b) => a - b) -> [1, 9, 10] richtig
Für Nachschlagen nimm Set und Map — beide O(1), beide mit Einfügereihenfolge:
const seen = new Set([1, 2]); seen.has(1) seen.add(3) seen.size
const counts = new Map(); counts.get(k) ?? 0 counts.set(k, v)
Ein einfaches Objekt geht auch als Map, aber seine Schlüssel sind immer
Strings — obj[1] und obj["1"] sind derselbe Eintrag. Map macht das nicht."""},
{"en": "sort() compares as text. Always pass (a, b) => a - b for numbers.",
"de": "sort() vergleicht als Text. Gib für Zahlen immer (a, b) => a - b mit."},
'''const nums = [10, 9, 1, 4];
console.log("default sort:", [...nums].sort());
console.log("numeric sort:", [...nums].sort((a, b) => a - b));
console.log("doubled:", nums.map(n => n * 2));
console.log("evens:", nums.filter(n => n % 2 === 0));
console.log("sum:", nums.reduce((a, b) => a + b, 0));
const seen = new Set(nums);
console.log("set:", seen, "has 9:", seen.has(9), "size:", seen.size);
const counts = new Map();
for (const ch of "mississippi") counts.set(ch, (counts.get(ch) ?? 0) + 1);
console.log("counts:", counts);
const byCount = [...counts.entries()].sort((a, b) => b[1] - a[1]);
console.log("most common:", byCount[0]);
''',
"second_largest", Sig([("nums", L(INT))], INT),
{"en": """Write second_largest(nums) returning the second largest DISTINCT value.
Return -1 if there are fewer than two distinct values.
second_largest([3, 1, 4, 4, 5]) -> 4
second_largest([7, 7, 7]) -> -1
A Set removes the duplicates. Remember that sort needs a comparator.""",
"de": """Schreib second_largest(nums), das den zweitgrößten VERSCHIEDENEN Wert liefert.
Gib -1 zurück, wenn es weniger als zwei verschiedene Werte gibt.
second_largest([3, 1, 4, 4, 5]) -> 4
second_largest([7, 7, 7]) -> -1
Ein Set entfernt die Duplikate. Denk daran, dass sort einen Vergleicher braucht."""},
[case([3, 1, 4, 4, 5], 4), case([7, 7, 7], -1), case([2, 1], 1),
case([], -1, hidden=True), case([-5, -2, -9, -2], -5, hidden=True)],
'''function second_largest(nums) {
const distinct = [...new Set(nums)].sort((a, b) => a - b);
return distinct.length < 2 ? -1 : distinct[distinct.length - 2];
}
''',
{"en": ["new Set(nums) throws away the duplicates",
"Spread it back into an array with [...set]",
"sort((a, b) => a - b), then take the second from the end"],
"de": ["new Set(nums) wirft die Duplikate weg",
"Mit [...set] wieder in ein Array ausbreiten",
"sort((a, b) => a - b), dann das zweitletzte nehmen"]})
L_("functions", "javascript", SEC_CORE,
{"en": "Functions are values", "de": "Funktionen sind Werte"},
{"en": """A function in JavaScript is an ordinary value: you can store it, pass it and
return it. That is why map/filter/reduce look the way they do.
function named(a) { return a * 2; } // hoisted
const arrow = a => a * 2; // concise, no own `this`
const block = (a, b) => { return a + b; };
A closure is a function that remembers the variables around it, even after the
outer call has finished. This is the single most-asked JavaScript concept:
function counter() {
let n = 0;
return () => ++n; // still sees n
}
const next = counter();
next(); next(); // 1, then 2
Useful shorthands you will read everywhere:
a ?? b b only when a is null or undefined (0 and "" survive)
a?.b undefined instead of throwing when a is null/undefined
const {x, y} = point; destructuring
f(...args) spread""",
"de": """Eine Funktion ist in JavaScript ein ganz normaler Wert: du kannst sie
speichern, übergeben und zurückgeben. Deshalb sehen map/filter/reduce so aus.
function named(a) { return a * 2; } // wird hochgezogen
const arrow = a => a * 2; // kurz, ohne eigenes `this`
const block = (a, b) => { return a + b; };
Eine Closure ist eine Funktion, die sich die Variablen um sie herum merkt —
auch nachdem der äußere Aufruf beendet ist. Das ist das meistgefragte
JavaScript-Konzept überhaupt:
function counter() {
let n = 0;
return () => ++n; // sieht n weiterhin
}
const next = counter();
next(); next(); // 1, dann 2
Kurzschreibweisen, die dir überall begegnen:
a ?? b b nur, wenn a null oder undefined ist (0 und "" bleiben)
a?.b undefined statt Absturz, wenn a null/undefined ist
const {x, y} = point; Destrukturierung
f(...args) Spread"""},
{"en": "A closure keeps the variables it was created with. That is the whole trick.",
"de": "Eine Closure behält die Variablen, mit denen sie entstand. Das ist der ganze Trick."},
'''const double = n => n * 2;
const apply = (fn, value) => fn(fn(value));
console.log("apply twice:", apply(double, 3));
function counter() {
let n = 0;
return () => ++n;
}
const next = counter();
console.log(next(), next(), next());
const other = counter(); // its own private n
console.log("a fresh counter:", other());
console.log("?? keeps 0:", 0 ?? 99, "| || does not:", 0 || 99);
const point = { x: 3, y: 4 };
const { x, y } = point;
console.log("destructured:", x, y);
console.log("optional chain on missing:", point.z?.deep);
const nums = [5, 3, 9];
console.log("spread into Math.max:", Math.max(...nums));
''',
"running_max", Sig([("nums", L(INT))], L(INT)),
{"en": """Write running_max(nums) returning the biggest value seen so far at every
position. The result has the same length as the input.
running_max([1, 3, 2, 5]) -> [1, 3, 3, 5]
running_max([]) -> []
Keep one variable outside the loop and update it as you go.""",
"de": """Schreib running_max(nums), das an jeder Position den bisher größten Wert
zurückgibt. Das Ergebnis hat dieselbe Länge wie die Eingabe.
running_max([1, 3, 2, 5]) -> [1, 3, 3, 5]
running_max([]) -> []
Führ eine Variable außerhalb der Schleife und aktualisier sie unterwegs."""},
[case([1, 3, 2, 5], [1, 3, 3, 5]), case([], []), case([4], [4]),
case([5, 4, 3], [5, 5, 5], hidden=True),
case([-3, -1, -2], [-3, -1, -1], hidden=True)],
'''function running_max(nums) {
const out = [];
let best = null;
for (const n of nums) {
best = best === null ? n : Math.max(best, n);
out.push(best);
}
return out;
}
''',
{"en": ["Hold the best value in a variable outside the loop",
"Push the accumulator AFTER updating it",
"The empty input must give an empty array"],
"de": ["Halt den besten Wert in einer Variablen außerhalb der Schleife",
"Häng den Akkumulator an, NACHDEM du ihn aktualisiert hast",
"Die leere Eingabe muss ein leeres Array ergeben"]})
L_("strings", "javascript", SEC_STRINGS,
{"en": "Strings", "de": "Zeichenketten"},
{"en": """Strings are immutable: every method returns a new one.
s.length not a method
s[0] or s.charAt(0)
s.slice(1, 4) s.slice(-3) takes the last three
s.toUpperCase() / .toLowerCase()
s.trim() .split(" ") .includes("x") .indexOf("x")
arr.join("-")
There is no built-in reverse for strings — go through an array:
[...s].reverse().join("")
Building a string in a loop with += is fine in modern engines, but collecting
into an array and joining once is still the habit interviewers expect.
For counting characters, a Map beats an object because it keeps non-string keys
and has a clean .get default with ?? 0.""",
"de": """Strings sind unveränderlich: jede Methode gibt einen neuen zurück.
s.length keine Methode
s[0] oder s.charAt(0)
s.slice(1, 4) s.slice(-3) nimmt die letzten drei
s.toUpperCase() / .toLowerCase()
s.trim() .split(" ") .includes("x") .indexOf("x")
arr.join("-")
Für Strings gibt es kein eingebautes reverse — geh über ein Array:
[...s].reverse().join("")
Einen String in einer Schleife mit += zu bauen ist in modernen Engines in
Ordnung, aber in ein Array zu sammeln und einmal zu joinen ist weiterhin die
Gewohnheit, die Interviewer erwarten.
Zum Zeichenzählen ist eine Map besser als ein Objekt: sie behält
Nicht-String-Schlüssel und hat mit ?? 0 einen sauberen Vorgabewert."""},
{"en": "No string reverse — spread into an array first. Count with a Map.",
"de": "Kein reverse für Strings — erst in ein Array ausbreiten. Zählen mit einer Map."},
'''const s = " Hello, World ";
console.log(JSON.stringify(s.trim()));
console.log("upper:", s.trim().toUpperCase());
console.log("split:", s.trim().split(", "));
console.log("last three:", s.trim().slice(-3));
console.log("reversed:", [...s.trim()].reverse().join(""));
const counts = new Map();
for (const ch of "mississippi") counts.set(ch, (counts.get(ch) ?? 0) + 1);
console.log("counts:", [...counts]);
const firstUnique = [..."swiss"].find(ch =>
[..."swiss"].filter(c => c === ch).length === 1);
console.log("first unique in 'swiss':", firstUnique);
const parts = [];
for (let i = 0; i < 5; i++) parts.push(i * i);
console.log(parts.join(", "));
''',
"normalise", Sig([("text", STR)], STR),
{"en": """Write normalise(text) that cleans up a messy name:
* trim both ends
* collapse any run of inner whitespace to a single space
* capitalise each word: first letter upper, the rest lower
normalise(" aDA LOVElace ") -> "Ada Lovelace"
normalise(" ") -> ""
split(/\\s+/) on a trimmed string gives you the words.""",
"de": """Schreib normalise(text), das einen unordentlichen Namen aufräumt:
* an beiden Enden trimmen
* jede Folge innerer Leerzeichen auf eines reduzieren
* jedes Wort groß schreiben: erster Buchstabe groß, Rest klein
normalise(" aDA LOVElace ") -> "Ada Lovelace"
normalise(" ") -> ""
split(/\\s+/) auf einem getrimmten String liefert dir die Wörter."""},
[case(" aDA LOVElace ", "Ada Lovelace"),
case("guido van ROSSUM", "Guido Van Rossum"), case(" ", ""),
case("a", "A", hidden=True),
case(" linus TORVALDS ", "Linus Torvalds", hidden=True)],
'''function normalise(text) {
const words = text.trim().split(/\\s+/).filter(Boolean);
return words
.map(w => w[0].toUpperCase() + w.slice(1).toLowerCase())
.join(" ");
}
''',
{"en": ["trim() first, then split on /\\s+/",
"filter(Boolean) drops the empty piece an all-space string leaves",
"w[0].toUpperCase() + w.slice(1).toLowerCase() capitalises one word"],
"de": ["Erst trim(), dann an /\\s+/ trennen",
"filter(Boolean) wirft das leere Stück weg, das ein reiner Leerstring lässt",
"w[0].toUpperCase() + w.slice(1).toLowerCase() macht ein Wort groß"]})
# ===========================================================================
# JAVA
# ===========================================================================
L_("syntax", "java", SEC_SYNTAX,
{"en": "Types and integer maths", "de": "Typen und Ganzzahlrechnung"},
{"en": """Java declares the type of everything. That is the biggest change coming from
Python, and it is also what lets the compiler catch your mistakes.
int count = 0;
long big = 3_000_000_000L; // int stops at about 2.1 billion
double ratio = 7 / 2.0;
boolean ready = true;
String name = "Ada"; // capital S — it is a class
var guessed = 42; // the compiler infers int
Integer division truncates and does NOT become a fraction:
7 / 2 -> 3 both operands are int
7 / 2.0 -> 3.5 one is a double, so the result is
7 % 2 -> 1
That first line bites everyone once. If you want a real quotient, make one side
a double.
Overflow is silent: int arithmetic wraps around instead of raising. When a
problem multiplies values, use long — the exercises here already do.
Every program lives in a class, and printing is System.out.println(...).""",
"de": """Java deklariert den Typ von allem. Das ist die größte Umstellung von Python
her, und genau das lässt den Compiler deine Fehler finden.
int count = 0;
long big = 3_000_000_000L; // int endet bei etwa 2,1 Milliarden
double ratio = 7 / 2.0;
boolean ready = true;
String name = "Ada"; // großes S — es ist eine Klasse
var guessed = 42; // der Compiler folgert int
Ganzzahldivision schneidet ab und wird NICHT zum Bruch:
7 / 2 -> 3 beide Operanden sind int
7 / 2.0 -> 3.5 einer ist double, also ist es das Ergebnis auch
7 % 2 -> 1
Diese erste Zeile erwischt jeden einmal. Willst du einen echten Quotienten,
mach eine Seite zu double.
Überlauf passiert lautlos: int-Rechnung läuft über, statt einen Fehler zu
werfen. Wenn eine Aufgabe multipliziert, nimm long — die Übungen hier tun das.
Jedes Programm lebt in einer Klasse, und ausgegeben wird mit
System.out.println(...)."""},
{"en": "int / int stays an int. Use long when anything gets multiplied.",
"de": "int / int bleibt int. Nimm long, sobald irgendetwas multipliziert wird."},
'''public class Main {
public static void main(String[] args) {
int count = 7;
long big = 3_000_000_000L;
String name = "Ada";
System.out.println(name + " has " + count);
System.out.println("7 / 2 = " + (7 / 2));
System.out.println("7 / 2.0 = " + (7 / 2.0));
System.out.println("7 % 2 = " + (7 % 2));
System.out.println("big = " + big);
int overflows = Integer.MAX_VALUE;
System.out.println("int max + 1 = " + (overflows + 1));
System.out.println("as long = " + ((long) overflows + 1));
System.out.printf("%s is %d, padded %05d%n", name, count, count);
System.out.println(String.format("%.3f", 2.0 / 3));
}
}
''',
"clock", Sig([("seconds", INT)], STR),
{"en": """Write clock(seconds) turning a number of seconds into "H:MM:SS".
clock(3661) -> "1:01:01"
clock(59) -> "0:00:59"
Hours have no leading zero; minutes and seconds always have two digits.
Integer division gives whole units, % gives the remainder, and
String.format("%02d", n) pads to two digits.""",
"de": """Schreib clock(seconds), das Sekunden in "H:MM:SS" verwandelt.
clock(3661) -> "1:01:01"
clock(59) -> "0:00:59"
Stunden ohne führende Null, Minuten und Sekunden immer zweistellig.
Ganzzahldivision liefert ganze Einheiten, % den Rest, und
String.format("%02d", n) füllt auf zwei Stellen auf."""},
[case(3661, "1:01:01"), case(59, "0:00:59"), case(0, "0:00:00"),
case(86399, "23:59:59", hidden=True), case(600, "0:10:00", hidden=True)],
'''class Solution {
static String clock(long seconds) {
long h = seconds / 3600;
long m = (seconds % 3600) / 60;
long s = seconds % 60;
return String.format("%d:%02d:%02d", h, m, s);
}
}
''',
{"en": ["seconds / 3600 already truncates — that is the hours",
"(seconds % 3600) / 60 gives the minutes",
"String.format(\"%d:%02d:%02d\", h, m, s) does the padding"],
"de": ["seconds / 3600 schneidet schon ab — das sind die Stunden",
"(seconds % 3600) / 60 ergibt die Minuten",
"String.format(\"%d:%02d:%02d\", h, m, s) füllt auf"]})
L_("collections", "java", SEC_DATA,
{"en": "Arrays, List, Map, Set", "de": "Arrays, List, Map, Set"},
{"en": """An array has a fixed length and a bare syntax:
long[] nums = {3, 1, 4};
nums.length a field, not a method
Arrays.sort(nums); sorts in place
Arrays.toString(nums) because printing an array shows its address
For anything that grows, use the collections:
List<Long> list = new ArrayList<>();
list.add(5L); list.get(0); list.size();
Map<String, Integer> counts = new HashMap<>();
counts.merge(key, 1, Integer::sum); the idiomatic counter
counts.getOrDefault(key, 0)
Set<Long> seen = new HashSet<>();
seen.add(x) returns false if it was already there
Generics cannot hold primitives, so it is List<Long>, not List<long>. Java
boxes and unboxes for you, which is convenient and occasionally slow.
The trap: == on objects compares identity, not contents. Use .equals for
String and boxed numbers. For arrays use Arrays.equals.""",
"de": """Ein Array hat feste Länge und eine nackte Syntax:
long[] nums = {3, 1, 4};
nums.length ein Feld, keine Methode
Arrays.sort(nums); sortiert an Ort und Stelle
Arrays.toString(nums) denn ein Array auszugeben zeigt seine Adresse
Für alles, was wächst, nimm die Collections:
List<Long> list = new ArrayList<>();
list.add(5L); list.get(0); list.size();
Map<String, Integer> counts = new HashMap<>();
counts.merge(key, 1, Integer::sum); der idiomatische Zähler
counts.getOrDefault(key, 0)
Set<Long> seen = new HashSet<>();
seen.add(x) liefert false, wenn es schon drin war
Generics können keine primitiven Typen halten, also List<Long>, nicht
List<long>. Java packt für dich ein und aus — bequem und gelegentlich langsam.
Die Falle: == vergleicht bei Objekten die Identität, nicht den Inhalt. Nimm
.equals für String und eingepackte Zahlen, Arrays.equals für Arrays."""},
{"en": "== compares identity for objects. Use .equals, and Arrays.equals for arrays.",
"de": "== vergleicht bei Objekten die Identität. Nimm .equals, für Arrays Arrays.equals."},
'''import java.util.*;
public class Main {
public static void main(String[] args) {
long[] nums = {10, 9, 1, 4};
Arrays.sort(nums);
System.out.println("sorted: " + Arrays.toString(nums));
List<Long> list = new ArrayList<>();
for (long n : nums) list.add(n * 2);
System.out.println("doubled: " + list);
Map<Character, Integer> counts = new HashMap<>();
for (char c : "mississippi".toCharArray())
counts.merge(c, 1, Integer::sum);
System.out.println("counts: " + counts);
Set<Long> seen = new HashSet<>();
for (long n : new long[]{1, 2, 1}) {
if (!seen.add(n)) System.out.println("duplicate: " + n);
}
String a = "ab", b = "a" + "b";
System.out.println("a.equals(b): " + a.equals(b));
System.out.println("new String comparison with ==: "
+ (a == new String("ab")));
}
}
''',
"second_largest", Sig([("nums", L(INT))], INT),
{"en": """Write second_largest(nums) returning the second largest DISTINCT value.
Return -1 if there are fewer than two distinct values.
second_largest([3, 1, 4, 4, 5]) -> 4
second_largest([7, 7, 7]) -> -1
A TreeSet keeps values sorted and unique, which does most of the work.""",
"de": """Schreib second_largest(nums), das den zweitgrößten VERSCHIEDENEN Wert liefert.
Gib -1 zurück, wenn es weniger als zwei verschiedene Werte gibt.
second_largest([3, 1, 4, 4, 5]) -> 4
second_largest([7, 7, 7]) -> -1
Ein TreeSet hält Werte sortiert und eindeutig — das erledigt die meiste Arbeit."""},
[case([3, 1, 4, 4, 5], 4), case([7, 7, 7], -1), case([2, 1], 1),
case([], -1, hidden=True), case([-5, -2, -9, -2], -5, hidden=True)],
'''import java.util.TreeSet;
class Solution {
static long second_largest(long[] nums) {
TreeSet<Long> distinct = new TreeSet<>();
for (long n : nums) distinct.add(n);
if (distinct.size() < 2) return -1;
distinct.pollLast();
return distinct.last();
}
}
''',
{"en": ["A TreeSet is sorted and holds each value once",
"pollLast() removes and returns the biggest",
"Check size() before you reach for anything"],
"de": ["Ein TreeSet ist sortiert und hält jeden Wert einmal",
"pollLast() entfernt das größte und gibt es zurück",
"Prüf size(), bevor du auf etwas zugreifst"]})
L_("classes", "java", SEC_CORE,
{"en": "Classes and objects", "de": "Klassen und Objekte"},
{"en": """Everything in Java lives in a class, so knowing how to write a small one is not
optional even for a coding test.
class Student {
final String name; // final = assigned once
int score;
Student(String name, int score) { // constructor: same name, no return
this.name = name;
this.score = score;
}
boolean beats(Student other) { return this.score > other.score; }
@Override
public String toString() { return name + "(" + score + ")"; }
}
`static` means "belongs to the class, not to an instance" — that is why the
exercises here are static methods: the harness calls Solution.method(...)
without creating an object.
A record is the short form for a plain data holder, and gives you the
constructor, equals, hashCode and toString for free:
record Point(int x, int y) {}""",
"de": """In Java lebt alles in einer Klasse — eine kleine schreiben zu können ist also
auch für einen Coding-Test nicht optional.
class Student {
final String name; // final = einmal zugewiesen
int score;
Student(String name, int score) { // Konstruktor: gleicher Name, kein return
this.name = name;
this.score = score;
}
boolean beats(Student other) { return this.score > other.score; }
@Override
public String toString() { return name + "(" + score + ")"; }
}
`static` heißt „gehört zur Klasse, nicht zu einer Instanz" — deshalb sind die
Übungen hier statische Methoden: der Testrahmen ruft Solution.methode(...) auf,
ohne ein Objekt zu erzeugen.
Ein record ist die Kurzform für einen reinen Datenhalter und schenkt dir
Konstruktor, equals, hashCode und toString:
record Point(int x, int y) {}"""},
{"en": "static belongs to the class; this.field is the instance. record is the short form.",
"de": "static gehört zur Klasse, this.feld zur Instanz. record ist die Kurzform."},
'''import java.util.*;
public class Main {
record Point(int x, int y) {}
static class Student {
final String name;
int score;
Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() { return name + "(" + score + ")"; }
}
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Ada", 92));
students.add(new Student("Linus", 78));
students.add(new Student("Grace", 99));
students.sort(Comparator.comparingInt(s -> -s.score));
System.out.println("by score: " + students);
System.out.println("winner: " + students.get(0).name);
Point p = new Point(3, 4);
System.out.println("record prints itself: " + p);
System.out.println("and compares by value: " + p.equals(new Point(3, 4)));
}
}
''',
"top_scorer", Sig([("names", L(STR)), ("scores", L(INT))], STR),
{"en": """Two lists of the same length: names[i] scored scores[i]. Return the name with
the highest score. If several tie, return the one that comes first in the list.
Return "" for empty input.
top_scorer(["Ada", "Linus"], [92, 78]) -> "Ada"
Write it however you like — but this is a good place to try a small helper
class, because that is what Java expects of you.""",
"de": """Zwei gleich lange Listen: names[i] hat scores[i] Punkte. Gib den Namen mit der
höchsten Punktzahl zurück. Bei Gleichstand den, der zuerst in der Liste steht.
Für leere Eingaben "".
top_scorer(["Ada", "Linus"], [92, 78]) -> "Ada"
Schreib es, wie du willst — aber hier lohnt sich eine kleine Hilfsklasse, denn
genau das erwartet Java von dir."""},
[case((["Ada", "Linus"], [92, 78]), "Ada"),
case((["Ada", "Grace"], [92, 99]), "Grace"),
case(([], []), ""),
case((["a", "b", "c"], [5, 5, 1]), "a", hidden=True),
case((["solo"], [0]), "solo", hidden=True)],
'''class Solution {
static String top_scorer(String[] names, long[] scores) {
if (names.length == 0) return "";
int best = 0;
for (int i = 1; i < names.length; i++) {
if (scores[i] > scores[best]) best = i;
}
return names[best];
}
}
''',
{"en": ["Track the index of the best score, not the score itself",
"Use a strict > so the earliest of a tie wins",
"Guard the empty input before touching index 0"],
"de": ["Merk dir den Index der besten Punktzahl, nicht die Punktzahl",
"Nimm ein striktes >, damit bei Gleichstand der frühere gewinnt",
"Fang die leere Eingabe ab, bevor du auf Index 0 zugreifst"]})
L_("strings", "java", SEC_STRINGS,
{"en": "Strings and StringBuilder", "de": "Strings und StringBuilder"},
{"en": """String is immutable. Every "change" makes a new object, which is why building
one in a loop with += is O(n^2) and why StringBuilder exists.
s.length() a method here, unlike arrays
s.charAt(0) there is no s[0]
s.substring(1, 4) start inclusive, end exclusive
s.toUpperCase() .trim() .isEmpty() .contains("x") .indexOf("x")
s.split("\\\\s+") takes a REGULAR EXPRESSION, not a plain string
String.join("-", parts)
StringBuilder sb = new StringBuilder();
sb.append("x");
sb.reverse();
sb.toString();
Two traps worth remembering: split takes a regex, so split(".") splits on every
character and gives you nothing; and comparing with == checks identity, so
always use .equals for text.""",
"de": """String ist unveränderlich. Jede „Änderung" erzeugt ein neues Objekt — deshalb
ist der Aufbau in einer Schleife mit += O(n^2), und deshalb gibt es
StringBuilder.
s.length() hier eine Methode, anders als bei Arrays
s.charAt(0) ein s[0] gibt es nicht
s.substring(1, 4) Start dabei, Ende nicht
s.toUpperCase() .trim() .isEmpty() .contains("x") .indexOf("x")
s.split("\\\\s+") nimmt einen REGULÄREN AUSDRUCK, keinen einfachen String
String.join("-", parts)
StringBuilder sb = new StringBuilder();
sb.append("x");
sb.reverse();
sb.toString();
Zwei Fallen zum Merken: split nimmt einen Regex, split(".") trennt also an
jedem Zeichen und liefert nichts; und == vergleicht die Identität — für Text
also immer .equals."""},
{"en": "split takes a regex. Use .equals for text and StringBuilder in loops.",
"de": "split nimmt einen Regex. Für Text .equals, in Schleifen StringBuilder."},
'''import java.util.*;
public class Main {
public static void main(String[] args) {
String s = " Hello, World ";
System.out.println("[" + s.trim() + "]");
System.out.println("upper: " + s.trim().toUpperCase());
System.out.println("substring: " + s.trim().substring(0, 5));
System.out.println("split: " + Arrays.toString(s.trim().split(",\\\\s*")));
System.out.println("joined: " + String.join("-", "a", "b", "c"));
System.out.println("reversed: "
+ new StringBuilder("interview").reverse());
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) sb.append(i * i).append(" ");
System.out.println("built: " + sb.toString().trim());
System.out.println("split(\\".\\") gives "
+ "hello".split("\\\\.").length + " part(s) with an escaped dot");
}
}
''',
"normalise", Sig([("text", STR)], STR),
{"en": """Write normalise(text) that cleans up a messy name:
* trim both ends
* collapse any run of inner whitespace to a single space
* capitalise each word: first letter upper, the rest lower
normalise(" aDA LOVElace ") -> "Ada Lovelace"
normalise(" ") -> ""
trim() then split("\\\\s+") gives you the words — mind the regex.""",
"de": """Schreib normalise(text), das einen unordentlichen Namen aufräumt:
* an beiden Enden trimmen
* jede Folge innerer Leerzeichen auf eine reduzieren
* jedes Wort groß schreiben: erster Buchstabe groß, Rest klein
normalise(" aDA LOVElace ") -> "Ada Lovelace"
normalise(" ") -> ""
trim() und dann split("\\\\s+") liefert die Wörter — achte auf den Regex."""},
[case(" aDA LOVElace ", "Ada Lovelace"),
case("guido van ROSSUM", "Guido Van Rossum"), case(" ", ""),
case("a", "A", hidden=True),
case(" linus TORVALDS ", "Linus Torvalds", hidden=True)],
'''class Solution {
static String normalise(String text) {
String trimmed = text.trim();
if (trimmed.isEmpty()) return "";
StringBuilder out = new StringBuilder();
for (String word : trimmed.split("\\\\s+")) {
if (out.length() > 0) out.append(' ');
out.append(Character.toUpperCase(word.charAt(0)));
out.append(word.substring(1).toLowerCase());
}
return out.toString();
}
}
''',
{"en": ["trim() first, and return early when nothing is left",
"split(\"\\\\s+\") on the trimmed text gives the words",
"Build the answer in a StringBuilder"],
"de": ["Erst trim(), und früh zurückkehren, wenn nichts übrig bleibt",
"split(\"\\\\s+\") auf dem getrimmten Text liefert die Wörter",
"Bau die Antwort in einem StringBuilder"]})
# ===========================================================================
# C#
# ===========================================================================
L_("syntax", "csharp", SEC_SYNTAX,
{"en": "Types and var", "de": "Typen und var"},
{"en": """C# is statically typed, but `var` lets the compiler work the type out for you.
The variable still has one fixed type — this is not Python's dynamism.
int count = 0;
long big = 3_000_000_000L;
double ratio = 7 / 2.0;
bool ready = true;
string name = "Ada"; // lower-case string, an alias for String
var inferred = 42; // int, decided at compile time
Integer division truncates, exactly as in Java:
7 / 2 -> 3
7 / 2.0 -> 3.5
7 % 2 -> 1
String interpolation is the nicest of the C-family:
$"{name} is {count}" and $"{value,5:F2}" aligns and rounds
Two things you will meet constantly: `?` marks a nullable type (int? may be
null), and `??` supplies a fallback when something is null.""",
"de": """C# ist statisch typisiert, aber `var` lässt den Compiler den Typ herleiten. Die
Variable hat trotzdem einen festen Typ — das ist nicht Pythons Dynamik.
int count = 0;
long big = 3_000_000_000L;
double ratio = 7 / 2.0;
bool ready = true;
string name = "Ada"; // kleingeschriebenes string, Alias für String
var inferred = 42; // int, zur Übersetzungszeit entschieden
Ganzzahldivision schneidet ab, genau wie in Java:
7 / 2 -> 3
7 / 2.0 -> 3.5
7 % 2 -> 1
String-Interpolation ist die angenehmste der C-Familie:
$"{name} ist {count}" und $"{value,5:F2}" richtet aus und rundet