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.

2 Likes
Can be cleaned up a bit with functions something like:

def rwrn(reservations, start \\0, acc \\[])
def rwrn([],_,acc), do: Enum.reverse(acc)
def rwrn([%{rooms: rooms} | t], start, acc) do
  upto = start + rooms
  rwrn(t, upto, [%{rooms: rooms, room_numbers: Enum.to_list(start..upto - 1)} | acc])
end
2 Likes

Thanks this is definitely clearer than the first implementation :slight_smile:

This would be easy in javascript with a simple generator function. I was hoping there would be some generator function which keeps giving new ids.

var makeGenerator = function(offset){
  return function(){
    offset += 1;
    return offset;
  }
};

var generator = makeGenerator(0);

var reservations = [{rooms: 1}, {rooms: 2}, {rooms: 3}];
reservations.map(x => {
  x.room_numbers = _.times(x.rooms, x => generator())
  return x;
})

1 Like

Stream.iterate(offset, &(&1+1)) ?

1 Like

The js code persists the state, so it can give new ids, However this code gives a new set of ids starting from 0 every time.

s = Stream.iterate(0, &(&1+1))

IO.inspect Enum.take(s, 2) #=> [0, 1]
IO.inspect Enum.take(s, 2) #=> [0, 1]
1 Like
defmodule RoomBuilder do
  def build([{:rooms, n} | rooms], built, next_id) do
    room_numbers =
      Stream.iterate(next_id, &(&1 + 1))
      |> Enum.take(n)
    build(rooms, [room_numbers | built], next_id + n)
  end
  def build([ ], built, _generator), do: Enum.reverse(built)
end

reservations = [rooms: 1, rooms: 2, rooms: 3]
RoomBuilder.build(reservations, [ ], 0)
|> IO.inspect
2 Likes

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
4 Likes

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.

4 Likes

Thanks for sharing this solution.

1 Like

I don’t quite understand how you mean starts again at 1. Documentation for :erlang.unique_interger, which I assume System.unique_integer calls, says:

“Generates and returns an integer unique on current runtime system instance. The integer is unique in the sense that this BIF, using the same set of modifiers, will not return the same integer more than once on the current runtime system instance.”

A simple test shows getting a different integer for each call.

2 Likes

Thanks for dropping in :slight_smile:

You are right about System.unique_integer, It gives a stream of increasing numbers. However, I want my ids to reset for every function call. So, something like below:

reservations = [%{rooms: 1},
                %{rooms: 4}]
assert make_reservation(rooms) == [%{rooms: 1, ids: [1]},
                %{rooms: 4, ids: [2, 3, 4, 5]}]
# and a subsequent call
reservations = [%{rooms: 2},
                %{rooms: 3}]
assert make_reservation(rooms) == [%{rooms: 1, ids: [1, 2]},
                %{rooms: 4, ids: [3, 4, 5]}]

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)
2 Likes

Sorry, I had misunderstood what you were after.

2 Likes

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)
4 Likes

:+1: for Stream.split/2.

3 Likes

Scanning your post I thought this was already in Elixir :slight_smile:

1 Like

I tried implementing this, but I’m not sure exactly how that would work. The most natural thing would appear to use the :suspend feature of the Enumerable protocol, but that does not work correctly (at least I wasn’t able to achieve it). Do you have any suggestions on how this could be implemented?

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.

4 Likes