Stream: beginners

Topic: Looping through a string


view this post on Zulip John Konecny (Jun 01 2024 at 23:45):

I'm working on day 1 of advent of code 2023 and it requires me to loop through a string, find the first and last numbers characters of the string, and combine them.

So for example if I had the string "x1dsdf425dk79dd" the result should be 19. The code I have below works, but it's pretty hacky. I was wondering if there's a better way to loop through a string. I also don't like how I did char = Result.withDefault (Str.fromUtf8 [byte]) "" to get the character, but it didn't seem like there was any other way to do it.

firstAndLastDigits = \s ->
    {first, last} = Str.walkUtf8 s { first: "", last: "" } \state, byte ->
        char = Result.withDefault (Str.fromUtf8 [byte]) ""
        isNumber = Result.isOk(Str.toU8 char)
        if Bool.and (Str.isEmpty state.first) isNumber then
            { first: char, last: char }
        else if isNumber then
            { state & last: char }
        else
            state
    Str.concat first last |> Str.toU64 |> Result.withDefault 0

view this post on Zulip Brendan Hansknecht (Jun 01 2024 at 23:47):

For advent of code, I would generally advise using List U8 as your default data type instead of string

view this post on Zulip Brendan Hansknecht (Jun 01 2024 at 23:47):

For anything pure ascii with lots of character based manipulation (like advent of code) that is generally better.

view this post on Zulip Brendan Hansknecht (Jun 01 2024 at 23:47):

Also, there is #Advent of Code > 2023 Day 1 with discussion on this problem and multiple solutions

view this post on Zulip Brendan Hansknecht (Jun 01 2024 at 23:50):

Also for advent of code, where you really don't care to handle errors, I prefer to write an unwrap helper function:

unwrap = \res ->
  when res is
    Ok v -> v
    Err e -> crash e

view this post on Zulip John Konecny (Jun 01 2024 at 23:59):

Sweet, thanks for the quick reply! I'll take a look at that thread.

view this post on Zulip Brendan Hansknecht (Jun 02 2024 at 00:04):

Oh, actually, unwrap should be:

unwrap = \res ->
   when res is
     Ok v -> v
     Err e -> crash (Inspect.toStr e)

view this post on Zulip Luke Boswell (Jun 02 2024 at 00:12):

I would encourage using AoC problems as a good way to learn proper error handling in Roc. It takes a bit of a mind shift, but once you wrap your head around it, it's really nice!

view this post on Zulip John Konecny (Jun 02 2024 at 00:32):

Thanks for all the help! This is much cleaner!

I avoided crashing for the extra challenge.

module []
import "example1.txt" as example1 : Str
import "example2.txt" as example2 : Str
import "real.txt" as real : Str


isDigit = \b ->
    b == '0' ||
    b == '1' ||
    b == '2' ||
    b == '3' ||
    b == '4' ||
    b == '5' ||
    b == '6' ||
    b == '7' ||
    b == '8' ||
    b == '9'

firstAndLastDigits = \s ->
    numbers = s
        |> Str.toUtf8
        |> List.keepIf isDigit
        |> List.keepOks (\x -> [x] |> Str.fromUtf8)
    first = List.first numbers
    last = List.last numbers
    when (first, last) is
        (Ok f, Ok l) -> Str.toU64 "$(f)$(l)"
        _ ->
            dbg "\"$(s)\" has no numbers???"
            Err NoNumberChars

solution1 = \fileContents ->
    fileContents
        |> Str.split "\n"
        |> List.keepOks firstAndLastDigits
        |> List.sum

solution2 = \fileContents ->
    fileContents
        |> Str.split "\n"
        |> List.map (\x -> Str.replaceEach x "one" "one1one")
        |> List.map (\x -> Str.replaceEach x "two" "two2two")
        |> List.map (\x -> Str.replaceEach x "three" "three3three")
        |> List.map (\x -> Str.replaceEach x "four" "four4our")
        |> List.map (\x -> Str.replaceEach x "five" "five5five")
        |> List.map (\x -> Str.replaceEach x "six" "six6six")
        |> List.map (\x -> Str.replaceEach x "seven" "seven7seven")
        |> List.map (\x -> Str.replaceEach x "eight" "eight8eight")
        |> List.map (\x -> Str.replaceEach x "nine" "nine9nine")
        |> List.keepOks firstAndLastDigits
        |> List.sum

expect solution1 example1 == 142
expect solution1 real == 54605
expect solution2 example2 == 281
expect solution2 real == 55429

view this post on Zulip Hristo (Jun 02 2024 at 06:21):

Optionally, your isDigit could be made a bit less error prone like so:

isDigit = \n -> n >= '0' && n <= '9'

view this post on Zulip Hannes (Jun 02 2024 at 08:46):

Quick plug for my package roc-ascii which might be useful for AoC :)

view this post on Zulip Chris Duncan (Jun 07 2024 at 12:39):

@Hannes

What's the difference between Ascii and String in Roc?

view this post on Zulip Norbert Hajagos (Jun 07 2024 at 13:53):

Glancing at the package, it is a string type that assumes fixed-width (ascii) characters, whereas Roc builtin strings are utf8 encoded, which is a variable-length encoding. The big difference is that you can index into an ascii string (like in most of the languages) and can expect to get a valid ascii character, while the same can't be said to an utf8 string, cause you can index into the middle of a "character that actually spreads multiple bytes and shouldn't be called a character". Like emojies.
If you are sure you will not need to handle utf8 strings, using the ascii package will give you more functions on (ascii) strings than what is available in the builtin String module, since ascii doesn't have the limitation of variable-length encoding.

view this post on Zulip Hannes (Jun 07 2024 at 13:59):

Couldn't have said it better myself Norbert! I might borrow some of that explanation for roc-ascii's readme!

view this post on Zulip Norbert Hajagos (Jun 07 2024 at 14:02):

Glad to help! Sure, do so!


Last updated: Jul 06 2025 at 12:14 UTC