Skip to content

Commit

Permalink
prune on edge deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
zblanco committed Sep 1, 2024
1 parent 91bcbb2 commit 1cadec7
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 10 deletions.
57 changes: 51 additions & 6 deletions lib/graph.ex
Original file line number Diff line number Diff line change
Expand Up @@ -86,21 +86,19 @@ defmodule Graph do
- `multigraph: true | false | fn edge -> key end`, enables edge indexing by a key.
- When `true`, the key is the edge label itself.
- When `false` no additional memory is used for sets of .
- `edge_indexer`: a function which accepts an `%Edge{}` and returns a unique identifier of said edge.
Defaults to `Graph.Utils.edge_label/1`, the edge label itself.
- `partition_by`: a function which accepts an `%Edge{}` and returns a unique identifier of said edge.
Defaults to `Graph.Utils.edge_label/1`, the edge label itself when multigraphs are enabled.
### Multigraph Edge Indexing
Indexing edges trades space for time to access only edges of a kind.
When `multigraph: true` is enabled the `edge_indexer` of the graph is used to build a a set of edge keys (`{vertex_id, vertex_id}`) under a separate key.
When `multigraph: true` is enabled the `partition_by` of the graph is used to build a set of edge keys (`{vertex_id, vertex_id}`) under a separate key.
This can be a useful trade-off when traversing a graph where many different kinds of edges exist between the same vertices and
you want to avoid iterating over the set of all edges. I.e. a [multigraph](https://en.wikipedia.org/wiki/Multigraph).
The index provides allows map access time to to a set of edges when managing the graph.
By default edges are indexed by the label but only when multigraph is toggled true.
## Example
iex> Graph.new()
Expand Down Expand Up @@ -1308,6 +1306,8 @@ defmodule Graph do
v1,
v2
) do
g = prune_edge_index(g, v1, v2, nil)

with v1_id <- vertex_identifier.(v1),
v2_id <- vertex_identifier.(v2),
edge_key <- {v1_id, v2_id},
Expand All @@ -1328,6 +1328,50 @@ defmodule Graph do
end
end

defp prune_edge_index(
%__MODULE__{
multigraph: true,
edge_index: edge_index,
edges: meta,
partition_by: partition_by,
vertex_identifier: vertex_identifier
} = g,
v1,
v2,
label
) do
v1_id = vertex_identifier.(v1)
v2_id = vertex_identifier.(v2)

{_label, edge_meta} =
meta
|> Map.get({v1_id, v2_id})
|> Enum.filter(fn {edge_label, _} -> edge_label == label end)
|> List.first()

edge =
Edge.new(v1, v2, label: label, weight: edge_meta.weight, properties: edge_meta.properties)

edge_p = partition_by.(edge)

v1_key = {v1_id, edge_p}
v2_key = {v2_id, edge_p}

edge_index =
edge_index
|> Map.delete(v1_key)
|> Map.delete(v2_key)

%__MODULE__{
g
| edge_index: edge_index
}
end

defp prune_edge_index(%__MODULE__{multigraph: false} = g, _v1, _v2, _label) do
g
end

@doc """
Removes an edge connecting `v1` to `v2`. A label can be specified to disambiguate the
specific edge you wish to delete, if not provided, the unlabelled edge, if one exists,
Expand Down Expand Up @@ -1379,6 +1423,8 @@ defmodule Graph do
v2,
label
) do
g = prune_edge_index(g, v1, v2, label)

with v1_id <- vertex_identifier.(v1),
v2_id <- vertex_identifier.(v2),
edge_key <- {v1_id, v2_id},
Expand Down Expand Up @@ -2235,7 +2281,6 @@ defmodule Graph do

edge_index
|> Map.get(key, MapSet.new())
|> IO.inspect(label: "edge_index_keys")
|> Enum.flat_map(fn {v1_id, _v2_id} = edge_key ->
v1 = Map.get(vs, v1_id)

Expand Down
49 changes: 45 additions & 4 deletions test/graph_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ defmodule GraphTest do
{:b, :c, weight: 3},
{:b, :a, label: {:complex, :label}}
])
|> IO.inspect(structs: false)
|> IO.inspect(structs: false, label: "multigraph")

assert Enum.count(Graph.out_edges(graph, :a)) == 3
assert [%Edge{label: :foo}] = Graph.out_edges(graph, :a, :foo)
Expand All @@ -41,7 +41,7 @@ defmodule GraphTest do
assert [] == Graph.out_edges(graph, :a, :foobar)
end

test "custom edge indexing function" do
test "custom edge partition_by function" do
graph =
Graph.new(multigraph: true, partition_by: fn edge -> edge.weight end)
|> Graph.add_edges([
Expand All @@ -57,13 +57,54 @@ defmodule GraphTest do
assert [%Edge{weight: 3}] = Graph.out_edges(graph, :b, 3)
end

test "removing edges prunes index" do

Check failure on line 60 in test/graph_test.exs

View workflow job for this annotation

GitHub Actions / test (25.0, 1.13.3)

test multigraphs removing edges prunes index (GraphTest)

Check failure on line 60 in test/graph_test.exs

View workflow job for this annotation

GitHub Actions / test (25.0, 1.14.0)

test multigraphs removing edges prunes index (GraphTest)

Check failure on line 60 in test/graph_test.exs

View workflow job for this annotation

GitHub Actions / test (24.2, 1.13.3)

test multigraphs removing edges prunes index (GraphTest)

Check failure on line 60 in test/graph_test.exs

View workflow job for this annotation

GitHub Actions / test (24.2, 1.14.0)

test multigraphs removing edges prunes index (GraphTest)

Check failure on line 60 in test/graph_test.exs

View workflow job for this annotation

GitHub Actions / test (24.2, 1.12.3)

test multigraphs removing edges prunes index (GraphTest)
g =
Graph.new(multigraph: true)
|> Graph.add_edges([
{:a, :b},
{:a, :b, label: :foo},
{:a, :b, label: :bar},
{:b, :c, weight: 3},
{:b, :a, label: {:complex, :label}}
])

g = Graph.delete_edges(g, [{:b, :c}, {:b, :a}])
refute Map.has_key?(g.edge_index, {g.vertex_identifier.(:b), {:complex, :label}})
end

test "traversal using indexed keys" do
end
end

test "edge properties" do
describe "edge properties" do
test "setting edge properties" do

Check failure on line 80 in test/graph_test.exs

View workflow job for this annotation

GitHub Actions / test (25.0, 1.13.3)

test edge properties setting edge properties (GraphTest)

Check failure on line 80 in test/graph_test.exs

View workflow job for this annotation

GitHub Actions / test (24.2, 1.13.3)

test edge properties setting edge properties (GraphTest)

Check failure on line 80 in test/graph_test.exs

View workflow job for this annotation

GitHub Actions / test (24.2, 1.12.3)

test edge properties setting edge properties (GraphTest)
g =
Graph.new()
|> Graph.add_edges([
{:a, :b, properties: %{foo: :bar}},
{:a, :b, label: :foo, properties: %{bar: :foo}}
])

assert [
%Edge{v1: :a, v2: :b, properties: %{foo: :bar}},
%Edge{v1: :a, v2: :b, label: :foo, properties: %{bar: :foo}}
] = Graph.out_edges(g, :a)
end

test "removing edges" do
test "updating edge properties" do

Check failure on line 94 in test/graph_test.exs

View workflow job for this annotation

GitHub Actions / test (25.0, 1.13.3)

test edge properties updating edge properties (GraphTest)

Check failure on line 94 in test/graph_test.exs

View workflow job for this annotation

GitHub Actions / test (25.0, 1.14.0)

test edge properties updating edge properties (GraphTest)

Check failure on line 94 in test/graph_test.exs

View workflow job for this annotation

GitHub Actions / test (24.2, 1.14.0)

test edge properties updating edge properties (GraphTest)
g =
Graph.new()
|> Graph.add_edges([
{:a, :b, properties: %{foo: :bar}},
{:a, :b, label: :foo, properties: %{bar: :foo}}
])
|> Graph.update_edge(:a, :b, properties: %{ham: :potato})
|> Graph.update_labelled_edge(:a, :b, :foo, properties: %{potato: :ham})

assert [
%Edge{v1: :a, v2: :b, properties: %{ham: :potato}},
%Edge{v1: :a, v2: :b, label: :foo, properties: %{potato: :ham}}
] = Graph.out_edges(g, :a)
end
end

Expand Down

0 comments on commit 1cadec7

Please sign in to comment.