Strings and Regular Expressions
Prerequisites: Strings
More string tools
open System
let csvLine = " Ada Lovelace, 36 ,London "
let fields = csvLine.Split(',') |> Array.map _.Trim()
printfn "%A" fields
printfn "%s" (String.Join(" | ", fields))
printfn "%s" ("hello".PadLeft(8, '.'))
printfn "%s" ("F#".PadRight(6) + "|")
printfn "%b" (String.IsNullOrWhiteSpace " ")
printfn "%d" ("banana".IndexOf "an")
printfn "%s" (String("ab".ToCharArray() |> Array.rev))
printfn "%A" ("a,b,,c".Split(',', StringSplitOptions.RemoveEmptyEntries))
[|"Ada Lovelace"; "36"; "London"|]
Ada Lovelace | 36 | London
...hello
F# |
true
1
ba
[|"a"; "b"; "c"|]
Strings are sequences of characters, so the Seq functions work on
them too:
let text = "Hello, World 2025!"
printfn "%d" (text |> Seq.filter System.Char.IsUpper |> Seq.length)
printfn "%s" (text |> String.filter System.Char.IsLetter)
printfn "%s" (text |> String.map System.Char.ToLower)
printfn "%b" (text |> String.exists System.Char.IsDigit)
2
HelloWorld
hello, world 2025!
true
Building strings efficiently
Concatenating in a loop creates many temporary strings. For large
output, use String.concat / String.Join, or a StringBuilder:
let sb = System.Text.StringBuilder()
for i in 1 .. 3 do
sb.Append($"line {i}; ") |> ignore
printfn "%s" (sb.ToString())
line 1; line 2; line 3;
|> ignore throws away a result you don't need — here, the builder that
Append returns. F# warns if you silently discard a non-unit value.
Regular expressions
.NET's System.Text.RegularExpressions.Regex works directly from F#:
open System.Text.RegularExpressions
let log = "2025-03-09 ERROR disk full; 2025-03-10 WARN cpu high; 2025-03-11 ERROR timeout"
printfn "%b" (Regex.IsMatch(log, @"ERROR"))
for m in Regex.Matches(log, @"(\d{4}-\d{2}-\d{2}) (\w+) ([^;]+)") do
printfn "%s | %-5s | %s" m.Groups[1].Value m.Groups[2].Value m.Groups[3].Value
printfn "%s" (Regex.Replace("call 555-0100 or 555-0199", @"\d{3}-\d{4}", "XXX-XXXX"))
printfn "%A" (Regex.Split("one, two;three", @"[,;]\s*"))
true
2025-03-09 | ERROR | disk full
2025-03-10 | WARN | cpu high
2025-03-11 | ERROR | timeout
call XXX-XXXX or XXX-XXXX
[|"one"; "two"; "three"|]
Use verbatim strings (@"...") or triple-quoted strings for patterns so
backslashes stay literal. You can try patterns interactively with the
Regex Tester.
Named groups
open System.Text.RegularExpressions
let pattern = Regex(@"^(?<user>[\w.]+)@(?<domain>[\w.]+)$")
for email in ["ada@example.com"; "not-an-email"] do
let m = pattern.Match email
if m.Success then
printfn "user=%s domain=%s" m.Groups["user"].Value m.Groups["domain"].Value
else
printfn "%s: no match" email
user=ada domain=example.com
not-an-email: no match
Creating a Regex object once and reusing it is faster than calling
the static methods repeatedly with the same pattern. Later in this level,
active patterns turn regexes into clean match cases.
Practice
Extract every hashtag (a # followed by letters or digits) from
"Loving #fsharp and #dotnet10 today #fp" and print them as a list,
without the #.
Show solution
open System.Text.RegularExpressions
Regex.Matches("Loving #fsharp and #dotnet10 today #fp", @"#(\w+)")
|> Seq.map (fun m -> m.Groups[1].Value)
|> List.ofSeq
|> printfn "%A"
["fsharp"; "dotnet10"; "fp"]