Djordjenp

Djordjenp

Function gives "2nd argument: not valid character data" error

I am new to elixir and I am trying to make a recursive anonymous function, but for some reason my anynymous function that works on it’s own as expected, throws me “2nd argument: not valid character data (an iodata term)” error.

Here is code:

calcTip = fn bill ->
  if bill >= 50 and bill <= 300, do: bill * 0.15, else: bill * 0.2
end

bills = [ 22, 295, 176, 440, 37, 105, 10, 1100, 86, 52 ]

calcTipsAndTotals = fn index, tips, totals, recursiveFn ->
  case index < length(bills) do
    true ->
      new_tip = calcTip.(Enum.at(bills, index, 0))
      new_tips = tips ++ [new_tip]
      new_totals = totals ++ [Enum.at(new_tips, index) + Enum.at(bills, index)]
      recursiveFn.(index + 1, new_tips, new_totals, recursiveFn)
    false -> [tips, totals]
  end
end

IO.puts(
  calcTipsAndTotals.(0, [], [], calcTipsAndTotals)
)

Marked As Solved

Also Liked

al2o3cr

al2o3cr

Not directly related to your question, but a general tip for new Elixir devs: calling functions like length and Enum.at inside a loop should make you slightly worried about performance.

The reason is that both of those functions take time that’s proportional to the size of the input (bills here usually), unlike other languages where arrays can be accessed in a constant amount of time. This means that calculating something like length(bills) inside a recursion over bills will immediately be accidentally quadratic.

IMO a good general principle is to avoid using indexes as much as possible. For instance, in your code above, every call to Enum.at uses the same index so the whole thing can be rewritten as an Enum.map:

calcTip = fn bill ->
  if bill >= 50 and bill <= 300, do: bill * 0.15, else: bill * 0.2
end

bills = [ 22, 295, 176, 440, 37, 105, 10, 1100, 86, 52 ]

tips_and_totals_as_pairs =
  Enum.map(bills, fn bill ->
    tip = calcTip.(bill)
    {tip, tip + bill}
  end)

tips_and_totals = Enum.unzip(tips_and_totals_as_pairs)

or an alternate version with explicit recursion, if that’s a requirement:

calcTip = fn bill ->
  if bill >= 50 and bill <= 300, do: bill * 0.15, else: bill * 0.2
end

bills = [ 22, 295, 176, 440, 37, 105, 10, 1100, 86, 52 ]

calc_tips_and_totals = fn
  [bill | rest], tips, totals, recursive_fn ->
    tip = calcTip.(bill)
    calc_tips_and_totals(rest, [tip | tips], [tip + bill | totals], recursive_fn)
  [], tips, totals, _ ->
    [Enum.reverse(tips), Enum.reverse(totals)]
  end

tips_and_totals = calc_tups_and_totals(bills, [], [], calc_tips_and_totals)

Some general notes from the above:

  • to know when to stop, instead of checking length (which is expensive) this pattern-matches on [bill | rest] vs [] (which is super-cheap)
  • instead of appending to the end of lists with totals ++ [new_value], this adds to the beginning of the list (super-cheap again) and then reverses at the end. See the BEAM Efficiency Guide for some additional discussion on this.
  • both of the versions above produce separate lists for tips and totals, but you may want to consider keeping those things together either as a tuple (omit the Enum.unzip) or even a map/struct. That way the values for a particular bill are always in one place, instead of spread across multiple lists.

Where Next?

Popular in Questions Top

hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36820 110
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 44265 214
New

We're in Beta

About us Mission Statement