Reputation: 313
Given a list, how can I split it into N sub-lists? They don't have necessarily have to be of the equal size. For instance, given 9 elements and split it into N = 3 sub-lists => 3x3. Or into N = 4 sub-lists => 2, 2, 2 and 1.
How can I do that? Isn't there a function in Elixir library?
Enum.split
splits a list into 2 parts
update:
If I have 7 elements and I want to split them into 3 sub-lists, there should be created 3 sublists:
[[3 elements], [2 elements], [2 elements]]
Namely, I want to preserve all the elements
Upvotes: 5
Views: 4517
Reputation: 5926
Found this question, but none of the current answers worked for my use case, for which I wanted the group sizes to be as uniform as possible. For example:
Enum.to_list(1..100) |> chunk_uniformly(6) |> Enum.map(&length/1)
# desired result is [17, 17, 17, 17, 16, 16]
# current accepted answer returns [20, 16, 16, 16, 16, 16]
Here's my eventual solution:
def chunk_uniformly(list, number_of_chunks) do
len = length(list)
optimal_chunk_size = div(len + number_of_chunks - 1, number_of_chunks)
overspill = optimal_chunk_size * number_of_chunks - len
{full_chunks, partial_chunks} =
list
|> Enum.split((number_of_chunks - overspill) * optimal_chunk_size)
Enum.concat(
full_chunks |> Enum.chunk_every(optimal_chunk_size),
case partial_chunks do
[] -> List.duplicate([], overspill)
ls -> ls |> Enum.chunk_every(optimal_chunk_size - 1)
end
)
end
If, like in my use case, you're actually working from a maximum chunk size, you can get the number of chunks like this:
number_of_chunks = div(length(list) + max_chunk_size - 1, max_chunk_size)
Upvotes: 0
Reputation: 121000
Let’s assume we want 3 chunks:
list = [1, 2, 3, 4, 5, 6, 7]
chunk_count = 3
chunk_length =
list
|> Enum.count()
|> div(chunk_count)
list
|> Enum.with_index() # to calculate position
|> Enum.reduce(%{}, fn {e, i}, acc ->
Map.update(acc, rem(div(i, chunk_length), chunk_count), [e], &([e | &1]))
end)
|> Map.values() # since we do need lists
|> Enum.map(&Enum.reverse/1)
#⇒ [[1, 2, 7], [3, 4], [5, 6]]
If lists in Elixir were to implement Access
behaviour, the code would be much simpler as we could use Kernel.put_in/3
.
Upvotes: 0
Reputation: 1970
You could consider using Enum.chunk_every
to split the list into sublists of n amount elements each:
some_list = [1, 2, 3, 4, 5, 6]
Enum.chunk_every(some_list, 2)
[[1, 2], [3, 4], [5, 6]]
By calculating the total length of the list first:
total_length = length(some_list)
and dividing that number with the desired amount of parts giving you the length of each chunk:
desired_amount_of_sublists = 3
chunk_length = Integer.floor_div(total_length, desired_amount_of_sublists)
should allow you to arbitrarily chunk the list into as many parts you require:
Enum.chunk_every(some_list, chunk_length)
[[1, 2], [3, 4], [5, 6]]
In the case that you have a hard requirement to have each sublist be of exactly n elements, then you can pass in the option :discard
to discard the last sublist if it is less than n elements:
Enum.chunk_every([1,2,3,4,5,6,7], 2, 2, :discard)
[[1, 2], [3, 4], [5, 6]]
In the case that you have the hard requirement where you cannot discard any element and for instance you need the remainder elements to be incorporated with the first sublist, then you can do as follows:
Say that with the above you arrive at:
result_so_far = Enum.chunk_every([1,2,3,4,5,6,7], 2)
[[1, 2], [3, 4], [5, 6], [7]]
First reverse result_so_far
, and take the first sublist thereof, which would be [7]
, as follows:
[last_sublist | other_sublists] = Enum.reverse(result_so_far)
Then you check the length of last_sublist
. If it corresponds to chunk_length
, then you are fine, result_so_far
has the desired result.
In the case it is smaller than chunk_length
, you will need to incorporate its elements with the first sublist of result_so_far
, which you can do as follows:
[first_sublist | rest ] = Enum.reverse(other_sublists)
[Enum.concat(first_sublist, last_sublist) | rest]
should then render
[[1, 2, 7], [3, 4], [5, 6]]
Upvotes: 8