vmg
Zip list of different lengths
I’m having trouble trying to zip lists of different lengths because elixir only returns the first match of each list.
I have the following list of lists:
[
["e5", "e7", "e7", "e8", "e8", "e2", "e2", "e0", "e0", "e0"],
["B5", "B5", "B5", "B3", "B1", "B1", "B1", "B0", "B1", "B1"],
["G5", "G5", "G5", "G2", "G2", "G2"],
["D7"]
]
Elixir return this:
[{“e5”, “B5”, “G5”, “D7”}]
And I would like to get the following list:
[
["D7", "G5", "B5", "e5"],
["G5", "B5", "e7"],
...,
["B1", "e0"]
]
Is there any way to accomplish this with zip or any other function?
Most Liked
Sebb
not directly possible.
The zipping finishes as soon as any enumerable in the given collection completes. [Enum — Elixir v1.16.0]
Sth like python’s zip_longest would be nice in Enum.
But even that would leave you with some handy work.
Hint: you could pad the lists with nil to fill all up to length of the longest list, and then remove the nils after that.
dimitarvp
akash-akya
@dimitarvp already suggested recursion, this is one way to do that using unfold
data = [
["e5", "e7", "e7", "e8", "e8", "e2", "e2", "e0", "e0", "e0"],
["B5", "B5", "B5", "B3", "B1", "B1", "B1", "B0", "B1", "B1"],
["G5", "G5", "G5", "G2", "G2", "G2"],
["D7"]
]
Stream.unfold(data, fn list_of_list ->
List.foldr(list_of_list, {[], []}, fn
[], {heads, tails} -> {heads, tails}
[h], {heads, tails} -> {[h | heads], tails}
[h | t], {heads, tails} -> {[h | heads], [t | tails]}
end)
|> case do
{[], _tails} -> nil
{heads, tails} -> {heads, tails}
end
end)
|> Enum.to_list()
Sebb
def zip([], zipped), do: zipped
def zip(lists, zipped) do
{zipped_, rest_} =
for [h | t] <- lists, reduce: {[], []} do
{hs, ts} -> {[h | hs], ts ++ [t]}
end
zip(Enum.reject(rest_, &(&1 == [])), zipped ++ [zipped_])
end
zip(data, []) #=> [
["D7", "G5", "B5", "e5"],
["G5", "B5", "e7"],
["G5", "B5", "e7"],
["G2", "B3", "e8"],
["G2", "B1", "e8"],
["G2", "B1", "e2"],
["B1", "e2"],
["B0", "e0"],
["B1", "e0"],
["B1", "e0"]
]
or using Enum.zip with help
def zip2(lists) do
max_len = lists |> Enum.map(&length(&1)) |> Enum.max()
lists
|> Enum.map(&(&1 ++ List.duplicate(nil, max_len - length(&1))))
|> Enum.zip()
|> Enum.map(& &1 |> Tuple.to_list() |> Enum.reject(fn x -> x == nil end))
end
Eiji
Here you go:
defmodule Example do
def sample(input) do
input
|> Enum.reduce({[], []}, fn
# when list contains only one element
# we add it as others and skipping empty list in next steps
[head], {left_acc, right_acc} -> {[head | left_acc], right_acc}
# in other case
# we add list's head and pass its tail, so it would be used in next steps
[head | tail], {left_acc, right_acc} -> {[head | left_acc], [tail | right_acc]}
end)
|> case do
# since we have lists of lists we need to wrap last list in another one-element list
# so it's added as one, last element of result
# instead of adding all elements of last list as separate result elements
# [
# list1,
# list2,
# # …
# listn_elem1,
# listn_elem2
# ]
{left_acc, []} -> [left_acc]
# left_acc here is a list of heads for each step
# tail is a recursive call of input lists except their heads
# we need to reverse it as otherwise each step would have opposite order to previous step
{left_acc, right_acc} -> [left_acc | right_acc |> Enum.reverse() |> sample()]
end
end
end
input = [
["e5", "e7", "e7", "e8", "e8", "e2", "e2", "e0", "e0", "e0"],
["B5", "B5", "B5", "B3", "B1", "B1", "B1", "B0", "B1", "B1"],
["G5", "G5", "G5", "G2", "G2", "G2"],
["D7"]
]
iex> Example.sample(input)
Helpful resources:







