Last active
February 3, 2025 04:01
-
-
Save isaacabraham/04fc03620a9d1e5160ea9c28edbc8bdd to your computer and use it in GitHub Desktop.
Active Patterns
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open System | |
let s = "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53" | |
// challenge - split above into values we can reason about e.g. | |
type Card = { | |
Id : int // 1 | |
Winning : int list // 41 48 83 86 17 | |
Ticket : int list // 83 86 6 31 17 9 48 53 | |
} | |
// this based on day four of Advent of Code 2023 https://adventofcode.com/2023/day/4 | |
// Active pattern to split a string on a given character and return the resultant parts as a list. | |
let (|Split|) (on: char) (s: string) = | |
s.Split(on, StringSplitOptions.RemoveEmptyEntries ||| StringSplitOptions.TrimEntries) | |
|> Array.toList | |
// 1. s split on : into a list bound to the symbol 'elements'. | |
match s with | |
| Split ':' elements -> () // ["Card 1"; "41 48 83 86 17 | 83 86 6 31 17 9 48 53"] | |
// 2. Expand 'elements' => must be a list of EXACTLY two items, bound to symbols 'a' and 'b'. | |
match s with | |
| Split ':' [ a; b ] -> () // a = "Card 1", b = "41 48 83 86 17 | 83 86 6 31 17 9 48 53" | |
| _ -> failwith "bad input" | |
// 3. Expand 'a' => Split on ' ' into a list that must have exactly two items, first element must be absolute value "Card". | |
match s with | |
| Split ':' [ Split ' ' [ "Card"; n ]; b ] -> () // n = "1" | |
| _ -> failwith "bad input" | |
// Active pattern to safely convert string to int | |
let (|Int|_|) (s: string) = | |
match Int32.TryParse s with | |
| true, n -> Some(Int n) | |
| false, _ -> None | |
// 4. Expand 'n' => n must be an Integer bound to the symbol 'n' | |
match s with | |
| Split ':' [ Split ' ' [ "Card"; Int n ]; b ] -> () // (n = 1) | |
| _ -> failwith "bad input" | |
// 5. Apply same technique to 'b' => Split on '|' into a list of EXACTLY two items which are bound to symbols 'a' and 'b'. | |
match s with | |
| Split ':' [ Split ' ' [ "Card"; Int n ]; Split '|' [ a; b ] ] -> () // a = "41 48 83 86 17", b = "83 86 6 31 17 9 48 53" | |
| _ -> failwith "bad input" | |
// 6. Expand 'a' => Split on ' ' and bind the list into the symbol 'winning' | |
match s with | |
| Split ':' [ Split ' ' [ "Card"; Int n ]; Split '|' [ Split ' ' winning; b ] ] -> () // winning = ["41"; "48"; "83"; "86"; "17"] | |
| _ -> failwith "bad input" | |
// 7. Expand 'b' => do the same as step 6 again and bind the list into the symbol 'ticket' | |
match s with | |
| Split ':' [ Split ' ' [ "Card"; Int n ]; Split '|' [ Split ' ' winning; Split ' ' ticket ] ] -> () // ticket = ["83"; "86"; "6"; "31"; "17"; "9"; "48"; "53"] | |
| _ -> failwith "bad input" | |
// 8. Final result | |
match s with | |
| Split ':' [ Split ' ' [ "Card"; Int n ]; Split '|' [ Split ' ' winning; Split ' ' ticket ] ] -> | |
{ Id = n; Winning = winning |> List.map int; Ticket = ticket |> List.map int } | |
| _ -> failwith "bad input" | |
// Equivalent "imperative" code (no pattern matching): | |
let elements = s.Split ':' | |
let cardNumber = elements[0].Split ' ' | |
if cardNumber[0] = "Card" && cardNumber.Length = 2 then | |
let parsed, number = Int32.TryParse cardNumber[1] | |
if parsed then | |
let tickets = elements[1].Split '|' | |
if tickets.Length = 2 then | |
let winningNumbers = | |
tickets[0].Split(' ', StringSplitOptions.RemoveEmptyEntries) |> Array.map int | |
let ticket = | |
tickets[1].Split(' ', StringSplitOptions.RemoveEmptyEntries) |> Array.map int | |
{ | |
Id = number | |
Winning = winningNumbers |> List.ofArray | |
Ticket = ticket |> List.ofArray | |
} | |
else | |
failwith "bad input" | |
else | |
failwith "bad input" | |
else | |
failwith "bad input" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For easy comparison with the "imperative" way, here are the key bits of the active pattern approach:
Very cool, thanks Isaac!