Tracks
/
Elixir
Elixir
/
Übungen
/
Datumsparser
Datumsparser

Datumsparser

Lernübung

Einführung

Reguläre Ausdrücke

Reguläre Ausdrücke (Regex) sind ein mächtiges Werkzeug für die Arbeit mit Strings in Elixir. Reguläre Ausdrücke in Elixir folgen der PCRE-Spezifikation (Perl Compatible Regular Expressions). String-Muster, die die Bedeutung des regulären Ausdrucks darstellen, werden zuerst kompiliert und dann verwendet, um einen String ganz oder teilweise zu matchen.

In Elixir ist der gebräuchlichste Weg, reguläre Ausdrücke zu erstellen, das ~r-Sigil. Sigils bieten syntaktischen Zucker als Abkürzung für häufige Aufgaben in Elixir. Um ein String-Literal zu matchen, können wir den String selbst als Muster nach dem Sigil verwenden.

~r/test/

Der =~/2-Operator ist nützlich, um mit einer Regex einen String zu matchen und ein boolean-Ergebnis zu erhalten.

"this is a test" =~ ~r/test/
# => true

Zwei Anmerkungen zur Verwendung von Sigils:

  • je nach deinen Anforderungen können viele verschiedene Trennzeichen statt / verwendet werden
  • String-Muster sind bereits escaped; wenn du das Muster als String schreibst und keinen Regex verwendest, musst du Backslashes (\) escapen

Zeichenklassen

Wenn du einen Zeichenbereich mit eckigen Klammern [] angibst, definierst du eine Zeichenklasse. Sie matcht genau ein Zeichen aus der Menge der Zeichen in der Klasse. Du kannst auch einen Zeichenbereich wie a-z angeben, solange Anfang und Ende einen zusammenhängenden Bereich von Codepunkten darstellen.

regex = ~r/[a-z][ADKZ][0-9][!?]/
"jZ5!" =~ regex
# => true
"jB5?" =~ regex
# => false

Kurzschreibweisen für Zeichenklassen machen das Muster kompakter. Zum Beispiel:

  • \d Kurzform für [0-9] (jede Ziffer)
  • \w Kurzform für [A-Za-z0-9_] (jedes „Wort“-Zeichen)
  • \s Kurzform für [ \t\r\n\f] (jedes Whitespace-Zeichen)

Wenn eine Kurzschreibweise für Zeichenklassen außerhalb eines Sigils verwendet wird, muss sie escaped werden: "\\d"

Alternativen

Alternativen verwenden | als Sonderzeichen, um auszudrücken, dass das eine oder das andere gematcht wird

regex = ~r/cat|bat/
"bat" =~ regex
# => true
"cat" =~ regex
# => true

Quantoren

Quantoren erlauben ein sich wiederholendes Muster im Regex. Sie wirken auf die Gruppe, die vor dem Quantor steht.

  • {N, M} wobei N die minimale Anzahl an Wiederholungen und M die maximale ist
  • {N,} matcht N oder mehr Wiederholungen
    • {0,} kann auch als * geschrieben werden: null oder mehr Wiederholungen matchen
    • {1,} kann auch als + geschrieben werden: eine oder mehr Wiederholungen matchen
  • {,N} matcht bis zu N Wiederholungen

Gruppen

Runde Klammern () werden verwendet, um Gruppen und Captures zu kennzeichnen. Die Gruppe kann in manchen Fällen auch gecaptured werden, um sie zur weiteren Verwendung zurückzugeben. In Elixir können diese benannt oder unbenannt sein. Captures werden benannt, indem du ?<name> nach der öffnenden Klammer anhängst. Gruppen funktionieren als eine Einheit, zum Beispiel wenn ein Quantor folgt.

regex = ~r/(h)at/
Regex.replace(regex, "hat", "\\1op")
# => "hop"

regex = ~r/(?<letter_b>b)/
Regex.scan(regex, "blueberry", capture: :all_names)
# => [["b"], ["b"]]

Anker

Anker werden verwendet, um den regulären Ausdruck an den Anfang oder das Ende des Strings zu binden, der gematcht werden soll:

  • ^ verankert am Anfang des Strings
  • $ verankert am Ende des Strings

Interpolation

Da ~r eine Abkürzung für "pattern" |> Regex.escape() |> Regex.compile!() ist, kannst du auch String-Interpolation verwenden, um ein Regex-Muster dynamisch aufzubauen:

anchor = "$"
regex = ~r/end of the line#{anchor}/
"end of the line?" =~ regex
# => false
"end of the line" =~ regex
# => true

Anleitung

Du sollst einen Dienst schreiben, der Ereignisse entgegennimmt. Jedes Ereignis hat ein zugehöriges Datum, aber dir fällt auf, dass an den Endpunkt deines Dienstes drei verschiedene Formate geschickt werden:

  • "01/01/1970"
  • "January 1, 1970"
  • "Thursday, January 1, 1970"

Du erkennst, dass es gewisse Ähnlichkeiten zwischen ihnen gibt, und beschließt, einige zusammensetzbare Muster für reguläre Ausdrücke zu schreiben.

1. Tag, Monat und Jahr aus einem Datum abgleichen

Implementiere day/0, month/0 und year/0, um ein String-Muster zurückzugeben, das, wenn es kompiliert wird, auf die numerischen Bestandteile in "01/01/1970" (dd/mm/yyyy) passt. Tag und Monat können als 1 oder 01 erscheinen (links mit Nullen aufgefüllt).

"31" =~ DateParser.day() |> Regex.compile!()
# => true
"12" =~ DateParser.month() |> Regex.compile!()
# => true
"1970" =~ DateParser.year() |> Regex.compile!()
# => true

2. Den Wochentag und den Monat des Jahres abgleichen

Implementiere day_names/0 und month_names/0, um ein String-Muster zurückzugeben, das, wenn es kompiliert wird, auf den ausgeschriebenen Wochentag bzw. den ausgeschriebenen Monat des Jahres passt.

"Tuesday" =~ DateParser.day_names() |> Regex.compile!()
# => true
"June" =~ DateParser.month_names() |> Regex.compile!()
# => true

3. Tag, Monat und Jahr erfassen

Implementiere capture_day/0, capture_month/0, capture_year/0, capture_day_name/0 und capture_month_name/0, um ein String-Muster zurückzugeben, das die jeweiligen Bestandteile unter den Namen "day", "month", "year", "day_name", "month_name" erfasst.

DateParser.capture_month_name()
|> Regex.compile!()
|> Regex.named_captures("December")

# => %{"month_name" => "December"}

4. Die erfassten Bestandteile kombinieren, um das ganze Datum zu erfassen

Implementiere capture_numeric_date/0, capture_month_name_date() und capture_day_month_name_date/0, um ein String-Muster zurückzugeben, das die Bestandteile aus Teil 3 mit dem jeweiligen Datumsformat erfasst:

  • numerisches Datum - "01/01/1970"
  • Datum mit Monatsnamen - "January 1, 1970"
  • Datum mit Wochentag und Monatsnamen - "Thursday, January 1, 1970"
DateParser.capture_numeric_date()
|> Regex.compile!()
|> Regex.named_captures("01/01/1970")

# => %{"day" => "01", "month" => "01", "year" => "1970"}

5. Die Erfassung einschränken, damit sie nur auf das Datum passt

Implementiere match_numeric_date/0, match_month_name_date/0 und match_day_month_name_date/0, um einen kompilierten regulären Ausdruck zurückzugeben, der nur auf das Datum passt und der außerdem die Bestandteile erfassen kann.

"Thursday, January 1, 1970 was the Unix epoch." =~ DateParser.match_day_month_name_date()
# => false
"Thursday, January 1, 1970" =~ DateParser.match_day_month_name_date()
# => true

DateParser.match_day_month_name_date()
|> Regex.named_captures("Thursday, January 1, 1970")

# => %{
#     "day" => "1",
#     "day_name" => "Thursday",
#     "month_name" => "January",
#     "year" => "1970"
#   }
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
Elixir Exercism

Bereit, mit Datumsparser zu starten?

Melde dich bei Exercism an, um Elixir mit 58 Konzepte168 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.