There is sometimes cases where you want to map every nth item starting on the nth item. Eg map every 2nd item starting on the 2nd one. Currently the map_every/3
always gives back the first item in the enumerable and then every nth item. I propose to add an optional parameter to the function for the offset for the first item. Another option would be to add a parameter to start on the nth item instead but that’s probably less flexible than the offset. What’s your thoughts? Is there another way to do this with the existing APIs?
@mackeyja92 I’d probably use Enum.drop
.
eg: “Map every 4th thing starting on the 2nd thing”
list |> Enum.drop(2) |> Enum.map_every(4, fun)
The problem with drop is it removes the items from the list. If you only need to do a certain operation on nth items but need to do another operation on the whole list after you’re stuck with removing items and storing in a temporary list and then pushing them back on the front after the map_every.
A real world example is the Luhn checksum.
# Reverse them first
numbers = Enum.reverse(numbers)
# Pop the first one off because elixir always does the first one when using map_every
{first, numbers} = List.pop_at(numbers, 0)
numbers
|> Enum.map_every(2, fn num ->
num = num * 2
# If the number after doubling is greater than 9 then subtract 9
if num > 9 do
num - 9
else
num
end
end)
# Put the first one back
|> List.insert_at(0, first)
# Sum them all
|> Enum.sum()
# If the modulo of 10 is 0 then it is a valid checksum
|> Kernel.rem(10)
|> Kernel.==(0)
I believe that is a perfectly fine solution. If you need to perform different operations on even and odd elements you could also try chunking the list.
E.g.
[1,2,3,4,5,6]
|> Enum.chunk_every(2)
# [[1,2], [3,4], [5,6]]
|> Enum.map(fn [first, second] -> [first*2, second*3] end)
|> List.flatten
# [2, 6, 6, 12, 10, 18]
That doubles every odd element and triples every even one.
I had fun turning the Luhn checksum into a simple Elixir pipeline a couple of years ago. The algorithm as stated is of a very imperative/mutable mindset, but you can do it with immutable pipelines.
If you want to get “fancy”, then you could simply use Enum.with_index
and Enum.map
. Bonus points if you use the Stream module
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|> Enum.with_index()
|> Enum.map(fn {num, index} ->
cond do
index < 4 -> num
rem(num, 2) == 0 -> num * 2
true -> num * 3
end
end)
[1, 2, 3, 4, 15, 12, 21, 16, 27, 20]