Current section
Files
Jump to
Current section
Files
lib/inductive_graph.ex
defmodule InductiveGraph do
@moduledoc """
Functions that work on graphs as an inductive data structure.
"""
alias InductiveGraph, as: Graph
alias InductiveGraph.Internal
alias InductiveGraph.Utilities
@opaque t :: %__MODULE__{}
@type value :: term
@type edge_value :: value
@type vertex_value :: value
@type vertex :: integer
@type neighbor :: vertex
@type edge :: {from_vertex :: vertex, to_vertex :: vertex}
@type tagged_vertex :: {vertex, vertex_value}
@type tagged_edge :: {from_vertex :: vertex, to_vertex :: vertex, edge_value}
@type adjacents :: [{edge_value, neighbor}]
@type predecessors :: adjacents
@type successors :: adjacents
@type context :: {predecessors, vertex, vertex_value, successors}
defstruct [
internal: Internal.empty_graph(),
]
defimpl Inspect do
def inspect(%Graph{}, _opts) do
"#InductiveGraph<>"
end
end
# Wraps `graph` to `%InductiveGraph{}`.
@spec wrap(Internal.t) :: t
defp wrap(graph), do: %Graph{internal: graph}
# Wraps `graph` in `position` of `fallible` to `%InductiveGraph{}`.
#
# Fallible is either `:error` or an `tuple` with first element `:ok`.
@spec wrap_fallible(tuple | :error, non_neg_integer) :: tuple | :error
defp wrap_fallible(fallible, position) do
with true <- is_tuple(fallible),
true <- position > 0,
:ok <- elem(fallible, 0) do
Utilities.tuple_update_position!(fallible, position, &wrap/1)
else
_error -> :error
end
end
@doc ~S"""
Pretty prints inductive representation of `graph`.
If `count` is provided, then up to `count` number of contexts will be shown.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> InductiveGraph.pretty_print(graph) <> "\n"
~s'''
| {[{"down", 2}], 3, "c", [{"up", 1}]}
& {[{"right", 1}], 2, "b", [{"left", 1}]}
& {[], 1, "a", []}
& Empty
'''
"""
@doc inspection: true
@spec pretty_print(t, integer) :: String.t
def pretty_print(graph, count \\ -1)
def pretty_print(%Graph{internal: graph}, count) do
Internal.pretty_print(graph, count)
end
@doc """
Creates an empty graph.
## Examples
iex> graph = InductiveGraph.empty_graph()
#InductiveGraph<>
iex> InductiveGraph.pretty_print(graph)
"| Empty"
"""
@doc construction: true
@spec empty_graph() :: t
def empty_graph(), do: %Graph{}
@doc """
Determines if `graph` is empty.
## Examples
iex> graph = InductiveGraph.empty_graph()
iex> InductiveGraph.empty?(graph)
true
iex> {:ok, graph} = InductiveGraph.make_graph([{1, 1}], [])
iex> InductiveGraph.empty?(graph)
false
"""
@doc inspection: true
@spec empty?(t) :: boolean
def empty?(graph)
def empty?(%Graph{internal: graph}), do: Internal.empty?(graph)
@doc """
Creates a graph from lists `tagged_vertices` and `tagged_edges`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> graph |> InductiveGraph.list_tagged_vertices() |> Enum.sort()
[{1, "a"}, {2, "b"}, {3, "c"}]
iex> graph |> InductiveGraph.list_tagged_edges() |> Enum.sort()
[{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
"""
@doc construction: true
@spec make_graph([tagged_vertex], [tagged_edge]) :: {:ok, t} | :error
def make_graph(tagged_vertices, tagged_edges) do
Internal.make_graph(tagged_vertices, tagged_edges)
|> wrap_fallible(1)
end
@doc """
Builds a graph from `contexts`.
## Examples
iex> contexts = [{[{"down", 2}], 3, "c", [{"up", 1}]}, {[{"right", 1}], 2, "b", [{"left", 1}]}, {[], 1, "a", []}]
iex> {:ok, graph1} = InductiveGraph.build_graph(contexts)
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> {:ok, graph2} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> InductiveGraph.equal?(graph1, graph2)
true
"""
@doc construction: true
@spec build_graph([context]) :: {:ok, t} | :error
def build_graph(contexts), do: insert_contexts(empty_graph(), contexts)
@doc """
Inserts `contexts` into `graph`.
## Examples
iex> graph1 = InductiveGraph.empty_graph()
iex> contexts = [{[{"down", 2}], 3, "c", [{"up", 1}]}, {[{"right", 1}], 2, "b", [{"left", 1}]}, {[], 1, "a", []}]
iex> {:ok, graph2} = InductiveGraph.insert_contexts(graph1, contexts)
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> {:ok, graph3} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> InductiveGraph.equal?(graph2, graph3)
true
"""
@doc construction: true
@spec insert_contexts(t, [context]) :: {:ok, t} | :error
def insert_contexts(graph, contexts)
def insert_contexts(%Graph{internal: graph}, contexts) do
merge =
fn
context, {:ok, graph} -> Internal.merge(graph, context)
_context, :error -> :error
end
contexts
|> List.foldr({:ok, graph}, merge)
|> wrap_fallible(1)
end
@doc ~S"""
Inserts `tagged_edge` into `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}]
iex> new_tagged_edge = {3, 1, "up"}
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> {:ok, graph} = InductiveGraph.insert_tagged_edge(graph, new_tagged_edge)
iex> InductiveGraph.pretty_print(graph) <> "\n"
~s'''
| {[{"down", 2}], 3, "c", [{"up", 1}]}
& {[{"right", 1}], 2, "b", [{"left", 1}]}
& {[], 1, "a", []}
& Empty
'''
"""
@doc construction: true
@spec insert_tagged_edge(t, tagged_edge) :: {:ok, t} | :error
def insert_tagged_edge(graph, tagged_edge)
def insert_tagged_edge(%Graph{internal: graph}, tagged_edge) do
Internal.insert_tagged_edge(graph, tagged_edge)
|> wrap_fallible(1)
end
@doc ~S"""
Inserts `tagged_edges` into `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = []
iex> new_tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> {:ok, graph} = InductiveGraph.insert_tagged_edges(graph, new_tagged_edges)
iex> InductiveGraph.pretty_print(graph) <> "\n"
~s'''
| {[{"down", 2}], 3, "c", [{"up", 1}]}
& {[{"right", 1}], 2, "b", [{"left", 1}]}
& {[], 1, "a", []}
& Empty
'''
"""
@doc construction: true
@spec insert_tagged_edges(t, [tagged_edge]) :: {:ok, t} | :error
def insert_tagged_edges(graph, tagged_edges)
def insert_tagged_edges(%Graph{internal: graph}, tagged_edges) do
Internal.insert_tagged_edges(graph, tagged_edges)
|> wrap_fallible(1)
end
@doc """
Inserts `tagged_vertex` into `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> new_tagged_vertex = {4, "d"}
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> {:ok, graph} = InductiveGraph.insert_tagged_vertex(graph, new_tagged_vertex)
iex> {:ok, context, _graph} = InductiveGraph.decompose(graph, 4)
iex> context
{[], 4, "d", []}
"""
@doc construction: true
@spec insert_tagged_vertex(t, tagged_vertex) :: {:ok, t} | :error
def insert_tagged_vertex(graph, tagged_vertex)
def insert_tagged_vertex(%Graph{internal: graph}, tagged_vertex) do
Internal.insert_tagged_vertex(graph, tagged_vertex)
|> wrap_fallible(1)
end
@doc ~S"""
Inserts `tagged_vertices` into `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> new_tagged_vertices = [{4, "d"}, {5, "e"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> {:ok, graph} = InductiveGraph.insert_tagged_vertices(graph, new_tagged_vertices)
iex> InductiveGraph.pretty_print(graph, 2) <> "\n"
~s'''
| {[], 5, "e", []}
& {[], 4, "d", []}
& InductiveGraph
'''
"""
@doc construction: true
@spec insert_tagged_vertices(t, [tagged_vertex]) :: {:ok, t} | :error
def insert_tagged_vertices(graph, tagged_vertices)
def insert_tagged_vertices(%Graph{internal: graph}, tagged_vertices) do
Internal.insert_tagged_vertices(graph, tagged_vertices)
|> wrap_fallible(1)
end
@doc """
Decomposes `graph` into the context containing `vertex` and the remaining
graph.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> {:ok, context1, graph} = InductiveGraph.decompose(graph, 3)
iex> context1
{[{"down", 2}], 3, "c", [{"up", 1}]}
iex> {:ok, context2, graph} = InductiveGraph.decompose(graph, 2)
iex> context2
{[{"right", 1}], 2, "b", [{"left", 1}]}
iex> {:ok, context3, graph} = InductiveGraph.decompose(graph, 1)
iex> context3
{[], 1, "a", []}
iex> InductiveGraph.empty?(graph)
true
"""
@doc destruction: true
@spec decompose(t, vertex) :: {:ok, context, t} | :error
def decompose(graph, vertex)
def decompose(%Graph{internal: graph}, vertex) do
Internal.decompose(graph, vertex)
|> wrap_fallible(2)
end
@doc """
Decomposes `graph` into an arbitrary context and the remaining graph.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> InductiveGraph.count_vertices(graph)
3
iex> {:ok, _context, graph} = InductiveGraph.decompose(graph)
iex> InductiveGraph.count_vertices(graph)
2
"""
@doc destruction: true
@spec decompose(t) :: {:ok, context, t} | :error
def decompose(graph)
def decompose(graph = %Graph{internal: internal}) do
[{vertex, _vertex_value} | _tagged_vertices] = Internal.list_tagged_vertices(internal) |> Enum.sort() |> Enum.reverse()
decompose(graph, vertex)
end
@doc """
Merges `context` into `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> {:ok, graph1} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> {:ok, context, graph2} = InductiveGraph.decompose(graph1, 3)
iex> {:ok, graph3} = InductiveGraph.merge(graph2, context)
iex> InductiveGraph.equal?(graph1, graph3)
true
"""
@doc construction: true
@spec merge(t, context) :: {:ok, t} | :error
def merge(graph, context)
def merge(%Graph{internal: graph}, context) do
Internal.merge(graph, context)
|> wrap_fallible(1)
end
@doc """
Lists all tagged vertices in `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, [])
iex> graph |> InductiveGraph.list_tagged_vertices() |> Enum.sort()
[{1, "a"}, {2, "b"}, {3, "c"}]
"""
@doc inspection: true
@spec list_tagged_vertices(t) :: [tagged_vertex]
def list_tagged_vertices(graph)
def list_tagged_vertices(%Graph{internal: graph}) do
Internal.list_tagged_vertices(graph)
end
@doc """
Counts number of vertices in `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, [])
iex> InductiveGraph.count_vertices(graph)
3
"""
@doc inspection: true
@spec count_vertices(t) :: non_neg_integer
def count_vertices(graph)
def count_vertices(%Graph{internal: graph}) do
Internal.count_vertices(graph)
end
@doc """
Lists all tagged edges in `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> graph |> InductiveGraph.list_tagged_edges() |> Enum.sort()
[{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
"""
@doc inspection: true
@spec list_tagged_edges(t) :: [tagged_edge]
def list_tagged_edges(graph)
def list_tagged_edges(%Graph{internal: graph}) do
Internal.list_tagged_edges(graph)
end
@doc """
Counts number of edges in `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> graph |> InductiveGraph.count_edges()
4
"""
@doc inspection: true
@spec count_edges(t) :: non_neg_integer
def count_edges(graph)
def count_edges(%Graph{internal: graph}) do
graph |> Internal.list_tagged_edges() |> length()
end
@doc """
Gets range of vertex values in `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, [])
iex> InductiveGraph.vertex_range(graph)
{:ok, 1, 3}
"""
@doc inspection: true
@spec vertex_range(t) :: {:ok, mininum :: integer, maximum :: integer} | :error
def vertex_range(graph)
def vertex_range(%Graph{internal: graph}) do
Internal.vertex_range(graph)
end
@doc """
Determines if `vertex` is in `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> edges = []
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, edges)
iex> InductiveGraph.has_vertex?(graph, 3)
true
iex> InductiveGraph.has_vertex?(graph, 4)
false
"""
@doc inspection: true
@spec has_vertex?(t, vertex) :: boolean
def has_vertex?(graph, vertex)
def has_vertex?(%Graph{internal: graph}, vertex), do: Internal.has_vertex?(graph, vertex)
@doc """
Determines if `edge` is in `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> InductiveGraph.has_edge?(graph, {1, 2})
true
iex> InductiveGraph.has_edge?(graph, {1, 3})
false
"""
@doc inspection: true
@spec has_edge?(t, edge) :: boolean
def has_edge?(graph, edge)
def has_edge?(%Graph{internal: graph}, edge), do: Internal.has_edge?(graph, edge)
@doc """
Checks if two graphs are equal.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> {:ok, graph1} = InductiveGraph.make_graph(tagged_vertices, [])
iex> {:ok, graph2} = InductiveGraph.insert_tagged_edges(graph1, tagged_edges)
iex> {:ok, graph3} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> InductiveGraph.equal?(graph1, graph2)
false
iex> InductiveGraph.equal?(graph2, graph3)
true
"""
@doc inspection: true
@spec equal?(t, t) :: boolean
def equal?(graph1, graph2) do
vertices1 = graph1 |> list_tagged_vertices() |> Enum.sort()
edges1 = graph1 |> list_tagged_edges() |> Enum.sort()
vertices2 = graph2 |> list_tagged_vertices() |> Enum.sort()
edges2 = graph2 |> list_tagged_edges() |> Enum.sort()
(vertices1 == vertices2) and (edges1 == edges2)
end
@doc """
Applies `function` to every context in `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> function = fn context = {_p, _v, value, _s} -> put_elem(context, 2, String.duplicate(value, 5)) end
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> graph = InductiveGraph.map_graph(graph, function)
iex> {:ok, context, _graph} = InductiveGraph.decompose(graph, 3)
iex> context
{[{"down", 2}], 3, "ccccc", [{"up", 1}]}
"""
@doc update: true
@spec map_graph(t, (context -> context)) :: t
def map_graph(graph, function)
def map_graph(%Graph{internal: graph}, function) do
Internal.map_graph(graph, function)
|> wrap()
end
@doc """
Applies `function` to every vertex value in `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> function = &String.duplicate(&1, 5)
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> graph = InductiveGraph.map_vertices(graph, function)
iex> {:ok, context, _graph} = InductiveGraph.decompose(graph, 3)
iex> context
{[{"down", 2}], 3, "ccccc", [{"up", 1}]}
"""
@doc update: true
@spec map_vertices(t, (vertex_value -> vertex_value)) :: t
def map_vertices(graph, function)
def map_vertices(%Graph{internal: graph}, function) do
Internal.map_vertices(graph, function)
|> wrap()
end
@doc """
Applies `function` to every edge value in `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> function = &String.reverse(&1)
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> graph = InductiveGraph.map_edges(graph, function)
iex> {:ok, context, _graph} = InductiveGraph.decompose(graph, 3)
iex> context
{[{"nwod", 2}], 3, "c", [{"pu", 1}]}
"""
@doc update: true
@spec map_edges(t, (edge_value -> edge_value)) :: t
def map_edges(graph, function)
def map_edges(%Graph{internal: graph}, function) do
Internal.map_edges(graph, function)
|> wrap()
end
@doc """
Applies `vertex_function` to every vertex value and `edge_function` to every
edge value in `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> vertex_function = &String.duplicate(&1, 5)
iex> edge_function = &String.reverse(&1)
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> graph = InductiveGraph.map_vertices_and_edges(graph, vertex_function, edge_function)
iex> {:ok, context, _graph} = InductiveGraph.decompose(graph, 3)
iex> context
{[{"nwod", 2}], 3, "ccccc", [{"pu", 1}]}
"""
@doc update: true
@spec map_vertices_and_edges(t, (vertex_value -> vertex_value), (edge_value -> edge_value)) :: t
def map_vertices_and_edges(graph, vertex_function, edge_function)
def map_vertices_and_edges(%Graph{internal: graph}, vertex_function, edge_function) do
Internal.map_vertices_and_edges(graph, vertex_function, edge_function)
|> wrap()
end
@doc """
Folds `function` over `graph` with starting value `accumulator`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> function = fn {_p, vertex, _v, _s}, result -> vertex + result end
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> InductiveGraph.unordered_fold(graph, 0, function)
6
"""
@doc update: true
@spec unordered_fold(t, term, (context, term -> term)) :: term
def unordered_fold(graph, accumulator, function) do
get_vertex = fn {vertex, _vertex_value} -> vertex end
vertices = graph |> list_tagged_vertices() |> Enum.map(get_vertex)
unordered_fold(graph, accumulator, function, vertices)
end
# Folds `function` over `graph` with starting value `accumulator`.
@spec unordered_fold(t, term, (context, term -> term), [vertex]) :: term
defp unordered_fold(_graph, accumulator, _function, []), do: accumulator
defp unordered_fold(graph, accumulator, function, [vertex | vertices]) do
{:ok, context, graph} = decompose(graph, vertex)
accumulator = unordered_fold(graph, accumulator, function, vertices)
function.(context, accumulator)
end
@doc """
Deletes `vertex` from `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> {:ok, graph} = InductiveGraph.delete_vertex(graph, 3)
iex> InductiveGraph.has_vertex?(graph, 3)
false
"""
@doc destruction: true
@spec delete_vertex(t, vertex) :: {:ok, t} | :error
def delete_vertex(graph, vertex) do
with {:ok, _context, graph} <- decompose(graph, vertex) do
{:ok, graph}
else
_error -> :error
end
end
@doc """
Deletes `vertices` from `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> {:ok, graph} = InductiveGraph.delete_vertices(graph, [2, 3])
iex> InductiveGraph.has_vertex?(graph, 3)
false
"""
@doc destruction: true
@spec delete_vertices(t, [vertex]) :: {:ok, t} | :error
def delete_vertices(graph, vertices) do
delete =
fn
vertex, {:ok, graph} -> delete_vertex(graph, vertex)
_vertex, :error -> :error
end
List.foldl(vertices, {:ok, graph}, delete)
end
@doc ~S"""
Deletes `edge` from `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> edge = {1, 2}
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> {:ok, graph} = InductiveGraph.delete_edge(graph, edge)
iex> InductiveGraph.pretty_print(graph) <> "\n"
~s'''
| {[{"down", 2}], 3, "c", [{"up", 1}]}
& {[], 2, "b", [{"left", 1}]}
& {[], 1, "a", []}
& Empty
'''
"""
@doc destruction: true
@spec delete_edge(t, edge) :: {:ok, t} | :error
def delete_edge(graph, edge)
def delete_edge(graph, {from_vertex, to_vertex}) do
predicate = fn {_edge_value, neighbor} -> neighbor == to_vertex end
with true <- has_vertex?(graph, from_vertex),
{:ok, context, graph} <- decompose(graph, from_vertex),
{predecessors, vertex, vertex_value, successors} = context,
successors = Enum.reject(successors, predicate),
context = {predecessors, vertex, vertex_value, successors} do
merge(graph, context)
else
_error -> :error
end
end
@doc ~S"""
Deletes `edges` from `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> edges = [{1, 2}, {2, 1}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> {:ok, graph} = InductiveGraph.delete_edges(graph, edges)
iex> InductiveGraph.pretty_print(graph) <> "\n"
~s'''
| {[{"down", 2}], 3, "c", [{"up", 1}]}
& {[], 2, "b", []}
& {[], 1, "a", []}
& Empty
'''
"""
@doc destruction: true
@spec delete_edges(t, [edge]) :: {:ok, t} | :error
def delete_edges(graph, edges) do
delete =
fn
edge, {:ok, graph} -> delete_edge(graph, edge)
_vertex, :error -> :error
end
List.foldl(edges, {:ok, graph}, delete)
end
@doc ~S"""
Filters edges based on edge values that fails `predicate` in `graph`.
## Examples
iex> tagged_vertices = [{1, "a"}, {2, "b"}, {3, "c"}]
iex> tagged_edges = [{1, 2, "right"}, {2, 1, "left"}, {2, 3, "down"}, {3, 1, "up"}]
iex> {:ok, graph} = InductiveGraph.make_graph(tagged_vertices, tagged_edges)
iex> predicate = fn edge_value -> String.length(edge_value) == 4 end
iex> graph = InductiveGraph.filter_edges(graph, predicate)
iex> InductiveGraph.pretty_print(graph) <> "\n"
~s'''
| {[{"down", 2}], 3, "c", []}
& {[], 2, "b", [{"left", 1}]}
& {[], 1, "a", []}
& Empty
'''
"""
@doc update: true
@spec filter_edges(t, (edge_value -> boolean)) :: t
def filter_edges(graph, predicate) do
filter =
fn
{predecessors, vertex, vertex_value, successors}, graph ->
filter = fn {edge_value, _neighbor} -> predicate.(edge_value) end
predecessors = Enum.filter(predecessors, filter)
successors = Enum.filter(successors, filter)
{:ok, graph} = merge(graph, {predecessors, vertex, vertex_value, successors})
graph
end
unordered_fold(graph, empty_graph(), filter)
end
end