minhajuddin
Is there a simpler way to generate ids
I am using the code below to generate room ids:
reservations = [%{rooms: 1},
%{rooms: 2},
%{rooms: 3}]
{reservations_with_room_numbers, _} =
Enum.reduce(reservations, {[], 0}, fn reservation, {acc, offset} ->
upper_limit = offset+reservation.rooms - 1
room_numbers = Enum.to_list(offset..upper_limit)
reservation_with_room_numbers = Map.put(reservation, :room_numbers, room_numbers)
{[reservation_with_room_numbers|acc], upper_limit + 1}
end)
IO.inspect Enum.reverse(reservations_with_room_numbers)
# =>
# [
# %{room_numbers: [0], rooms: 1}
# %{room_numbers: [1, 2], rooms: 2},
# %{room_numbers: [3, 4, 5], rooms: 3},
# ]
The code above seems too complicated for such a simple task, Is there a simpler way to do the same?
System.unique_integer [:monotonic, :positive] gives out unique integers starting at 1, However there is no way to reset it after the generation is done, so that it starts from 1 for the next call.
Marked As Solved
josevalim
I believe your initial solution looks good, I would just use map_reduce instead of reduce to clean things up:
reservations = [%{rooms: 1},
%{rooms: 2},
%{rooms: 3}]
{reservations_with_room_numbers, _} =
Enum.map_reduce(reservations, 0, fn reservation, offset ->
upper_limit = offset+reservation.rooms - 1
room_numbers = Enum.to_list(offset..upper_limit)
reservation_with_room_numbers = Map.put(reservation, :room_numbers, room_numbers)
{reservation_with_room_numbers, upper_limit + 1}
end)
Also Liked
JEG2
Or, with a generator:
defmodule Generator do
def new(start) do
Agent.start_link(fn -> start end)
end
def next(generator) do
Agent.get_and_update(generator, fn value -> {value, value + 1} end)
end
end
defmodule RoomBuilder do
def build([{:rooms, n} | rooms], built, generator) do
room_numbers =
Stream.repeatedly(fn -> Generator.next(generator) end)
|> Enum.take(n)
build(rooms, [room_numbers | built], generator)
end
def build([ ], built, _generator), do: Enum.reverse(built)
end
{:ok, generator} = Generator.new(0)
reservations = [rooms: 1, rooms: 2, rooms: 3]
RoomBuilder.build(reservations, [ ], generator)
|> IO.inspect
benwilson512
One thing that isn’t clear is if we want a pure or impure solution. The original post had both. They’re wildly different solutions and people need to be aware of that.
michalmuskala
I believe this shows another use case for a function like Stream.split/2 - similar to Enum.split/2, but one that would leave the “rest” part as a stream. This would allow for the most natural way to solve this:
ids = Stream.itrerate(1, &(&1 + 1))
Enum.map_reduce(reservations, ids, fn reservation, ids ->
{room_numbers, rest} = Stream.split(ids, reservation.rooms)
{Map.put(reservation, :room_numbers, room_numbers), rest}
end) |> elem(0)
sasajuric
It requires more code, but I tend to wrap such reductions in separate modules. Not sure about the wider context of your problem, so let’s say that multiple reservations make a singe “booking”. Then, I can have the booking module:
defmodule Booking do
def new(), do: %{offset: 0, reservations: []}
def reserve(booking, rooms) do
reservation =
%{rooms: rooms, room_numbers: Enum.map(1..rooms, &(&1 + booking.offset))}
%{booking |
reservations: [reservation | booking.reservations],
offset: booking.offset + rooms
}
end
def reservations(booking), do: Enum.reverse(booking.reservations)
end
That’s more LOC, but on the upside, the client code can IMO become more readable:
[%{rooms: 1}, %{rooms: 2}, %{rooms: 3}]
|> Enum.reduce(Booking.new(), &Booking.reserve(&2, &1.rooms))
|> Booking.reservations()
# [%{room_numbers: [1], rooms: 1}, %{room_numbers: [2, 3], rooms: 2},
# %{room_numbers: [4, 5, 6], rooms: 3}]
If the module functionality is small, and it’s used in only one place, I just “inline” these funs (in this case new/0, reserve/2, reservations/1), i.e. implement them as private in the same module where I use them.
josevalim
for Stream.split/2.








