ahmadferdous

ahmadferdous

Why should we iterate a list twice to pipe 2 aggregate functions?

I have structs Company, Employee and a list of struct values employees. Struct Employee contains 2 fields - hours and bill.

I need to know total hours and total bill. Should I iterate once (for loop?!) to calculate both together or should we do the following? One of my colleagues say the following is Elixir-y. Why is iterating multiple times encouraged when it is inefficient?

I’m sure there is a good reason and I want to know. Please help me.

def calculate(company) do
    company
    |> calculate_total_hours()
    |> calculate_total_bill()
end

def calculate_total_hours(company) do
  total = company.employees
  |> Enum.map(& &1.hours)
  |> Enum.sum()

  %{company | total_hours: total}
end

def calculate_total_bill(company) do
  total = company.employees
  |> Enum.map(& &1.bill)
  |> Enum.sum()

  %{company | total_bill: total}
end

EDIT: Updated code

Marked As Solved

al2o3cr

al2o3cr

It would take very careful benchmarking to clearly show whether the single reduce is faster or slower than the two passes over the list. My back-of-the-envelope calculation:

Split loops, per element in the list:

  • two “get rest of list”
  • two map accesses (to get bill or hours)
  • two additions

Fused loops, per element in the list:

  • two map accesses (maybe? Not 100% sure what that pattern-match compiles to)
  • one “get rest of list”
  • two additions
  • one “create new tuple”

Both have two map accesses and two additions per element in the list, as well as a single “get rest of list”; so the budget tradeoff comes down to an additional “get rest of list” versus a “create new tuple” operation.

The BEAM’s optimizer is going to complicate things even further; it might be able to elide most of the cost of “create new tuple” by reusing the tuple passed as an argument - but if the split-loop functions are called more often they might get JITed more aggressively.

Also Liked

gregvaughn

gregvaughn

I would likely write this as a single reduce, and I don’t think it suffers elixir-y-ness

{total_hours, total_bill} =
  Enum.reduce(company.employees, {0, 0}, fn %{hours: h, bill: b}, {h_acc, b_acc} ->
    {h_acc + h, b_acc + b}
  end)
10
Post #4
aenglisc

aenglisc

“Make it work, then make it beautiful, then if you really, really have to, make it fast. 90 percent of the time, if you make it beautiful, it will already be fast. So really, just make it beautiful!”

tomkonidas

tomkonidas

You linked the wrong repo :wink:

al2o3cr

al2o3cr

Because it’s easier to read operations that are cleanly decomposed into smaller parts, and because “inefficient” is relative. Traversing a list is a very cheap operation on the BEAM (it’s almost a Lisp under the hood).

It’s hard to say more about the specific example since it doesn’t work: calculate_total_hours returns a number but is piped into a function that expects a list, and it’s unclear where the total hours and total bill should be stored in company.

lud

lud

If you want only calculate_total_hours you can call that single function. It may not bee a good idea to bind two independent pieces of logic together, unless you are 100% sure that you will always need to call the two computations. And in that case I would just follow above advice: make it beautiful then move on.

Where Next?

Popular in Questions Top

lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
kostonstyle
Hi all I want to have a unix time, from the current time plus 1 hour. DateTime.now + 1 hour How to get it in elixir? Thanks
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

Other popular topics Top

chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30048 115
New
peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New

We're in Beta

About us Mission Statement