Codewars Lösung | Ease the StockBroker


coden
Codewars. Achieve mastery through challenge.
Daniel Kaser|9. Oktober 2024
5 min.

Inhalt

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

Die Fakten:

Plattform:codewars.com
Name:Ease the StockBroker
Level:6 kyu
Sprache:TypeScript

Beschreibung:

Clients place orders to a stockbroker as strings. The order can be simple or multiple or empty.

Type of a simple order: Quote/white-space/Quantity/white-space/Price/white-space/Status

where Quote is formed of non-whitespace character, Quantity is an int, Price a double (with mandatory decimal point "." ), Status is represented by the letter B (buy) or the letter S (sell).

Example:

"GOOG 300 542.0 B"

A multiple order is the concatenation of simple orders with a comma between each.

Example:

"ZNGA 1300 2.66 B, CLH15.NYM 50 56.32 B, OWW 1000 11.623 B, OGG 20 580.1 B"

or

"ZNGA 1300 2.66 B,CLH15.NYM 50 56.32 B,OWW 1000 11.623 B,OGG 20 580.1 B"

To ease the stockbroker your task is to produce a string of type

"Buy: b Sell: s" where b and s are 'double' formatted with no decimal, b representing the total price of bought stocks and s the total price of sold stocks.

Example:

"Buy: 294990 Sell: 0"

Unfortunately sometimes clients make mistakes. When you find mistakes in orders, you must pinpoint these badly formed orders and produce a string of type:

"Buy: b Sell: s; Badly formed nb: badly-formed 1st simple order ;badly-formed nth simple order ;"

where nb is the number of badly formed simple orders, b representing the total price of bought stocks with correct simple order and s the total price of sold stocks with correct simple order.

Examples:

"Buy: 263 Sell: 11802; Badly formed 2: CLH16.NYM 50 56 S ;OWW 1000 11 S ;"

"Buy: 100 Sell: 56041; Badly formed 1: ZNGA 1300 2.66 ;"

Notes:

  • If the order is empty, Buy is 0 and Sell is 0 hence the return is: "Buy: 0 Sell: 0".
  • Due to Codewars whitespace differences will not always show up in test results.
  • With Golang (and maybe others) you can use a format with "%.0f" for "Buy" and "Sell".

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

Als Erstes brauchen wir ein paar Variablen.

Schritt 2

Eine für die Buy-Summe, eine für die Sell-Summe, eine für die Summe der schlechten Orders und eine für die gesammelten schlechten Orders.

Schritt 3

Damit wir durch die Orders loopen können, sollten wir aus dem Input-String zuerst ein Array mit Orders machen.

Schritt 4

Dann der Loop.

Schritt 5

Wir zerlegen jede Order in ihre Bestandteile und speichern uns diese in Variablen.

Schritt 6

Jetzt können wir checken, ob die Order valide ist.

Schritt 7

Eine valide Order muss einen Preis als Komma-Zahl, eine Stückzahl als Integer (also keine Komma-Zahl) und einen Status von B oder S haben.

Schritt 8

Wenn das zutrifft, können wir beim Status B die Buy-Summe um den Wert der aktuellen Order erhöhen, und beim Status S die Sell-Summe.

Schritt 9

Ansonsten hängen wir die schlechte Order ans Ende der Variablen für die schlechten Orders und erhöhen den Zähler für diese um 1.

Schritt 10

Nach dem Loop geben wir die Summen der Order-Werte gefolgt von den schlechten Orders als String zurück.

Code

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

Der besseren Lesbarkeit halber erstelle ich mir für den Komma-Zahl-Check eine kleine Hilfsfunktion.

Lösungsschritte
Die erste Zeile meiner Hilfsfunktion:
function isFloat(strNum: string): boolean {
Wenn die Zahl (im String-Format) einen Punkt hat, ist es eine Komma-Zahl:
  return strNum.includes(".");
}

Das war’s auch schon für die Hilfsfunktion. Weiter geht’s mit der Hauptfunktion!

Die erste Zeile meiner Hauptfunktion (ich habe strng in orderStr umbenannt):
export function balanceStatements(ordersStr: string): string {
Zuerst also unsere Variablen:
  let sumBuys = 0;
  let sumSells = 0;
  let sumBadlyFormed = 0;
  let badlyFormed = "";

Die schlechten Orders sammle ich also in einem String. Find ich lesbarer. Du kannst sie natürlich auch in einem Array sammeln, dann sparst Du Dir außerdem die Zählvariable.

Jetzt den Input-String in ein Array umwandeln:
  const orders = ordersStr.split(", ");
Dann der Loop:
  for (const order of orders) {

Da man mit einer Array-Method leider den Loop oder einzelne Iterationen nicht abbrechen kann, entscheide ich mich hier für einen for...of-Loop.

Als Erstes der Check, ob überhaupt ein Order-Array existiert:
    if (!order) break;

Wenn nicht, haben wir nämlich einen leeren Input-String bekommen und können in diesem Fall den Loop komplett überspringen.

Ansonsten zerlegen wir die aktuelle Order in ihre Bestandteile:
    const [_, quantity, price, status] = order.split(" ");

Den Ticker bzw. das Aktienkürzel brauchen wir hier nicht, darum nenne ich es _.

Der Lesbarkeit halber speichere ich mir in einer Variablen, ob der Status gültig ist (optional):
    const isValidStatus = status === "B" || status === "S";
Dann prüfen wir, ob die aktuelle Order valide ist:
    if (isFloat(price) && !isFloat(quantity) && isValidStatus) {
Wenn ja, können wir uns den Order-Wert in einer Variablen speichern (optional):
      const amount = Number(quantity) * Number(price);
Und dann je nach Order-Art den Wert zur richtigen Summe hinzufügen:
      if (status === "B") sumBuys += amount;
      if (status === "S") sumSells += amount;
Danach springen wir zur nächsten Order:
      continue;
    }

Ich bin ein großer Fan des continue-Keywords. Macht den Code meiner Meinung nach lesbarer als wüste If-Else-Verschachtelungen.

Wenn die Order nicht valide ist, fügen wir sie zu unseren schlechten Orders hinzu:
    badlyFormed += order + " ;";
    sumBadlyFormed++;
  }
TIPP
Eigentlich braucht man die Summe für die schlechten Orders nicht. Man könnte am Ende auch einfach die ";" in der Variable zählen.
Dann erstelle ich mir eine Variable für den Output der gültigen Order-Werte:
  const outputOrderValues = `Buy: ${Math.round(sumBuys)} Sell: ${Math.round(sumSells)}`;
Und noch eine für den Output der schlechten Orders:
  const outputBadlyFormed = sumBadlyFormed ? `; Badly formed ${sumBadlyFormed}: ${badlyFormed}` : "";

Wenn es keine schlechten Orders gibt, wird diese Variable einfach auf einen leeren String gesetzt 😉

Zum Schluss nur noch beide Output-Variablen zusammenfügen und ausgeben:
  return outputOrderValues + outputBadlyFormed;
}
Voilá! 💪

Fragen?

Komplettlösung
export function balanceStatements(ordersStr: string): string {
  let sumBuys = 0;
  let sumSells = 0;
  let sumBadlyFormed = 0;
  let badlyFormed = "";

  const orders = ordersStr.split(", ");

  for (const order of orders) {
    if (!order) break;

    const [_, quantity, price, status] = order.split(" ");
    const isValidStatus = status === "B" || status === "S";

    if (isFloat(price) && !isFloat(quantity) && isValidStatus) {
      const amount = Number(quantity) * Number(price);

      if (status === "B") sumBuys += amount;
      if (status === "S") sumSells += amount;

      continue;
    }

    badlyFormed += order + " ;";
    sumBadlyFormed++;
  }

  const outputOrderValues = `Buy: ${Math.round(sumBuys)} Sell: ${Math.round(sumSells)}`;
  const outputBadlyFormed = sumBadlyFormed ? `; Badly formed ${sumBadlyFormed}: ${badlyFormed}` : "";

  return outputOrderValues + outputBadlyFormed;
}

function isFloat(strNum: string): boolean {
  return strNum.includes(".");
}

Feedback

Schreib mir!