orestis

orestis

So I’ve started learning Elixir. I took on the Advent of Code challenges, and solved the first day. I’ve skimmed a few books on elixir/erlang, and have had some exposure to FP constructs (including a pleasant semester of Prolog at uni 10 years ago :slight_smile: ).

I came up with the following solution to the day 1 problem. The two test functions showcase what the requirements are (and hopefully the implementation is written such a way what it will be immediately obvious).

Now, after I solved this I searched online to see what other people did. Here are some linked examples:

Reddit Thread
Github 1
Github 2

In those links I see the obvious Enum.reduce approach plus some approaches that seem really weird. I took a classic recursive approach with pattern matching, but only because it’s the feature I prominently remember and I didn’t remember how to write the case construct, so I wanted to see if it would be possible. I have to confess when this code worked, I giggled a bit :slight_smile:

So my question is, is this code considered idiomatic Elixir? The Pythonista in me really likes this code, even if it is a bit more verbose than other solutions. Could I rewrite it to be more idiomatic but keeping the same recursive approach?

Thanks!

defmodule Day1 do
  def santa(s) do
    l = String.codepoints(s)
    santa(l, 0)
  end

  defp santa([], n) do
    n
  end

  defp santa(["(" | tail], n) do
    santa(tail, n + 1)
  end

  defp santa([")" | tail], n) do
    santa(tail, n - 1)
  end

  def test_santa do 
    IO.puts(santa("(())") == 0)
    IO.puts(santa("()()") == 0)
    IO.puts(santa("(((") == 3)
    IO.puts(santa("(()(()(") == 3)
    IO.puts(santa("))(((((") == 3)
    IO.puts(santa("())") == -1)
    IO.puts(santa("))(") == -1)
    IO.puts(santa(")))") == -3)
    IO.puts(santa(")())())") == -3)
  end

  def solve do 
    IO.puts "and now, the punchline"
    {:ok, input} = File.read "day1.input.txt"
    IO.puts(santa input)
    IO.puts(basement input)
  end

  def basement(s) do
    basement(String.codepoints(s), 0, 0)
  end

  defp basement(_, pos, -1) do
    pos
  end

  defp basement(["(" | tail], pos, floor) do
    basement(tail, pos + 1, floor + 1)
  end

  defp basement([")" | tail], pos, floor) do
    basement(tail, pos + 1, floor - 1)
  end

  def test_basement do
    IO.puts(basement(")") == 1)
    IO.puts(basement("()())") == 5)
  end

end


Day1.solve

Showing Posts 1 to 8

OvermindDL1

OvermindDL1

I prefer the classic recursive approach as well, though I’d probably align the santa/2 parts to be more like:

  defp santa([], n)           ,do: n
  defp santa(["(" | tail], n) ,do: santa(tail, n + 1)
  defp santa([")" | tail], n) ,do: santa(tail, n - 1)

Two things though, I notice no clause to handle a non-parenthesis, and this is fine for small inputs but huge inputs then might be better without TCO, basically this:

  def santa(s) ,do: s
    |> String.codepoints
    |> do_santa

  defp do_santa([])           ,do: 0
  defp do_santa(["(" | tail]) ,do: santa(tail) + 1
  defp do_santa([")" | tail]) ,do: santa(tail) - 1

All the above code is untested, but is just my way of doing it. :slight_smile:

Also, instead of the testing you are doing there this would be an almost trivial example case for a property testing library if you want to learn something fascinating and useful. :slight_smile:

orestis

orestis OP

Good call about the alignment of parts (coming from Python I don’t really like the multiple syntax and optional parenthesis, but I can live with them).

I’m not sure I get the part about handling a non-parenthesis, I thought we were supposed to “fail-fast” :wink: - if I wanted to ignore any non-parenthesis I’d just add:

 defp santa([_ | tail], n) ,do: santa(tail, n)

At the end, no?

OvermindDL1

OvermindDL1

If you do not want to accept non-parenthesis then yes, fail fast. ^.^
I’m not sure what the requirements of the exercise are so I did not know. :slight_smile:

But yes, that is how you could ignore them.

orestis

orestis OP

Thanks for the testing links, I didn’t use mix for this so I couldn’t be bothered to use any external stuff.

Can you explain the bit about not using TCO? I thought that TCO was a must as it effectively turns recursion into iteration and you can go to arbitrary lengths without blowing the stack. If I’m not mistaken santa(tail) + 1 will not be TCO.

OvermindDL1

OvermindDL1

Correct, but TCO is not free, and I was typing up a long post but I found a good conversation about it on these forums so deleted it and here is the post that describes it in detail and ‘why’ it is the way it is: Tail Call Optimization in Elixir & Erlang – not as efficient and important as you probably think

In essence, an EVM (Erlang VM) process’s stack is also on the heap and they grow toward each other, so it does not matter which you use from a memory perspective, and overall it is more important to keep memory changes down. So in this specific case TCO would not really hurt, but it is not necessary and would likely be faster without TCO since you have bounded inputs.

The only time TCO should really be focused on is when, as per @rvirding as I recall, only when you have unbounded loops, like a receive loop.

gregvaughn

gregvaughn

I think that manual recursion approach is something everyone needs to know how to do, and it’s great as a learning exercise. That said, however, I’d expect something more like this in production code:

 def santa(s) do
    s |> String.codepoints
      |> Enum.reduce(0, 
        fn "(", n -> n + 1
           ")", n -> n - 1
        end)
  end
orestis

orestis OP

Thanks for that, that does seem nice and understandable. I’m still learning the (many) constructs of Elixir, it didn’t occur to me that even inline functions can have multiple clauses.

In the code linked a lot of people instead use case, but it seems that pattern matching is more idiomatic (if you can use it).

gregvaughn

gregvaughn

Agreed. Technically you do pattern match even in case statements, but yes, there’s a general preference for doing it in function heads (even anonymous function heads). I have run across a circumstance where I felt the code read better by using a case rather than splitting things up across multiple function heads, but I think that’s just an aesthetic one has to acquire with practice.

— All posts loaded —

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews