saudade
Hi there! Newbie to the language looking for tips on how to write more idiomatic elixir.
The problem: Given a list of intervals (start, end), merge any overlapping intervals and return the new list.
Ex:
iex(1)> intervals = [%{start: 1, end: 2},%{start: 2,end: 3}, %{start: 4, end: 6}, %{start: 7, end: 10}, %{start: 9, end:
12}]
iex(2)> Intervals.merge_intervals intervals
[%{end: 3, start: 1}, %{end: 6, start: 4}, %{end: 12, start: 7}]
Here’s what I’ve got working:
defmodule Intervals do
def merge_intervals(intervals) do
Enum.sort(intervals, &(&1.start <= &2.start))
|> _merge_intervals([])
|> Enum.reverse
end
defp _merge_intervals([], merged_intervals), do: merged_intervals
defp _merge_intervals([%{end: cur_end} = cur, %{start: next_start} = next | tail], merged_intervals) when cur_end >= next_start do
collapsed = _collapse_intervals(cur,next)
_merge_intervals(tail, [collapsed | merged_intervals])
end
defp _merge_intervals([head | tail], merged_intervals), do: _merge_intervals(tail, [head | merged_intervals])
defp _collapse_intervals(a, b), do: %{start: a.start, end: b.end}
end
Obviously looking for any no-brainer idiomatic improvements, but my one nagging question is if inclusion of the second merge_intervals definition is the right way to do things. It seems significantly more difficult to read (especially pattern matching to extract cur_end and next_start) than throwing a control-flow if into the body of the function definition. I know that pattern matching is powerful, but is there a compelling reason to use that functionality in a case like this one? Is there a better way to do this?
Thanks!
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










First 10 of 10 Posts
minhajuddin
Looks idomatic to me. A few changes I would make are:
_prefixes, I haven’t seen used this in Elixir for private functions. You can just use the same name as the arity is different.whena new linesaudade
Would my above solution be preferable to a standard reduce?
Three questions related to that solution:
Notice that I now am only piping two functions instead of three. It seemed to be required to start off the reduce with the first interval in the accumulator already and I couldn’t figure out how to achieve the [head | tail] match without removing my first pipe. Is there a way do that?
Is there a way to define the merge_step function as an anonymous function on multiple lines? I come from the javascript world where something like:
intervals.reduce( (acc, int) => {
…work
});
is standard. I tried the fn(int,acc) → end syntax but it seems like that only works if the end is on the same line.
To point 2 that you made: This was suggested in Programming Elixir 1.3. I’m happy to choose away from it, as I’ve never liked leading underscores anyway, but would be interested in what the consensus is.
bbense
Leading underscores for private functions is a terrible idea IMHO. Leading underscores have meaning to the compiler for variable names. Adding them to functions just leads to confusion.
benwilson512
Correct me if I’m wrong, but don’t you want to sort by
&(&1.start <= &2.start)?saudade
Yep. Fixed that in the above code.
peerreynders
I think in this case the
reduceversion is more readable - but that doesn’t mean thatreduceis always better than recursion - context is everything.Actually according the Elixir style guide that code should be written as
Well you could do this
… but I don’t see anything to recommend that approach.
Works fine, no problems …
… but why for heavens sake would you choose to write code like this??? I find this infinitely more readable:
i.e. no anonymous functions “cleverly” interleaving code everywhere, but rather well-named functions that describe what they do (so OK,
consis a bit conceited). Anonymous functions have their place but I use them when I need a closure - other than that I prefer well named functions.And you’ve just hit one of my pet peeves with the “JavaScript community style” - to quote my own rant:
The main issue with
ifis that it looks like a statement when in fact it is an expression - anifwithout anelsewill return a value ofnil(which actually is:nil, i.e. its an atom) when the condition fails. So I personally focus on usingcondandcase(as shown in the example above) to remind myself that everything is an expression.Also unless you are planning to add more information to the maps, I think its perfectly OK to use tuples in this case:
saudade
Great stuff, thanks!
One question: What’s going on here?
do: lhs <= rhsHaven’t seen this syntax yet. Is it effectively a merge?
OvermindDL1
Nope, all this does is the body of the function (delineated via the
do:) just compares the left and right bindings of the nameslhsandrhsusing the<=operator, basically just like1 <= 2, and returnstrueorfalsedepending on if thelhsis less than or equal to therhs.peerreynders
This could be considered another refinement
It’s very important to recognize that pattern matching is a conditional construct - unlike JavaScript’s Destructuring Assignment.
I personally also view the guard sequence (
when-clause) as an extension of the pattern match as the guard sequence can supply match conditions that would be difficult to express within the pattern itself.saudade
…that makes more sense