Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add docs for date-parser exercise #492

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ pom.xml.asc
.clj-kondo
.lsp
*.calva
*.cpcache
57 changes: 57 additions & 0 deletions exercises/concept/date-parser/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Hints

## General

- Review regular expression patterns from the introduction. Remember, when creating the pattern from a string, you must escape some characters.
- Refer to the full [Java regex spec][java-util-regex-pattern].
- Check out this website about regular expressions: [Regular-Expressions.info][website-regex-info].
- Check out this website about regular expressions: [Rex Egg -The world's most tyrannosauical regex tutorial][website-rexegg].
- Check out this website about regular expressions: [RegexOne - Learn Regular Expressions with simple, interactive exercises.][website-regexone].
- Check out this website about regular expressions: [Regular Expressions 101 - an online regex sandbox][website-regex-101].
- Check out this website about regular expressions: [RegExr - an online regex sandbox][website-regexr].

## 1. Match the day, month, and year from a date

- Remember to return a string representing the regular expression pattern.
- Review how to create _character classes_ or use _shorthand character classes_.
- Review _quantifiers_.
- A day is one or two digits.
- A month is one or two digits.
- A year is four digits.
- Create a regex pattern with [`re-pattern`][re-pattern].
- Return a regex match with [`re-matches`][re-matches].

## 2. Match the day of the week and the month of the year

- Review how to write a pattern to match _string literals_.
- Review _alternations_.
- Wrap the whole expression in a _group_.

## 3. Capture the day, month, and year

- Review how to write patterns for captures and named captures.
- Reuse the `day`, `month`, `year`, `day-names`, and `month-names` functions that you already implemented.
- You can use [`re-matcher`][re-matcher] to return an instance of `java.util.regex.Matcher` to use with [`re-find`][re-find].

## 4. Combine the captures to capture the whole date

- Remember, string concatenation may be used to join strings.
- Reuse the `capture-day`, `capture-month`, `capture-year`, `capture-day-name`, and `capture-month-name` functions that you already implemented.

## 5. Narrow the capture to match only on the date

- Remember, _anchors_ help to match the pattern to the **whole line**.
- String concatenation may be used in a call to `re-pattern`.
- Reuse the `capture-numeric-date`, `capture-month-name-date`, and `capture-day-month-nam-date` functions that you already implemented.

[java-util-regex-pattern]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/regex/Pattern.html
[re-find]: https://clojuredocs.org/clojure.core/re-find
[re-matcher]: https://clojuredocs.org/clojure.core/re-matcher
[re-matches]: https://clojuredocs.org/clojure.core/re-matches
[re-pattern]: https://clojuredocs.org/clojure.core/re-pattern
[website-regex-info]: https://www.regular-expressions.info
[website-rexegg]: https://www.rexegg.com/
[website-regexone]: https://regexone.com/
[website-regex-101]: https://regex101.com/
[website-regexr]: https://regexr.com/
[website-regex-crossword]: https://regexcrossword.com/
13 changes: 12 additions & 1 deletion exercises/concept/date-parser/.docs/instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Implement `day-name` and `month-name` to return a string pattern which, when exe

## 3. Capture the day, month, and year

Implement `capture-day`, `capture-month`, `capture-year`, `capture-day-name`, `capture-month-name` to return a map of the respective components to the names: `"day"`, `"month"`, `"year"`, `"day-name"`, `"month-name"`
Implement `capture-day`, `capture-month`, `capture-year`, `capture-day-name`, and `capture-month-name` to return a map of the respective components to the names: `"day"`, `"month"`, `"year"`, `"day-name"`, and `"month-name"`.

```clojure
(capture-month-name "December")
Expand All @@ -49,3 +49,14 @@ Implement `capture-numeric-date`, `capture-month-name-date`, and `capture-day-mo
(capture-numeric-date "01/01/1970")
;;=> {:day "01", :month "01", :year "1970"}
```

## 5. Narrow the capture to match only on the date

Implement `match-numeric-date`, `match-month-name-date`, and `match-day-month-name-date` to return a `java.util.regex.Pattern` that only matches the date, and which can also capture the components.

```clojure
(re-matches date-parser/match-day-month-name-date "Thursday, January 1, 1970 was the day")
;; => nil
(re-matches date-parser/match-day-month-name-date "Thursday, January 1, 1970")
;; => ["Thursday, January 1, 1970" "Thursday" "January" "1" "1970"]
```
84 changes: 84 additions & 0 deletions exercises/concept/date-parser/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Introduction

## Regular Expressions

Regular expressions (regex) are a powerful tool for working with strings in Clojure. Regular expressions in Clojure follow the Java specification. String patterns representing the regular expression's meaning are first compiled then used for matching all or part of a string.

In Clojure, the most concise way to create regular expressions is using the #"pattern" syntax. This provides _syntactic sugar_ as a convenience. To match a _string literal_, we can use this.

```clojure
#"test"
```

If you want to construct a regex pattern dynamically at run time, then you need to use `re-pattern` to convert a string to a pattern that can be used for matching. When doing so, you need to escape every `\` character with another `\`. But if your pattern is one you write into the source code, it is more convenient to use the #"pattern" syntax.

### Character classes

Matching a range of characters using square brackets `[]` defines a _character class_. This will match any one character to the characters in the class. You can also specify a range of characters like `a-z`, as long as the start and end represent a contiguous range of code points.

```clojure
(def regex #"[a-z][ADKZ][0-9][!?]")
(re-matches regex "jZ5!")
;; => "jZ5!"
(re-matches regex "jB5?")
;; => nil
```

_Shorthand character classes_ make the pattern more concise. For example:

- `\d` short for `[0-9]` (any digit)
- `\w` short for `[A-Za-z0-9_]` (any 'word' character)
- `\s` short for `[ \t\n\x0B\f\r]` (any whitespace character)

When a _shorthand character class_ is used outside of the #"pattern" syntax, it must be escaped: `"\\d"`

### Alternations

_Alternations_ use `|` as a special character to denote matching one _or_ another

```clojure
(def regex #"cat|bat")
(re-matches regex "bat")
;; => "bat"
(re-matches regex "bat")
;; => "cat"
```

### Quantifiers

_Quantifiers_ allow for a repeating pattern in the regex. They affect the group preceding the quantifier.

- `{N, M}` where `N` is the minimum number of repetitions, and `M` is the maximum
- `{N,}` match `N` or more repetitions
- `{0,}` may also be written as `*`: match zero-or-more repetitions
- `{1,}` may also be written as `+`: match one-or-more repetitions

### Groups

Round brackets `()` are used to denote _groups_ and _captures_. The group may also be _captured_ in some instances to be returned for use. In Clojure, these may be named or un-named. Captures are named by appending `?<name>` after the opening parenthesis. Groups function as a single unit, like when followed by _quantifiers_.

```clojure
(re-find #"(?<letterb>b)" "blueberry")
;; => ["b" "b"]
```

### Anchors

_Anchors_ are used to tie the regular expression to the beginning or end of the string to be matched:

- `^` anchors to the beginning of the string
- `$` anchors to the end of the string

### Concatenation

Because the `#"pattern"` syntax is a shortcut for `re-pattern`, you may also use string concatenation to dynamically build a regular expression pattern:

```clojure
(def anchor "$")
(def regex (str "end of the line" anchor))
(re-matches regex "end of the line?")
;; => nil
(re-matches regex "end of the line")
"end of the line" =~ regex
;; => "end of the line"
```
9 changes: 9 additions & 0 deletions exercises/concept/date-parser/.meta/exemplar.clj
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,12 @@
:month-name (.group matcher "month")
:day (.group matcher "day")
:year (.group matcher "year")})))

(def match-numeric-date
(re-pattern (str "(?<day>" day ")/(?<month>" month ")/(?<year>" year ")")))

(def match-month-name-date
(re-pattern (str "(?<month>" months ") (?<day>" day "), (?<year>" year ")")))

(def match-day-month-name-date
(re-pattern (str "(?<dayname>" days "), (?<month>" months ") (?<day>" day "), (?<year>" year ")")))
30 changes: 10 additions & 20 deletions exercises/concept/date-parser/src/date_parser.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,24 @@

(def days)

(defn day-names [s]
)
(defn day-names [s])

(def months)

(defn month-names [s]
)
(defn month-names [s])

(defn capture-month [s]
)
(defn capture-month [s])

(defn capture-day [s]
)
(defn capture-day [s])

(defn capture-year [s]
)
(defn capture-year [s])

(defn capture-month-name [s]
)
(defn capture-month-name [s])

(defn capture-day-name [s]
)
(defn capture-day-name [s])

(defn capture-numeric-date [s]
)
(defn capture-numeric-date [s])

(defn capture-month-name-date [s]
)
(defn capture-month-name-date [s])

(defn capture-day-month-name-date [s]
)
(defn capture-day-month-name-date [s])
55 changes: 48 additions & 7 deletions exercises/concept/date-parser/test/date_parser_test.clj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(ns date-parser-test
(:require [clojure.test :refer [deftest testing is]]
date-parser))

(deftest ^{:task 1} day-test
(testing "numeric pattern for day matches"
(testing "un-padded 1"
Expand Down Expand Up @@ -84,7 +84,7 @@
(is (= "08" (re-matches (re-pattern date-parser/day) "08"))))
(testing "padded 09"
(is (= "09" (re-matches (re-pattern date-parser/day) "09")))))
(testing "numeric pattern for day doesn't match"
(testing "numeric pattern for a day that doesn't match"
(testing "too few digits"
(is (nil? (re-matches (re-pattern date-parser/day) ""))))
(testing "too many digits"
Expand Down Expand Up @@ -120,8 +120,8 @@
(is (= "11" (re-matches (re-pattern date-parser/month) "11"))))
(testing "un-padded 12"
(is (= "12" (re-matches (re-pattern date-parser/month) "12"))))
(testing "un-padded 1"
(is (= "1" (re-matches (re-pattern date-parser/month) "1"))))
(testing "padded 01"
(is (= "01" (re-matches (re-pattern date-parser/month) "01"))))
(testing "padded 02"
(is (= "02" (re-matches (re-pattern date-parser/month) "02"))))
(testing "padded 03"
Expand All @@ -138,7 +138,7 @@
(is (= "08" (re-matches (re-pattern date-parser/month) "08"))))
(testing "padded 09"
(is (= "09" (re-matches (re-pattern date-parser/month) "09")))))
(testing "numeric pattern for month doesn't match"
(testing "numeric pattern for month that doesn't match"
(testing "too few digits"
(is (nil? (re-matches (re-pattern date-parser/month) ""))))
(testing "too many digits"
Expand Down Expand Up @@ -182,7 +182,10 @@
(testing "numeric day of the week (0-indexed)"
(is (nil? (date-parser/day-names "0"))))
(testing "numeric day of the week (1-indexed)"
(is (nil? (date-parser/day-names "1"))))))
(is (nil? (date-parser/day-names "1")))))
(testing "day names don't match with trailing or leading whitespace"
(is (nil? (date-parser/day-names " Sunday ")))))


(deftest ^{:task 2} month-names-test
(testing "month names match"
Expand All @@ -206,7 +209,9 @@
(testing "numeric month of the year (0-indexed)"
(is (nil? (date-parser/month-names "0"))))
(testing "numeric month of the year (1-indexed)"
(is (nil? (date-parser/month-names "1"))))))
(is (nil? (date-parser/month-names "1")))))
(testing "month names don't match with trailing or leading whitespace"
(is (nil? (date-parser/day-names " January ")))))

(deftest ^{:task 3} capture-test
(testing "capture numeric month"
Expand All @@ -228,3 +233,39 @@
(testing "day and month named date"
(is (= {:year "1970", :month-name "January", :day "1", :day-name "Thursday"}
(date-parser/capture-day-month-name-date "Thursday, January 1, 1970")))))

(deftest match-numeric-date-test
(testing "pattern to match numeric date is a regex"
(is (= java.util.regex.Pattern (type date-parser/match-numeric-date))))
(testing "numeric date matches"
(is (= "01/02/1970"
(first (re-matches date-parser/match-numeric-date "01/02/1970")))))
(testing "numeric date has named captures"
(is (= ["01/02/1970" "01" "02" "1970"]
(re-matches date-parser/match-numeric-date "01/02/1970"))))
(testing "numeric date with a prefix doesn't match"
(is (nil? (re-matches date-parser/match-numeric-date "The day was 01/02/1970"))))
(testing "numeric date with a suffix doesn't match"
(is (nil? (re-matches date-parser/match-numeric-date "01/02/1970 was the day"))))
(testing "pattern to match month name date is a regex"
(is (= java.util.regex.Pattern (type date-parser/match-month-name-date))))
(testing "month named date matches"
(is (= "January 1, 1970"
(first (re-matches date-parser/match-month-name-date "January 1, 1970")))))
(testing "month named date has named captures"
(is (= ["January 1, 1970" "January" "1" "1970"]
(re-matches date-parser/match-month-name-date "January 1, 1970"))))
(testing "month named date with a prefix doesn't match"
(is (nil? (re-matches date-parser/match-month-name-date "The day was January 1, 1970"))))
(testing "month named date with a suffix doesn't match"
(is (nil? (re-matches date-parser/match-month-name-date "January 1, 1970 was the day"))))
(testing "day and month names date matches"
(is (= "Thursday, January 1, 1970"
(first (re-matches date-parser/match-day-month-name-date "Thursday, January 1, 1970")))))
(testing "month named date has named captures"
(is (= ["Thursday, January 1, 1970" "Thursday" "January" "1" "1970"]
(re-matches date-parser/match-day-month-name-date "Thursday, January 1, 1970"))))
(testing "day and month names date with a prefix doesn't match"
(is (nil? (re-matches date-parser/match-day-month-name-date "The day way Thursday, January 1, 1970"))))
(testing "day and month names date with a suffix doesn't match"
(is (nil? (re-matches date-parser/match-day-month-name-date "Thursday, January 1, 1970 was the day")))))