Current section

Files

Jump to
livebook lib livebook utils graph.ex
Raw

lib/livebook/utils/graph.ex

defmodule Livebook.Utils.Graph do
@moduledoc false
@typedoc """
A bottom-up graph representation encoded as a map
of child-to-parent entries.
"""
@type t() :: %{node_id => node_id | nil}
@type t(node_id) :: %{node_id => node_id | nil}
@type node_id :: term()
@doc """
Finds a path between nodes `from_id` and `to_id`.
If the path exists, a top-down list of nodes is
returned including the extreme nodes. Otherwise,
an empty list is returned.
"""
@spec find_path(t(), node_id(), node_id()) :: list(node_id())
def find_path(graph, from_id, to_id) do
find_path(graph, from_id, to_id, [])
end
defp find_path(_graph, to_id, to_id, path), do: [to_id | path]
defp find_path(_graph, nil, _to_id, _path), do: []
defp find_path(graph, from_id, to_id, path),
do: find_path(graph, graph[from_id], to_id, [from_id | path])
@doc """
Finds graph leave nodes, that is, nodes with
no children.
"""
@spec leaves(t()) :: list(node_id())
def leaves(graph) do
children = MapSet.new(graph, fn {key, _} -> key end)
parents = MapSet.new(graph, fn {_, value} -> value end)
MapSet.difference(children, parents) |> MapSet.to_list()
end
@doc """
Reduces each top-down path in the graph.
Returns a list of accumulators, one for each leaf in the graph,
in no specific order.
"""
@spec reduce_paths(t(), acc, (node_id(), acc -> acc)) :: acc when acc: term()
def reduce_paths(graph, acc, fun) do
leaves = Livebook.Utils.Graph.leaves(graph)
cache = do_reduce(graph, leaves, acc, fun, %{})
Enum.map(leaves, &cache[&1])
end
defp do_reduce(_graph, [], _initial_acc, _fun, cache), do: cache
defp do_reduce(graph, [cell_id | cell_ids], initial_acc, fun, cache) do
if parent_id = graph[cell_id] do
case cache do
%{^parent_id => acc} ->
acc = fun.(cell_id, acc)
cache = put_in(cache[cell_id], acc)
do_reduce(graph, cell_ids, initial_acc, fun, cache)
_ ->
do_reduce(graph, [parent_id, cell_id | cell_ids], initial_acc, fun, cache)
end
else
acc = fun.(cell_id, initial_acc)
cache = put_in(cache[cell_id], acc)
do_reduce(graph, cell_ids, initial_acc, fun, cache)
end
end
end