2
\$\begingroup\$

I'm attempting to create a Module, I've called it Timeline, that resembles a drag n' drop list. Given a Timeline, someone could continually add items (I'm not including removing or updating Items here), and at some time decide to move one list item before another. (For the sake of brevity, I'm not concerned with moving an Item after another Item at this time).

I'd like to know if the way I've expressed my code is easy to reason about and if there are other algorithms I can draw inspiration from.

You can copy both modules here into IEX:

defmodule Timeline.Item do
  defstruct time: nil,
            title: nil,
            notes: nil,
            tags: []
end

defmodule Timeline do
  alias Timeline.Item

  defstruct entries: %{}, auto_id: 0

  def add(%Timeline{entries: entries} = timeline, %Item{} = item) do
    auto_id = timeline.auto_id + 1

    %Timeline{
      entries: Map.put_new(entries, auto_id, item),
      auto_id: auto_id
    }
  end

  def reorder(
        %Timeline{auto_id: auto_id} = timeline,
        from,
        to
      ) do
    from_item = Map.get(timeline.entries, from)
    difference = to - from

    case difference do
      1 ->
        timeline

      _ ->
        # filter everyone before TO to and subtract 1 from their id,
        # except if they're the item intending to move
        map2 =
          Enum.filter(timeline.entries, fn {k, _v} -> k < to && k != from end)
          |> Enum.map(fn {k, v} ->
            case k do
              k when k == 0 -> {k, v}
              k when k == 1 -> {k, v}
              k when k > 1 -> {k - 1, v}
            end
          end)
          |> Enum.reduce(%{}, fn {k, v}, acc ->
            Map.put(acc, k, v)
          end)

        map1 = Map.put(timeline.entries, to - 1, from_item)
        new_entries = Map.merge(map1, map2)

        %Timeline{
          entries: new_entries,
          auto_id: auto_id
        }
    end
  end

end

Create a Timeline and add Items:

timeline = %Timeline{} |> Timeline.add(%Timeline.Item{title: "1"})
timeline = timeline |> Timeline.add(%Timeline.Item{title: "2"})
timeline = timeline |> Timeline.add(%Timeline.Item{title: "3"})
timeline = timeline |> Timeline.add(%Timeline.Item{title: "4"})
timeline = timeline |> Timeline.add(%Timeline.Item{title: "5"})

Reorder the Items by placing Item 2 before Item 5.

timeline = timeline |> Timeline.reorder(2, 5)

The result is:

%Timeline{
  auto_id: 5,
  entries: %{
    1 => %Timeline.Item{notes: nil, tags: [], time: nil, title: "1"},
    2 => %Timeline.Item{notes: nil, tags: [], time: nil, title: "3"},
    3 => %Timeline.Item{notes: nil, tags: [], time: nil, title: "4"},
    4 => %Timeline.Item{notes: nil, tags: [], time: nil, title: "2"},
    5 => %Timeline.Item{notes: nil, tags: [], time: nil, title: "5"}
  }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.