Earlier chapters focused on list processing via recursion. Now we’re getting into the more familiar iterables.
Enums
Stream is similar to the Python Generator; calculate next value as needed.
Enum.at
uses a zero-based index.Enum.att(10..20, 0)
is10
.After working through the first set of exercises, the need to use recursion vs. iteration became clearer.
The countdown example introduces message passing.
Comprehension simplifies the earlier
filter
exercise.
Rewrite the Filter Exercise
Rewrite the Lists and Recursions 5 filter
using comprehension.
defmodule Ch10 do
def filter(lst, func) do
for x <- lst, func.(x), do: x
end
end
Ch10.filter([1, 2, 3], fn x -> rem(x, 2) == 0 end)
Exercise: Lists and Recursions 5
all?
Apply a function to the collection. Return true if all evaluations are true, else false.
We’ve not yet discussed how to break out of iterations. Solution is inefficient for large lists.
defmodule Ch10 do
def all?([], _) do
false
end
def all?(lst, func) do
Enum.reduce(Enum.map(lst, func), true, &(&1 and &2))
end
end
Ch10.all?([], &(&1 > 5))
Ch10.all?([8, 9, 10], &(&1 > 5))
Ch10.all?([8,3,21], &(&1 > 5))
each
Invoke the given func for each element in the enumerable and return :ok. The Elixir documentation example shows using it with IO.puts.
defmodule Ch10 do
def each(lst, func) do
for el <- lst do
func.(el)
end
:ok
end
end
Ch10.each([1, 3, 5], &(IO.puts(&1)))
filter
defmodule Ch10 do
# filter
# Return only those elements for which func returns a truthy value.
# Finally gave up and looked up the official implementation.
# I'm still thinking in too much Python.
# List generation is different in Elixir.
# (This is not the official implementation.)
defp filter_list([], _) do
[]
end
defp filter_list([head | tail], func) do
if func.(head) do
[head | filter_list(tail, func)]
else
filter_list(tail, func)
end
end
def filter(lst, func) do
filter_list(lst, func)
end
end
Ch10.filter([1, 2, 3], fn x -> rem(x, 2) == 0 end)
split
Splits the enumerable into two enumerables, leaving the count elements in the first one. If count is a negative number, it starts counting from the back to the beginning of the enumerable.
Ended up looking up this code as well as I could not figure out how to return the tuple. I now understand that pattern matching and guards can be used to return the final value that is different from what the recursion returns.
The recursion is building an accumulator and what is left of the list.
After recursing through the count
number of elements, return the accumulated elements and the list remnant in the tuple.
The accumulated element list must be reversed because the iteration prepends.
take
Returns an amount of elements from the beginning (positive amount) or end (negative amount) of the enumerable.
I could simply call split and return first element of the tuple but I implemented it long-form using what I learned about split.
defmodule Ch10 do
defp take_up_to(_, 0, acc) do
:lists.reverse(acc)
end
defp take_up_to([], _, acc) do
:lists.reverse(acc)
end
defp take_up_to([head | tail], count, acc) do
take_up_to(tail, count - 1, [head | acc])
end
def take([], _) do
[]
end
def take(lst, count) when count < 0 do
:lists.reverse(take_up_to(:lists.reverse(lst), abs(count), []))
end
def take(lst, count) do
take_up_to(lst, count, [])
end
end
Ch10.take([1,2,3], -2)
Exercise: Lists and Recursions 6 – First Attempt
Write a flatten(list). The exercise is marked as hard and notes I may need to call Enum.reverse. Did not find it difficult and did not need to reverse list. Obviously not approaching it properly. It was also inefficient as appending two lists requires a list copy.
defmodule Ch10 do
defp flatten_it([]) do
[]
end
defp flatten_it([head | tail]) when is_list(head) do
# Head is a list.
IO.inspect head, label: "sublist"
flatten_it(head) ++ flatten_it(tail)
end
defp flatten_it([head | tail]) do
# Head is not a list.
IO.inspect head, label: "element"
[head | flatten_it(tail)]
end
def flatten(lst) do
flatten_it(lst)
end
end
Ch10.flatten([ 1, [ 2, 3, [4] ], 5, [[[6]]]])
Exercise: Lists and Recursions 6 – Recursively
Re-solved the problem with recursion.
defmodule Ch10 do
defp flatten_it([], acc) do
acc
end
defp flatten_it([head | tail], acc) when is_list(head) do
# Head is a list.
IO.inspect head, label: "sublist"
flatten_it(tail, flatten_it(head, acc))
end
defp flatten_it([head | tail], acc) do
# Head is not a list.
IO.inspect head, label: "element"
flatten_it(tail, [head | acc])
end
def flatten(lst) do
Enum.reverse(flatten_it(lst, []))
end
end
Ch10.flatten([ 1, [ 2, 3, [4] ], 5, [[[6]]]])
ListsAndRecursion-7
Used the prime?
function from a Steve Molloy gist
defmodule Ch10 do
# ListsAndRecursions-4
def span(from, to) when from==to, do: [to]
def span(from, to) do
[from | span(from+1, to)]
end
def prime?(2), do: :true
def prime?(num) do
last = num
|> :math.sqrt
|> Float.ceil
|> trunc
notprime = 2..last
|> Enum.any?(fn a -> rem(num, a)==0 end)
!notprime
end
end
for x <- Ch10.span(1,100), Ch10.prime?(x), do: x
ListsAndRecursions-8
defmodule Ch10 do
def add_tax(orders, tax_rates) do
taxed = for order <- orders do
state = order[:ship_to]
tax = tax_rates[state]
if is_nil(tax) do
# Return order with 0 tax.
Keyword.put order, :tax, 0.00
else
Keyword.put order, :tax, (order[:net_amount] * tax)
end
end
taxed
end
end
tax_rates = [ NC: 0.075, TX: 0.08]
orders = [
[ id: 123, ship_to: :NC, net_amount: 100.00 ],
[ id: 124, ship_to: :OK, net_amount: 35.50 ],
[ id: 125, ship_to: :TX, net_amount: 24.00]
]
Ch10.add_tax(orders, tax_rates)
All notes and comments are my own opinion. Follow me at @rgacote@genserver.social