Hi. I'm working on a project where I've implemented is_alpha, is_hex, etc. and was wondering if there was an existing project that provided this functionality and I found casefold.
The implementation you have for is_alpha and similar functions is at least 3-7 times slower than an optimized version would be on all matching or mixed (the performance is the same for all invalid because list.all short circuits).
It would likely be beneficial to provide _char versions of these functions as well when someone is dealing with grapheme values already (I'm doing so with pattern matching in my parser) so that the overhead of list.all and string.to_graphemes is avoided if those expenses have already been paid.
Benchmark
//// Comparative benchmark: internal is_hex_digit vs casefold's contains
//// approach
importgleam/intimportgleam/ioimportgleam/listimportgleam/stringimportgleamy/benchconsthex="0123456789abcdefABCDEF"constnon_hex="ghijklmnopqrstuvwxyzGHIJKLMNOPQRSTUVWXYZ"pubfnmain(){lethex_chars=string.to_graphemes(hex)letnon_chars=string.to_graphemes(non_hex)let#(valid,invalid,mixed)=int.range(from:1,to:100,with:#([],[],[]),run:fn(acc,i){let#(va,ia,ma)=accletmixed=caseint.random(6){vifv<3->build_sample(hex_chars,i)_->build_sample(non_chars,i)}#([build_sample(hex_chars,i),..va],[build_sample(non_chars,i),..ia],[mixed,..ma])})bench.run([bench.Input("all valid",valid),bench.Input("all invalid",invalid),bench.Input("mixed",mixed),],[bench.Function("is_hex_case * 1000",bench.repeat(1000,fn(lists){list.each(lists,is_hex_case)}),),bench.Function("is_hex_contains * 1000",bench.repeat(1000,fn(lists){list.each(lists,is_hex_contains)}),),],[bench.Duration(2000),bench.Warmup(500)],)|>bench.table([bench.IPS,bench.Min,bench.Mean,bench.P(99)])|>io.println}fnis_hex_case(s:String)->Bool{string.to_graphemes(s)|>list.all(fn(ch){casech{"0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"|"a"|"b"|"c"|"d"|"e"|"f"|"A"|"B"|"C"|"D"|"E"|"F"->True_->False}})}fnis_hex_contains(s:String)->Bool{string.to_graphemes(s)|>list.all(fn(ch){string.contains(hex,ch)})}fnbuild_sample(pool:List(String),i:Int)->String{list.sample(pool,i)|>string.join("")}
Hi. I'm working on a project where I've implemented `is_alpha`, `is_hex`, etc. and was wondering if there was an existing project that provided this functionality and I found casefold.
The implementation you have for `is_alpha` and similar functions is at least 3-7 times slower than an optimized version would be on all matching or mixed (the performance is the same for all invalid because `list.all` short circuits).
It would likely be beneficial to provide `_char` versions of these functions as well when someone is dealing with grapheme values already (I'm doing so with pattern matching in my parser) so that the overhead of `list.all` and `string.to_graphemes` is avoided if those expenses have already been paid.
<details><summary>Benchmark</summary>
```gleam
//// Comparative benchmark: internal is_hex_digit vs casefold's contains
//// approach
import gleam/int
import gleam/io
import gleam/list
import gleam/string
import gleamy/bench
const hex = "0123456789abcdefABCDEF"
const non_hex = "ghijklmnopqrstuvwxyzGHIJKLMNOPQRSTUVWXYZ"
pub fn main() {
let hex_chars = string.to_graphemes(hex)
let non_chars = string.to_graphemes(non_hex)
let #(valid, invalid, mixed) =
int.range(from: 1, to: 100, with: #([], [], []), run: fn(acc, i) {
let #(va, ia, ma) = acc
let mixed = case int.random(6) {
v if v < 3 -> build_sample(hex_chars, i)
_ -> build_sample(non_chars, i)
}
#([build_sample(hex_chars, i), ..va], [build_sample(non_chars, i), ..ia], [
mixed,
..ma
])
})
bench.run(
[
bench.Input("all valid", valid),
bench.Input("all invalid", invalid),
bench.Input("mixed", mixed),
],
[
bench.Function(
"is_hex_case * 1000",
bench.repeat(1000, fn(lists) { list.each(lists, is_hex_case) }),
),
bench.Function(
"is_hex_contains * 1000",
bench.repeat(1000, fn(lists) { list.each(lists, is_hex_contains) }),
),
],
[bench.Duration(2000), bench.Warmup(500)],
)
|> bench.table([bench.IPS, bench.Min, bench.Mean, bench.P(99)])
|> io.println
}
fn is_hex_case(s: String) -> Bool {
string.to_graphemes(s)
|> list.all(fn(ch) {
case ch {
"0"
| "1"
| "2"
| "3"
| "4"
| "5"
| "6"
| "7"
| "8"
| "9"
| "a"
| "b"
| "c"
| "d"
| "e"
| "f"
| "A"
| "B"
| "C"
| "D"
| "E"
| "F" -> True
_ -> False
}
})
}
fn is_hex_contains(s: String) -> Bool {
string.to_graphemes(s)
|> list.all(fn(ch) { string.contains(hex, ch) })
}
fn build_sample(pool: List(String), i: Int) -> String {
list.sample(pool, i) |> string.join("")
}
```
</details>