Skip to content

Commit 5aaabff

Browse files
committed
partition_by many keys && fix edges reflection vertex order
1 parent ab4305e commit 5aaabff

3 files changed

Lines changed: 104 additions & 77 deletions

File tree

lib/graph.ex

Lines changed: 78 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ defmodule Graph do
3333
vertices: %{},
3434
type: :directed,
3535
vertex_identifier: &Graph.Utils.vertex_id/1,
36-
partition_by: &Graph.Utils.edge_label/1,
36+
partition_by: &Graph.Utils.by_edge_label/1,
3737
multigraph: false
3838

3939
alias Graph.{Edge, EdgeSpecificationError}
@@ -65,7 +65,7 @@ defmodule Graph do
6565
vertices: %{vertex_id => vertex},
6666
type: graph_type,
6767
vertex_identifier: (vertex() -> term()),
68-
partition_by: (Edge.t() -> edge_index_key),
68+
partition_by: (Edge.t() -> list(edge_index_key)),
6969
multigraph: boolean()
7070
}
7171
@type graph_info :: %{
@@ -86,8 +86,8 @@ defmodule Graph do
8686
- `multigraph: true | false | fn edge -> key end`, enables edge indexing by a key.
8787
- When `true`, the key is the edge label itself.
8888
- When `false` no additional memory is used for sets of .
89-
- `partition_by`: a function which accepts an `%Edge{}` and returns a unique identifier of said edge.
90-
Defaults to `Graph.Utils.edge_label/1`, the edge label itself when multigraphs are enabled.
89+
- `partition_by`: a function which accepts an `%Edge{}` and returns a list of unique identifiers used as the partition keys.
90+
Defaults to `Graph.Utils.by_edge_label/1`, which partitions edges by the label when multigraphs are enabled.
9191
9292
### Multigraph Edge Indexing
9393
@@ -113,11 +113,15 @@ defmodule Graph do
113113
iex> g = Graph.new(vertex_identifier: fn v -> :erlang.phash2(v) end) |> Graph.add_edges([{:a, :b}, {:b, :a}])
114114
...> Graph.edges(g)
115115
[%Graph.Edge{v1: :a, v2: :b}, %Graph.Edge{v1: :b, v2: :a}]
116+
117+
iex> g = Graph.new(multigraph: true, partition_by: fn edge -> [edge.weight] end) |> Graph.add_edges([{:a, :b, weight: 1}, {:b, :a, weight: 2}])
118+
...> Graph.edges(g, by: 1)
119+
[%Graph.Edge{v1: :a, v2: :b, weight: 1}]
116120
"""
117121
def new(opts \\ []) do
118122
type = Keyword.get(opts, :type) || :directed
119123
vertex_identifier = Keyword.get(opts, :vertex_identifier) || (&Graph.Utils.vertex_id/1)
120-
partition_by = Keyword.get(opts, :partition_by) || (&Graph.Utils.edge_label/1)
124+
partition_by = Keyword.get(opts, :partition_by) || (&Graph.Utils.by_edge_label/1)
121125
multigraph = Keyword.get(opts, :multigraph, false)
122126

123127
%__MODULE__{
@@ -555,7 +559,7 @@ defmodule Graph do
555559
v2 = Map.get(vs, v2_id)
556560

557561
for {label, meta_value} <- edge_meta do
558-
Edge.new(v2, v,
562+
Edge.new(v, v2,
559563
label: label,
560564
weight: meta_value.weight,
561565
properties: meta_value.properties
@@ -658,9 +662,9 @@ defmodule Graph do
658662
properties: edge_meta.properties
659663
)
660664

661-
edge_partition = g.partition_by.(edge)
665+
edge_partitions = g.partition_by.(edge)
662666

663-
if include_edge_for_filtered_partitions?(edge, edge_partition, partitions, where_fun) do
667+
if include_edge_for_filtered_partitions?(edge, edge_partitions, partitions, where_fun) do
664668
[edge | acc]
665669
else
666670
acc
@@ -711,9 +715,9 @@ defmodule Graph do
711715
properties: edge_meta.properties
712716
)
713717

714-
edge_partition = g.partition_by.(edge)
718+
edge_partitions = g.partition_by.(edge)
715719

716-
if include_edge_for_filtered_partitions?(edge, edge_partition, partitions, where_fun) do
720+
if include_edge_for_filtered_partitions?(edge, edge_partitions, partitions, where_fun) do
717721
[edge | acc]
718722
else
719723
acc
@@ -1216,34 +1220,36 @@ defmodule Graph do
12161220
end
12171221

12181222
defp index_multigraph_edge(
1219-
%__MODULE__{multigraph: true, edge_index: edge_index} = g,
1223+
%__MODULE__{multigraph: true} = graph,
12201224
{v1_id, v2_id},
12211225
%Edge{} = edge
12221226
) do
1223-
partition = g.partition_by.(edge)
1227+
partitions = graph.partition_by.(edge)
12241228

1225-
edge_partition = Map.get(edge_index, partition, %{})
1229+
Enum.reduce(partitions, graph, fn partition, g ->
1230+
edge_partition = Map.get(g.edge_index, partition, %{})
12261231

1227-
v1_set = Map.get(edge_partition, v1_id, MapSet.new())
1228-
v2_set = Map.get(edge_partition, v2_id, MapSet.new())
1232+
v1_set = Map.get(edge_partition, v1_id, MapSet.new())
1233+
v2_set = Map.get(edge_partition, v2_id, MapSet.new())
12291234

1230-
new_edge_partition =
1231-
edge_partition
1232-
|> Map.put(
1233-
v1_id,
1234-
MapSet.put(v1_set, {v1_id, v2_id})
1235-
)
1236-
|> Map.put(
1237-
v2_id,
1238-
MapSet.put(v2_set, {v1_id, v2_id})
1239-
)
1235+
new_edge_partition =
1236+
edge_partition
1237+
|> Map.put(
1238+
v1_id,
1239+
MapSet.put(v1_set, {v1_id, v2_id})
1240+
)
1241+
|> Map.put(
1242+
v2_id,
1243+
MapSet.put(v2_set, {v1_id, v2_id})
1244+
)
12401245

1241-
%__MODULE__{
1242-
g
1243-
| edge_index:
1244-
edge_index
1245-
|> Map.put(partition, new_edge_partition)
1246-
}
1246+
%__MODULE__{
1247+
g
1248+
| edge_index:
1249+
g.edge_index
1250+
|> Map.put(partition, new_edge_partition)
1251+
}
1252+
end)
12471253
end
12481254

12491255
@doc """
@@ -1530,20 +1536,22 @@ defmodule Graph do
15301536
edge =
15311537
Edge.new(v1, v2, label: label, weight: edge_meta.weight, properties: edge_meta.properties)
15321538

1533-
edge_p = partition_by.(edge)
1539+
edge_partitions = partition_by.(edge)
15341540

1535-
v1_key = {v1_id, edge_p}
1536-
v2_key = {v2_id, edge_p}
1541+
Enum.reduce(edge_partitions, acc, fn edge_p, acc ->
1542+
v1_key = {v1_id, edge_p}
1543+
v2_key = {v2_id, edge_p}
15371544

1538-
edge_index =
1539-
edge_index
1540-
|> Map.delete(v1_key)
1541-
|> Map.delete(v2_key)
1545+
edge_index =
1546+
edge_index
1547+
|> Map.delete(v1_key)
1548+
|> Map.delete(v2_key)
15421549

1543-
%__MODULE__{
1544-
acc
1545-
| edge_index: edge_index
1546-
}
1550+
%__MODULE__{
1551+
acc
1552+
| edge_index: edge_index
1553+
}
1554+
end)
15471555
end)
15481556
end
15491557

@@ -1568,27 +1576,29 @@ defmodule Graph do
15681576
edge =
15691577
Edge.new(v1, v2, label: label, weight: edge_meta.weight, properties: edge_meta.properties)
15701578

1571-
edge_p = partition_by.(edge)
1579+
edge_partitions = partition_by.(edge)
15721580

1573-
partition =
1574-
edge_index
1575-
|> Map.get(edge_p, %{})
1576-
|> Map.reject(fn {k, v} ->
1577-
(k == v1_id and MapSet.member?(v, {v1_id, v2_id})) or
1578-
(k == v2_id and MapSet.member?(v, {v1_id, v2_id}))
1579-
end)
1581+
Enum.reduce(edge_partitions, g, fn edge_p, acc ->
1582+
partition =
1583+
edge_index
1584+
|> Map.get(edge_p, %{})
1585+
|> Map.reject(fn {k, v} ->
1586+
(k == v1_id and MapSet.member?(v, {v1_id, v2_id})) or
1587+
(k == v2_id and MapSet.member?(v, {v1_id, v2_id}))
1588+
end)
15801589

1581-
edge_index =
1582-
if not Enum.empty?(partition) do
1583-
Map.put(edge_index, edge_p, partition)
1584-
else
1585-
Map.delete(edge_index, edge_p)
1586-
end
1590+
edge_index =
1591+
if not Enum.empty?(partition) do
1592+
Map.put(edge_index, edge_p, partition)
1593+
else
1594+
Map.delete(edge_index, edge_p)
1595+
end
15871596

1588-
%__MODULE__{
1589-
g
1590-
| edge_index: edge_index
1591-
}
1597+
%__MODULE__{
1598+
acc
1599+
| edge_index: edge_index
1600+
}
1601+
end)
15921602
end
15931603

15941604
defp prune_edge_index(%__MODULE__{multigraph: false} = g, _v1, _v2, _label) do
@@ -2527,9 +2537,9 @@ defmodule Graph do
25272537
properties: edge_meta.properties
25282538
)
25292539

2530-
edge_partition = partition_by.(edge)
2540+
edge_partitions = partition_by.(edge)
25312541

2532-
if edge_partition == partition do
2542+
if Enum.any?(edge_partitions, fn edge_partition -> edge_partition == partition end) do
25332543
edge
25342544
else
25352545
nil
@@ -2681,9 +2691,9 @@ defmodule Graph do
26812691
properties: edge_meta.properties
26822692
)
26832693

2684-
edge_partition = partition_by.(edge)
2694+
edges_in_partitions = partition_by.(edge)
26852695

2686-
if include_edge_for_filtered_partitions?(edge, edge_partition, partitions, where_fun) do
2696+
if include_edge_for_filtered_partitions?(edge, edges_in_partitions, partitions, where_fun) do
26872697
[edge | acc]
26882698
else
26892699
acc
@@ -2692,13 +2702,13 @@ defmodule Graph do
26922702
end)
26932703
end
26942704

2695-
defp include_edge_for_filtered_partitions?(_edge, edge_partition, partitions, nil = _where_fun) do
2696-
edge_partition in partitions
2705+
defp include_edge_for_filtered_partitions?(_edge, edge_partitions, partitions, nil = _where_fun) do
2706+
Enum.any?(edge_partitions, fn ep -> ep in partitions end)
26972707
end
26982708

2699-
defp include_edge_for_filtered_partitions?(edge, edge_partition, partitions, where_fun)
2709+
defp include_edge_for_filtered_partitions?(edge, edge_partitions, partitions, where_fun)
27002710
when is_function(where_fun) do
2701-
edge_partition in partitions and where_fun.(edge)
2711+
Enum.any?(edge_partitions, fn ep -> ep in partitions and where_fun.(edge) end)
27022712
end
27032713

27042714
defp include_edge_for_filtered_partitions?(edge, _edge_partition, _partitions, where_fun)

lib/graph/utils.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,5 @@ defmodule Graph.Utils do
111111
@max_phash 4_294_967_296
112112
def vertex_id(v), do: :erlang.phash2(v, @max_phash)
113113

114-
def edge_label(%{label: label}), do: label
114+
def by_edge_label(%{label: label}), do: [label]
115115
end

test/graph_test.exs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ defmodule GraphTest do
6969

7070
test "custom edge partition_by function" do
7171
graph =
72-
Graph.new(multigraph: true, partition_by: fn edge -> edge.weight end)
72+
Graph.new(multigraph: true, partition_by: fn edge -> [edge.weight] end)
7373
|> Graph.add_edges([
7474
{:a, :b},
7575
{:a, :b, label: :foo},
@@ -87,6 +87,25 @@ defmodule GraphTest do
8787
Graph.out_edges(graph, :b, where: fn edge -> edge.weight == 3 end)
8888
end
8989

90+
test "custom partition_by supports indexing to more than one partition" do
91+
graph =
92+
Graph.new(multigraph: true, partition_by: fn edge -> [edge.weight, edge.label] end)
93+
|> Graph.add_edges([
94+
{:a, :b},
95+
{:a, :d, label: :foo},
96+
{:a, :b, label: :bar},
97+
{:b, :c, weight: 3},
98+
{:b, :a, weight: 6, label: :foo}
99+
])
100+
101+
assert Enum.count(Graph.out_edges(graph, :b)) == 2
102+
103+
assert [%Edge{weight: 6, label: :foo}] =
104+
Graph.out_edges(graph, :b, by: 6)
105+
106+
assert Enum.count(Graph.edges(graph, by: [:foo])) == 2
107+
end
108+
90109
test "removing edges prunes index" do
91110
g =
92111
Graph.new(multigraph: true)
@@ -300,13 +319,11 @@ defmodule GraphTest do
300319
])
301320
|> Graph.edges(:a)
302321

303-
expected_result = [
304-
%Graph.Edge{label: "label3", v1: :b, v2: :a, weight: 1},
305-
%Graph.Edge{label: "label1", v1: :a, v2: :b, weight: 1},
306-
%Graph.Edge{label: "label2", v1: :a, v2: :b, weight: 1}
307-
]
308-
309-
assert generated_result == expected_result
322+
for edge <- generated_result do
323+
assert edge.label in ["label1", "label2", "label3"] and
324+
((edge.v1 == :a and edge.v2 == :b) or
325+
(edge.v1 == :b and edge.v2 == :a))
326+
end
310327
end
311328

312329
test "is_subgraph?" do

0 commit comments

Comments
 (0)