Codewars Lösung | Multi-tap Keypad Text Entry on an Old Mobile Phone


coden
Codewars. Achieve mastery through challenge.
Daniel Kaser|12. August 2024
4 min.

Inhalt

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

Die Fakten:

Plattform:codewars.com
Name:Multi-tap Keypad Text Entry on an Old Mobile Phone
Level:6 kyu
Sprache:JavaScript

Beschreibung:

Prior to having fancy iPhones, teenagers would wear out their thumbs sending SMS messages on candybar-shaped feature phones with 3x4 numeric keypads.

------- ------- -------
|     | | ABC | | DEF |
|  1  | |  2  | |  3  |
------- ------- -------
------- ------- -------
| GHI | | JKL | | MNO |
|  4  | |  5  | |  6  |
------- ------- -------
------- ------- -------
|PQRS | | TUV | | WXYZ|
|  7  | |  8  | |  9  |
------- ------- -------
------- ------- -------
|  *  | |space| |  #  |
|     | |  0  | |     |
------- ------- -------

Prior to the development of T9 systems (predictive text entry), the method to type words was called "multi-tap" and involved pressing a button repeatedly to cycle through all its possible values, in order. For example:

  • Pressing the button 7 repeatedly will cycle through the letters P -> Q -> R -> S -> 7 -> P -> ....
  • Pressing the button 0 is cycling through SPACE -> 0 -> SPACE -> 0 -> ....
  • Buttons with a single symbol on it just type this symbol.

A character is "locked in" and inserted into the message once the user presses a different key or pauses for a short period of time (thus, no extra button presses are required beyond what is needed for each letter individually). For example:

  • To type a letter "R" you would press the 7 key three times (as the screen display for the current character cycles through P->Q->R->S->7).
  • To type in a digit 3, you would press the button 3 four times.
  • To type in the message "ABC", you would press the button 2 once, wait a second, then press the button 2 twice to enter the letter B, then pause for another second, and press the button 2 three times, to enter the letter C. You would have to press the button 2 six times in total.

In order to send the message "WHERE DO U WANT 2 MEET L8R" a teen would have to actually do 47 button presses. No wonder they abbreviated...

For this assignment, write code that can calculate the amount of button presses required for any phrase, with the following requirements:

  • Punctuation can be ignored for this exercise.
  • Likewise, the phone doesn't distinguish between upper and lowercase characters (but you should allow your module to accept input in either form, for convenience).
  • Tested phrases contain letters (A-Z and a-z), digits (0-9), and special characters # and *.

Quelle: codewars.com

Lösung

Pseudo-Code

Es gibt wie immer viele Varianten, hier ist eine meiner.

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

Lösungsschritte
Schritt 1

Als Erstes definieren wir uns das Keypad mit den Buttons und eine Variable um die Button-Drückungen zu zählen.

Schritt 2

Dann brauchen wir einen Loop durch den Input-String (Ich entscheide mich hier für .reduce(), wird also etwas anders).

Schritt 3

Wir suchen den jeweils aktuellen Buchstaben im Keypad.

Schritt 4

Wenn wir den passenden Button gefunden haben, ermitteln wir die Position des gesuchten Buchstaben.

Schritt 5

Damit können wir ermitteln, wie oft der Button gedrückt werden muss.

Schritt 6

Das speichern wir in unserer Variablen.

Schritt 7

Dann addieren wir die Anzahl der Drückungen zu einer Summe.

Schritt 8

Zum Schluss geben wir alles noch schön zurück.

Code

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

Lösungsschritte
Meine erste Zeile:
function presses(phrase) {
Als Erstes unsere Variablen für Keypad und Anzahl der Drückungen:
const keypad = [
  ["1"],
  ["ABC2"],
  ["DEF3"],
  ["GHI4"],
  ["JKL5"],
  ["MNO6"],
  ["PQRS7"],
  ["TUV8"],
  ["WXYZ9"],
  ["*"],
  [" 0"],
  ["#"],
];

let numPresses = 0;
TIPP
Statt als 2-dimensionales Array kannst du das Keypad auch gerne als String-Array anlegen (→ ["1", "ABC2", "DEF3", ...]).
Dann der Loop durch den Input-String:
return phrase
  .toUpperCase()
  .split("")
  .reduce((sum, char) => {
    // Todo
  }, 0);

Vorher müssen wir den phrase noch in ein Array umwandeln.

Wie oben erwähnt, entscheide ich mich hier für einen .reduce()-Loop. Meinen Accumulator nenne ich sum, das jeweils aktuelle Elment char und meinen Startwert setze ich auf 0.

Außerdem setze ich das return hier schon mal davor. Mal sehen ob’s klappt... 🫢

Jetzt können wir den jeweils aktuellen Buchstaben im Keypad suchen. Dafür loopen wir also innerhalb des .reduce() noch mal durch jeden Button des Keypads:
      for (const button of keypad) {
        const buttonChars = button[0].split("");

Der Übersicht und Lesbarkeit halber speichere ich die Zeichen des aktuellen Buttons als Array in einer Variablen.

Da jeder Button ein Array ist, müssen wir immer das erste Element auswählen (button[0]). Man hätte das Keypad auch als String-Array anlegen können (s.o.), dann kann man hier direkt auf den button zugreifen.

Dann können wir die Position des aktuellen Zeichen auf dem Button und damit die Anzahl der nötigen Drückungen ermitteln:
        numPresses = buttonChars.indexOf(char) + 1;
        if (numPresses) break; // optional
      }

Wenn wir den richtigen Button gefunden und die Drückungen berechnet haben, können wir den inneren Loop vorzeitig beenden. Das kitzelt noch mal ein Klitze Performance aus unserer krassen Funktion. Müssen wir aber nicht.

Nach dem inneren Loop müssen wir unser Ergebnis noch für die .reduce()-Methode zurückgeben:
      return numPresses ? sum + numPresses : sum;
    }, 0);
}

Wenn wir Drückungen feststellen konnten, addieren wir sie zu unserer Summe, ansonsten geben wir die Summe so zurück wie sie vorher war.

Voilá! 💪

Fragen?

Komplettlösung
function presses(phrase) {
  const keypad = [
    ["1"],
    ["ABC2"],
    ["DEF3"],
    ["GHI4"],
    ["JKL5"],
    ["MNO6"],
    ["PQRS7"],
    ["TUV8"],
    ["WXYZ9"],
    ["*"],
    [" 0"],
    ["#"],
  ];

  let numPresses = 0;

  return phrase
    .toUpperCase()
    .split("")
    .reduce((sum, char) => {
      for (const button of keypad) {
        const buttonChars = button[0].split("");
        numPresses = buttonChars.indexOf(char) + 1;
        if (numPresses) break;
      }

      return numPresses ? sum + numPresses : sum;
    }, 0);
}

Feedback

Schreib mir!