Codewars Lösung | Cubic Tap Code


coden
Codewars. Achieve mastery through challenge.
Daniel Kaser|10. Oktober 2024
6 min.

Inhalt

  1. Die Fakten
  2. Beschreibung
  3. Lösung
    1. Pseudo-Code
    2. Code
  4. Feedback

Die Fakten:

Plattform:codewars.com
Name:Cubic Tap Code
Level:6 kyu
Sprache:TypeScript

Beschreibung:

Cubic Tap Code

This works similarly to Tap Code except instead of being mapped onto a 5x5 square, letters are mapped onto a 3x3x3 cube, left to right, top to bottom, front to back with space being the 27th "letter". Letters are represented by a series of taps (represented as dots .) and pauses (represented by spaces ), for example A is represented as . . . (first column, first row, first layer) and is represented as ... ... ... (third column, third row, third layer).

For reference the three layers of the cube are as follows (underscore represents space):

1  1  2  3 
1  A  B  C
2  D  E  F
3  G  H  I

2  1  2  3 
1  J  K  L
2  M  N  O
3  P  Q  R

3  1  2  3 
1  S  T  U
2  V  W  X
3  Y  Z  _

Your task (should you choose to accept it)

Create two functions encode() and decode(), to encode and decode strings to and from cubic tap code.

Input

encode() takes a string of uppercase letters and spaces and outputs a string of dots and spaces. decode() takes a string of dots and spaces and outputs a string of uppercase letters and spaces. All inputs will be valid.

Examples

encode("N") => ".. .. .."
encode("TEST") => ".. . ... .. .. . . . ... .. . ..."
encode("HELLO WORLD") => ".. ... . .. .. . ... . .. ... . .. ... .. .. ... ... ... .. .. ... ... .. .. ... ... .. ... . .. . .. ."

decode(".. .. ..") => "N"
decode(".. . ... .. .. . . . ... .. . ...") => "TEST"
decode(".. ... . .. .. . ... . .. ... . .. ... .. .. ... ... ... .. .. ... ... .. .. ... ... .. ... . .. . .. .") => "HELLO WORLD"

Quelle: codewars.com

Lösung

Pseudo-Code

Wie immer gibt's reichlich Varianten, hier ist eine meiner.

Erst die Lösungsschritte in Pseudo-Code. Los geht’s:

Lösungsschritte
Schritt 1

Zuerst können wir uns ein Objekt erstellen, das alle Buchstaben auf den Tap-Code umschlüsselt.

Schritt 2

Für die encode-Funktion können wir dann einfach den Buchstaben in unserem Objekt nachschlagen und ausgeben.

Schritt 3

Für die decode-Funktion können wir unser Objekt mit Buchstaben und Tap-Codes einfach umdrehen. D.h., keys und values vertauschen.

Schritt 4

Dann müssen wir aus dem Input-String Dreier-Gruppen erstellen.

Schritt 5

Diese Dreier-Gruppen können wir dann im umgedrehten Objekt nachschlagen.

Schritt 6

Den jeweils erhaltenen Buchstaben nur noch ans Ergebnis anhängen.

Schritt 7

Am Ende alles ausgeben - fertig!

Code

Geil. Übersetzen wir unseren Pseudo-Code in TypeScript:

Lösungsschritte
Zuerst mein Objekt mit Buchstaben und Tap-Codes:
const map: { [key: string]: string } = {
  A: ". . .",
  B: ".. . .",
  C: "... . .",
  D: ". .. .",
  E: ".. .. .",
  F: "... .. .",
  G: ". ... .",
  H: ".. ... .",
  I: "... ... .",
  J: ". . ..",
  K: ".. . ..",
  L: "... . ..",
  M: ". .. ..",
  N: ".. .. ..",
  O: "... .. ..",
  P: ". ... ..",
  Q: ".. ... ..",
  R: "... ... ..",
  S: ". . ...",
  T: ".. . ...",
  U: "... . ...",
  V: ". .. ...",
  W: ".. .. ...",
  X: "... .. ...",
  Y: ". ... ...",
  Z: ".. ... ...",
  " ": "... ... ...",
};
Als nächstes speichere ich mir schon mal das umgedrehte Objekt in einer Variablen:
const reversedMap = Object.fromEntries(
  Object.entries(map).map(([key, value]) => [value, key]),
);

Object.entries() erstellt ein Array aus einem Objekt durch das wir dann mappen können. Das Array habe ich direkt deconstructed. Das macht es schön kurz und lesbar.

Object.fromEntries() wiederum erstellt ein Objekt aus einem Array.

Der Inhalt des umgedrehten Objekts sieht dann also so aus:

{
  '. . .': 'A',
  '.. . .': 'B',
  '... . .': 'C',
  '. .. .': 'D',
  '.. .. .': 'E',
  '... .. .': 'F',
  '. ... .': 'G',
  '.. ... .': 'H',
  '... ... .': 'I',
  '. . ..': 'J',
  '.. . ..': 'K',
  '... . ..': 'L',
  '. .. ..': 'M',
  '.. .. ..': 'N',
  '... .. ..': 'O',
  '. ... ..': 'P',
  '.. ... ..': 'Q',
  '... ... ..': 'R',
  '. . ...': 'S',
  '.. . ...': 'T',
  '... . ...': 'U',
  '. .. ...': 'V',
  '.. .. ...': 'W',
  '... .. ...': 'X',
  '. ... ...': 'Y',
  '.. ... ...': 'Z',
  '... ... ...': ' '
}

Dann können wir uns an die encode-Funktion machen.

Die erste Zeile meiner encode-Funktion:
export function encode(str: string): string {
Zuerst wandeln wir den Input-String in ein Array um:
  return str.split("");

Ich behaupte, wir können hier chainen. Darum setze ich schon mal das return davor.

Dann der Loop für das Nachschlagen der Buchstaben bzw. Tap-Codes:
    .map((char) => map[char])

Da wir für jeden Buchstaben genau einen Tap-Code zurückbekommen wollen, eignet sich hier wunderbar .map().

Zum Schluss nur noch das Array wieder zurück in einen String umwandeln:
    .join(" ");
}

Das war schon die encode-Funktion. Weiter geht’s mit der decode-Funktion.

Die erste Zeile meiner decode-Funktion:
export function decode(str: string): string {
Hier brauchen wir als Erstes eine Variable in der wir alle entschlüsselten Buchstaben speichern:
  const chars: string[] = [];
Außerdem erstelle ich mir noch eine Variable in der ich die Dreier-Gruppen zusammenstelle:
  let triple: string[] = [];
Dann der Loop durch Punkt-Gruppen:
  str.split(" ").forEach((dots, i) => {
Jede Punkt-Gruppe hängen wir an unsere Variable für die Dreier-Gruppen:
    triple.push(dots);
Dann prüfen wir, ob die Dreier-Gruppe komplett ist, also 3 Punkt-Gruppen enthält:
    if (i % 3 === 2) {

Das mache ich hier über den Index. Es ginge aber natürlich auch über die Array-Länge 😉

Wenn die Dreier-Gruppe komplett ist, schlagen wir sie in unserem umgedrehten Objekt (reversedMap) nach:
      const charDots = triple.join(" ");
      const char = reversedMap[charDots];
Den erhaltenen Buchstaben hängen wir dann an unser chars-Array:
      chars.push(char);
      triple = [];
    }
  });

Danach setzen wir noch das Array für die aktuelle Dreier-Gruppe zurück, damit wir die nächste Dreier-Gruppe darin sammeln können.

Zum Schluss nur noch das chars-Array als String zurückgeben:
  return chars.join("");
}
Voilá! 💪

Fragen?

Komplettlösung
const map: { [key: string]: string } = {
  A: ". . .",
  B: ".. . .",
  C: "... . .",
  D: ". .. .",
  E: ".. .. .",
  F: "... .. .",
  G: ". ... .",
  H: ".. ... .",
  I: "... ... .",
  J: ". . ..",
  K: ".. . ..",
  L: "... . ..",
  M: ". .. ..",
  N: ".. .. ..",
  O: "... .. ..",
  P: ". ... ..",
  Q: ".. ... ..",
  R: "... ... ..",
  S: ". . ...",
  T: ".. . ...",
  U: "... . ...",
  V: ". .. ...",
  W: ".. .. ...",
  X: "... .. ...",
  Y: ". ... ...",
  Z: ".. ... ...",
  " ": "... ... ...",
};

const reversedMap = Object.fromEntries(
  Object.entries(map).map(([key, value]) => [value, key]),
);

export function encode(str: string): string {
  return str
    .split("")
    .map((char) => map[char])
    .join(" ");
}

export function decode(str: string): string {
  const chars: string[] = [];
  let triple: string[] = [];

  str.split(" ").forEach((dots, i) => {
    triple.push(dots);

    if (i % 3 === 2) {
      const charDots = triple.join(" ");
      const char = reversedMap[charDots];
      chars.push(char);
      triple = [];
    }
  });

  return chars.join("");
}

Feedback

Schreib mir!