-
Notifications
You must be signed in to change notification settings - Fork 75
/
graph.ex
2213 lines (1826 loc) · 70.3 KB
/
graph.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
defmodule Graph do
@moduledoc """
This module defines a graph data structure, which supports directed and undirected graphs, in both acyclic and cyclic forms.
It also defines the API for creating, manipulating, and querying that structure.
As far as memory usage is concerned, `Graph` should be fairly compact in memory, but if you want to do a rough
comparison between the memory usage for a graph between `libgraph` and `digraph`, use `:digraph.info/1` and
`Graph.info/1` on the two graphs, and both results will contain memory usage information. Keep in mind we don't have a precise
way to measure the memory usage of a term in memory, whereas ETS is able to give a more precise answer, but we do have
a fairly good way to estimate the usage of a term, and we use that method within `libgraph`.
The Graph struct is structured like so:
- A map of vertex ids to vertices (`vertices`)
- A map of vertex ids to their out neighbors (`out_edges`),
- A map of vertex ids to their in neighbors (`in_edges`), effectively the transposition of `out_edges`
- A map of vertex ids to vertex labels (`vertex_labels`), (labels are only stored if a non-nil label was provided)
- A map of edge ids (where an edge id is simply a tuple of `{vertex_id, vertex_id}`) to a map of edge metadata (`edges`)
- Edge metadata is a map of `label => weight`, and each entry in that map represents a distinct edge. This allows
us to support multiple edges in the same direction between the same pair of vertices, but for many purposes simply
treat them as a single logical edge.
This structure is designed to be as efficient as possible once a graph is built, but it turned out that it is also
quite efficient for manipulating the graph as well. For example, splitting an edge and introducing a new vertex on that
edge can be done with very little effort. We use vertex ids everywhere because we can generate them without any lookups,
we don't incur any copies of the vertex structure, and they are very efficient as keys in a map.
"""
defstruct in_edges: %{},
out_edges: %{},
edges: %{},
vertex_labels: %{},
vertices: %{},
type: :directed,
vertex_identifier: &Graph.Utils.vertex_id/1
alias Graph.{Edge, EdgeSpecificationError}
@typedoc """
Identifier of a vertex. By default a non_neg_integer from `Graph.Utils.vertex_id/1` utilizing `:erlang.phash2`.
"""
@type vertex_id :: non_neg_integer() | term()
@type vertex :: term
@type label :: term
@type edge_weight :: integer | float
@type edge_key :: {vertex_id, vertex_id}
@type edge_value :: %{label => edge_weight}
@type graph_type :: :directed | :undirected
@type vertices :: %{vertex_id => vertex}
@type t :: %__MODULE__{
in_edges: %{vertex_id => MapSet.t()},
out_edges: %{vertex_id => MapSet.t()},
edges: %{edge_key => edge_value},
vertex_labels: %{vertex_id => term},
vertices: %{vertex_id => vertex},
type: graph_type,
vertex_identifier: (vertex() -> term())
}
@type graph_info :: %{
:num_edges => non_neg_integer(),
:num_vertices => non_neg_integer(),
:size_in_bytes => number(),
:type => :directed | :undirected
}
@doc """
Creates a new graph using the provided options.
## Options
- `type: :directed | :undirected`, specifies what type of graph this is. Defaults to a `:directed` graph.
- `vertex_identifier`: a function which accepts a vertex and returns a unique identifier of said vertex.
Defaults to `Graph.Utils.vertex_id/1`, a hash of the whole vertex utilizing `:erlang.phash2/2`.
## Example
iex> Graph.new()
#Graph<type: directed, vertices: [], edges: []>
iex> g = Graph.new(type: :undirected) |> Graph.add_edges([{:a, :b}, {:b, :a}])
...> Graph.edges(g)
[%Graph.Edge{v1: :a, v2: :b}]
iex> g = Graph.new(type: :directed) |> Graph.add_edges([{:a, :b}, {:b, :a}])
...> Graph.edges(g)
[%Graph.Edge{v1: :a, v2: :b}, %Graph.Edge{v1: :b, v2: :a}]
iex> g = Graph.new(vertex_identifier: fn v -> :erlang.phash2(v) end) |> Graph.add_edges([{:a, :b}, {:b, :a}])
...> Graph.edges(g)
[%Graph.Edge{v1: :a, v2: :b}, %Graph.Edge{v1: :b, v2: :a}]
"""
def new(opts \\ []) do
type = Keyword.get(opts, :type) || :directed
vertex_identifier = Keyword.get(opts, :vertex_identifier) || (&Graph.Utils.vertex_id/1)
%__MODULE__{type: type, vertex_identifier: vertex_identifier}
end
@doc """
Returns a map of summary information about this graph.
NOTE: The `size_in_bytes` value is an estimate, not a perfectly precise value, but
should be close enough to be useful.
## Example
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = g |> Graph.add_edges([{:a, :b}, {:b, :c}])
...> match?(%{type: :directed, num_vertices: 4, num_edges: 2}, Graph.info(g))
true
"""
@spec info(t) :: graph_info()
def info(%__MODULE__{type: type} = g) do
%{
type: type,
num_edges: num_edges(g),
num_vertices: num_vertices(g),
size_in_bytes: Graph.Utils.sizeof(g)
}
end
@doc """
Converts the given Graph to DOT format, which can then be converted to
a number of other formats via Graphviz, e.g. `dot -Tpng out.dot > out.png`.
If labels are set on a vertex, then those labels are used in the DOT output
in place of the vertex itself. If no labels were set, then the vertex is
stringified if it's a primitive type and inspected if it's not, in which
case the inspect output will be quoted and used as the vertex label in the DOT file.
Edge labels and weights will be shown as attributes on the edge definitions, otherwise
they use the same labelling scheme for the involved vertices as described above.
NOTE: Currently this function assumes graphs are directed graphs, but in the future
it will support undirected graphs as well.
## Example
> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
> g = Graph.add_edges([{:a, :b}, {:b, :c}, {:b, :d}, {:c, :d}])
> g = Graph.label_vertex(g, :a, :start)
> g = Graph.label_vertex(g, :d, :finish)
> g = Graph.update_edge(g, :b, :d, weight: 3)
> IO.puts(Graph.to_dot(g))
strict digraph {
start
b
c
finish
start -> b [weight=1]
b -> c [weight=1]
b -> finish [weight=3]
c -> finish [weight=1]
}
"""
@spec to_dot(t) :: {:ok, binary} | {:error, term}
def to_dot(%__MODULE__{} = g) do
Graph.Serializers.DOT.serialize(g)
end
@spec to_edgelist(t) :: {:ok, binary} | {:error, term}
def to_edgelist(%__MODULE__{} = g) do
Graph.Serializers.Edgelist.serialize(g)
end
@doc """
Returns the number of edges in the graph.
Pseudo-edges (label/weight pairs applied to an edge) are not counted, only distinct
vertex pairs where an edge exists between them are counted.
## Example
iex> g = Graph.add_edges(Graph.new, [{:a, :b}, {:b, :c}, {:a, :a}])
...> Graph.num_edges(g)
3
"""
@spec num_edges(t) :: non_neg_integer
def num_edges(%__MODULE__{out_edges: oe, edges: meta}) do
Enum.reduce(oe, 0, fn {from, tos}, sum ->
Enum.reduce(tos, sum, fn to, s ->
s + map_size(Map.get(meta, {from, to}))
end)
end)
end
@doc """
Returns the number of vertices in the graph
## Example
iex> g = Graph.add_vertices(Graph.new, [:a, :b, :c])
...> Graph.num_vertices(g)
3
"""
@spec num_vertices(t) :: non_neg_integer
def num_vertices(%__MODULE__{vertices: vs}) do
map_size(vs)
end
@doc """
Returns true if and only if the graph `g` is a tree.
This function always returns false for undirected graphs.
NOTE: Multiple edges between the same pair of vertices in the same direction are
considered a single edge when determining if the provided graph is a tree.
"""
@spec is_tree?(t) :: boolean
def is_tree?(%__MODULE__{type: :undirected}), do: false
def is_tree?(%__MODULE__{out_edges: es, vertices: vs} = g) do
num_edges = Enum.reduce(es, 0, fn {_, out}, sum -> sum + MapSet.size(out) end)
if num_edges == map_size(vs) - 1 do
length(components(g)) == 1
else
false
end
end
@doc """
Returns true if the graph is an aborescence, a directed acyclic graph,
where the *root*, a vertex, of the arborescence has a unique path from itself
to every other vertex in the graph.
"""
@spec is_arborescence?(t) :: boolean
def is_arborescence?(%__MODULE__{type: :undirected}), do: false
def is_arborescence?(%__MODULE__{} = g), do: Graph.Directed.is_arborescence?(g)
@doc """
Returns the root vertex of the arborescence, if one exists, otherwise nil.
"""
@spec arborescence_root(t) :: vertex | nil
def arborescence_root(%__MODULE__{type: :undirected}), do: nil
def arborescence_root(%__MODULE__{} = g), do: Graph.Directed.arborescence_root(g)
@doc """
Returns true if and only if the graph `g` is acyclic.
"""
@spec is_acyclic?(t) :: boolean
defdelegate is_acyclic?(g), to: Graph.Directed
@doc """
Returns true if the graph `g` is not acyclic.
"""
@spec is_cyclic?(t) :: boolean
def is_cyclic?(%__MODULE__{} = g), do: not is_acyclic?(g)
@doc """
Returns true if graph `g1` is a subgraph of `g2`.
A graph is a subgraph of another graph if it's vertices and edges
are a subset of that graph's vertices and edges.
## Example
iex> g1 = Graph.new |> Graph.add_vertices([:a, :b, :c, :d]) |> Graph.add_edge(:a, :b) |> Graph.add_edge(:b, :c)
...> g2 = Graph.new |> Graph.add_vertices([:b, :c]) |> Graph.add_edge(:b, :c)
...> Graph.is_subgraph?(g2, g1)
true
iex> g1 = Graph.new |> Graph.add_vertices([:a, :b, :c, :d]) |> Graph.add_edges([{:a, :b}, {:b, :c}])
...> g2 = Graph.new |> Graph.add_vertices([:b, :c, :e]) |> Graph.add_edges([{:b, :c}, {:c, :e}])
...> Graph.is_subgraph?(g2, g1)
false
"""
@spec is_subgraph?(t, t) :: boolean
def is_subgraph?(%__MODULE__{} = a, %__MODULE__{} = b) do
meta1 = a.edges
vs1 = a.vertices
meta2 = b.edges
vs2 = b.vertices
for {v, _} <- vs1 do
unless Map.has_key?(vs2, v), do: throw(:not_subgraph)
end
for {edge_key, g1_edge_meta} <- meta1 do
case Map.fetch(meta2, edge_key) do
{:ok, g2_edge_meta} ->
unless MapSet.subset?(MapSet.new(g1_edge_meta), MapSet.new(g2_edge_meta)) do
throw(:not_subgraph)
end
_ ->
throw(:not_subgraph)
end
end
true
catch
:throw, :not_subgraph ->
false
end
@doc """
See `dijkstra/3`.
"""
@spec get_shortest_path(t, vertex, vertex) :: [vertex] | nil
defdelegate get_shortest_path(g, a, b), to: Graph.Pathfinding, as: :dijkstra
@doc """
Gets the shortest path between `a` and `b`.
As indicated by the name, this uses Dijkstra's algorithm for locating the shortest path, which
means that edge weights are taken into account when determining which vertices to search next. By
default, all edges have a weight of 1, so vertices are inspected at random; which causes this algorithm
to perform a naive depth-first search of the graph until a path is found. If your edges are weighted however,
this will allow the algorithm to more intelligently navigate the graph.
## Example
iex> g = Graph.new |> Graph.add_edges([{:a, :b}, {:b, :c}, {:c, :d}, {:b, :d}])
...> Graph.dijkstra(g, :a, :d)
[:a, :b, :d]
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :c}, {:b, :c}, {:b, :d}])
...> Graph.dijkstra(g, :a, :d)
nil
"""
@spec dijkstra(t, vertex, vertex) :: [vertex]
defdelegate dijkstra(g, a, b), to: Graph.Pathfinding
@doc """
## Example
iex> g = Graph.new |> Graph.add_edges([
...> {:b, :c, weight: -2}, {:a, :b, weight: 1},
...> {:c, :d, weight: 3}, {:b, :d, weight: 4}])
...> Graph.bellman_ford(g, :a)
%{97 => 0, 98 => 1, 99 => -1, 100 => 2}
iex> g = Graph.new |> Graph.add_edges([
...> {:b, :c, weight: -2}, {:a, :b, weight: -1},
...> {:c, :d, weight: -3}, {:d, :a, weight: -5}])
...> Graph.bellman_ford(g, :a)
nil
"""
@spec bellman_ford(t, vertex) :: [vertex]
defdelegate bellman_ford(g, a), to: Graph.Pathfinding
@doc """
Gets the shortest path between `a` and `b`.
The A* algorithm is very much like Dijkstra's algorithm, except in addition to edge weights, A*
also considers a heuristic function for determining the lower bound of the cost to go from vertex
`v` to `b`. The lower bound *must* be less than the cost of the shortest path from `v` to `b`, otherwise
it will do more harm than good. Dijkstra's algorithm can be reframed as A* where `lower_bound(v)` is always 0.
This function puts the heuristics in your hands, so you must provide the heuristic function, which should take
a single parameter, `v`, which is the vertex being currently examined. Your heuristic should then determine what the
lower bound for the cost to reach `b` from `v` is, and return that value.
## Example
iex> g = Graph.new |> Graph.add_edges([{:a, :b}, {:b, :c}, {:c, :d}, {:b, :d}])
...> Graph.a_star(g, :a, :d, fn _ -> 0 end)
[:a, :b, :d]
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :c}, {:b, :c}, {:b, :d}])
...> Graph.a_star(g, :a, :d, fn _ -> 0 end)
nil
"""
@spec a_star(t, vertex, vertex, (vertex, vertex -> integer)) :: [vertex]
defdelegate a_star(g, a, b, hfun), to: Graph.Pathfinding
@doc """
Builds a list of paths between vertex `a` and vertex `b`.
The algorithm used here is a depth-first search, which evaluates the whole
graph until all paths are found. Order is guaranteed to be deterministic,
but not guaranteed to be in any meaningful order (i.e. shortest to longest).
## Example
iex> g = Graph.new |> Graph.add_edges([{:a, :b}, {:b, :c}, {:c, :d}, {:b, :d}, {:c, :a}])
...> Graph.get_paths(g, :a, :d)
[[:a, :b, :c, :d], [:a, :b, :d]]
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :c}, {:b, :c}, {:b, :d}])
...> Graph.get_paths(g, :a, :d)
[]
"""
@spec get_paths(t, vertex, vertex) :: [[vertex]]
defdelegate get_paths(g, a, b), to: Graph.Pathfinding, as: :all
@doc """
Return a list of all the edges, where each edge is expressed as a tuple
of `{A, B}`, where the elements are the vertices involved, and implying the
direction of the edge to be from `A` to `B`.
NOTE: You should be careful when using this on dense graphs, as it produces
lists with whatever you've provided as vertices, with likely many copies of
each. I'm not sure if those copies are shared in-memory as they are unchanged,
so it *should* be fairly compact in memory, but I have not verified that to be sure.
## Example
iex> g = Graph.new |> Graph.add_vertex(:a) |> Graph.add_vertex(:b) |> Graph.add_vertex(:c)
...> g = g |> Graph.add_edge(:a, :c) |> Graph.add_edge(:b, :c)
...> Graph.edges(g)
[%Graph.Edge{v1: :a, v2: :c}, %Graph.Edge{v1: :b, v2: :c}]
"""
@spec edges(t) :: [Edge.t()]
def edges(%__MODULE__{out_edges: edges, edges: meta, vertices: vs}) do
edges
|> Enum.flat_map(fn {source_id, out_neighbors} ->
source = Map.get(vs, source_id)
out_neighbors
|> Enum.flat_map(fn out_neighbor ->
target = Map.get(vs, out_neighbor)
meta = Map.get(meta, {source_id, out_neighbor})
Enum.map(meta, fn {label, weight} ->
Edge.new(source, target, label: label, weight: weight)
end)
end)
end)
end
@doc """
Returns a list of all edges inbound or outbound from vertex `v`.
## Example
iex> g = Graph.new |> Graph.add_edges([{:a, :b}, {:b, :c}])
...> Graph.edges(g, :b)
[%Graph.Edge{v1: :a, v2: :b}, %Graph.Edge{v1: :b, v2: :c}]
iex> g = Graph.new |> Graph.add_edges([{:a, :b}, {:b, :c}])
...> Graph.edges(g, :d)
[]
"""
@spec edges(t, vertex) :: [Edge.t()]
def edges(
%__MODULE__{
in_edges: ie,
out_edges: oe,
edges: meta,
vertices: vs,
vertex_identifier: vertex_identifier
},
v
) do
v_id = vertex_identifier.(v)
v_in = Map.get(ie, v_id) || MapSet.new()
v_out = Map.get(oe, v_id) || MapSet.new()
v_all = MapSet.union(v_in, v_out)
e_in =
Enum.flat_map(v_all, fn v2_id ->
case Map.get(meta, {v2_id, v_id}) do
nil ->
[]
edge_meta when is_map(edge_meta) ->
v2 = Map.get(vs, v2_id)
for {label, weight} <- edge_meta do
Edge.new(v2, v, label: label, weight: weight)
end
end
end)
e_out =
Enum.flat_map(v_all, fn v2_id ->
case Map.get(meta, {v_id, v2_id}) do
nil ->
[]
edge_meta when is_map(edge_meta) ->
v2 = Map.get(vs, v2_id)
for {label, weight} <- edge_meta do
Edge.new(v, v2, label: label, weight: weight)
end
end
end)
e_in ++ e_out
end
@doc """
Returns a list of all edges between `v1` and `v2`.
## Example
iex> g = Graph.new |> Graph.add_edge(:a, :b, label: :uses)
...> g = Graph.add_edge(g, :a, :b, label: :contains)
...> Graph.edges(g, :a, :b)
[%Graph.Edge{v1: :a, v2: :b, label: :contains}, %Graph.Edge{v1: :a, v2: :b, label: :uses}]
iex> g = Graph.new(type: :undirected) |> Graph.add_edge(:a, :b, label: :uses)
...> g = Graph.add_edge(g, :a, :b, label: :contains)
...> Graph.edges(g, :a, :b)
[%Graph.Edge{v1: :a, v2: :b, label: :contains}, %Graph.Edge{v1: :a, v2: :b, label: :uses}]
"""
@spec edges(t, vertex, vertex) :: [Edge.t()]
def edges(%__MODULE__{type: type, edges: meta, vertex_identifier: vertex_identifier}, v1, v2) do
with v1_id <- vertex_identifier.(v1),
v2_id <- vertex_identifier.(v2),
edge_key <- {v1_id, v2_id},
edge_meta <- Map.get(meta, edge_key, %{}) do
case type do
:directed ->
edge_list(v1, v2, edge_meta, type)
:undirected ->
edge_meta2 = Map.get(meta, {v2_id, v1_id}, %{})
merged_meta = Map.merge(edge_meta, edge_meta2)
edge_list(v1, v2, merged_meta, type)
end
end
end
defp edge_list(v1, v2, edge_meta, :undirected) do
for {label, weight} <- edge_meta do
if v1 > v2 do
Edge.new(v2, v1, label: label, weight: weight)
else
Edge.new(v1, v2, label: label, weight: weight)
end
end
end
defp edge_list(v1, v2, edge_meta, _) do
for {label, weight} <- edge_meta do
Edge.new(v1, v2, label: label, weight: weight)
end
end
@doc """
Get an Edge struct for a specific vertex pair, or vertex pair + label.
## Example
iex> g = Graph.new |> Graph.add_edges([{:a, :b}, {:a, :b, label: :contains}, {:a, :b, label: :uses}])
...> Graph.edge(g, :b, :a)
nil
iex> g = Graph.new |> Graph.add_edges([{:a, :b}, {:a, :b, label: :contains}, {:a, :b, label: :uses}])
...> Graph.edge(g, :a, :b)
%Graph.Edge{v1: :a, v2: :b}
iex> g = Graph.new |> Graph.add_edges([{:a, :b}, {:a, :b, label: :contains}, {:a, :b, label: :uses}])
...> Graph.edge(g, :a, :b, :contains)
%Graph.Edge{v1: :a, v2: :b, label: :contains}
iex> g = Graph.new(type: :undirected) |> Graph.add_edges([{:a, :b}, {:a, :b, label: :contains}, {:a, :b, label: :uses}])
...> Graph.edge(g, :a, :b, :contains)
%Graph.Edge{v1: :a, v2: :b, label: :contains}
"""
@spec edge(t, vertex, vertex) :: Edge.t() | nil
@spec edge(t, vertex, vertex, label) :: Edge.t() | nil
def edge(%__MODULE__{} = g, v1, v2) do
edge(g, v1, v2, nil)
end
def edge(%__MODULE__{type: :undirected} = g, v1, v2, label) do
if v1 > v2 do
do_edge(g, v2, v1, label)
else
do_edge(g, v1, v2, label)
end
end
def edge(%__MODULE__{} = g, v1, v2, label) do
do_edge(g, v1, v2, label)
end
defp do_edge(%__MODULE__{edges: meta, vertex_identifier: vertex_identifier}, v1, v2, label) do
with v1_id <- vertex_identifier.(v1),
v2_id <- vertex_identifier.(v2),
edge_key <- {v1_id, v2_id},
{:ok, edge_meta} <- Map.fetch(meta, edge_key),
{:ok, weight} <- Map.fetch(edge_meta, label) do
Edge.new(v1, v2, label: label, weight: weight)
else
_ ->
nil
end
end
@doc """
Returns a list of all the vertices in the graph.
NOTE: You should be careful when using this on large graphs, as the list it produces
contains every vertex on the graph. I have not yet verified whether Erlang ensures that
they are a shared reference with the original, or copies, but if the latter it could result
in running out of memory if the graph is too large.
## Example
iex> g = Graph.new |> Graph.add_vertex(:a) |> Graph.add_vertex(:b)
...> Graph.vertices(g)
[:a, :b]
"""
@spec vertices(t) :: vertex
def vertices(%__MODULE__{vertices: vs}) do
Map.values(vs)
end
@doc """
Returns true if the given vertex exists in the graph. Otherwise false.
## Example
iex> g = Graph.new |> Graph.add_vertices([:a, :b])
...> Graph.has_vertex?(g, :a)
true
iex> g = Graph.new |> Graph.add_vertices([:a, :b])
...> Graph.has_vertex?(g, :c)
false
"""
@spec has_vertex?(t, vertex) :: boolean
def has_vertex?(%__MODULE__{vertices: vs, vertex_identifier: vertex_identifier}, v) do
v_id = vertex_identifier.(v)
Map.has_key?(vs, v_id)
end
@doc """
Returns the label for the given vertex.
If no label was assigned, it returns [].
## Example
iex> g = Graph.new |> Graph.add_vertex(:a) |> Graph.label_vertex(:a, :my_label)
...> Graph.vertex_labels(g, :a)
[:my_label]
"""
@spec vertex_labels(t, vertex) :: term | []
def vertex_labels(%__MODULE__{vertex_labels: labels, vertex_identifier: vertex_identifier}, v) do
with v1_id <- vertex_identifier.(v),
true <- Map.has_key?(labels, v1_id) do
Map.get(labels, v1_id)
else
_ -> []
end
end
@doc """
Adds a new vertex to the graph. If the vertex is already present in the graph, the add is a no-op.
You can provide optional labels for the vertex, aside from the variety of uses this has for working
with graphs, labels will also be used when exporting a graph in DOT format.
## Example
iex> g = Graph.new |> Graph.add_vertex(:a, :mylabel) |> Graph.add_vertex(:a)
...> [:a] = Graph.vertices(g)
...> Graph.vertex_labels(g, :a)
[:mylabel]
iex> g = Graph.new |> Graph.add_vertex(:a, [:mylabel, :other])
...> Graph.vertex_labels(g, :a)
[:mylabel, :other]
"""
@spec add_vertex(t, vertex, label) :: t
def add_vertex(g, v, labels \\ [])
def add_vertex(
%__MODULE__{vertices: vs, vertex_labels: vl, vertex_identifier: vertex_identifier} = g,
v,
labels
)
when is_list(labels) do
id = vertex_identifier.(v)
case Map.get(vs, id) do
nil ->
%__MODULE__{g | vertices: Map.put(vs, id, v), vertex_labels: Map.put(vl, id, labels)}
_ ->
g
end
end
def add_vertex(g, v, label) do
add_vertex(g, v, [label])
end
@doc """
Like `add_vertex/2`, but takes a list of vertices to add to the graph.
## Example
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :a])
...> Graph.vertices(g)
[:a, :b]
"""
@spec add_vertices(t, [vertex]) :: t
def add_vertices(%__MODULE__{} = g, vs) when is_list(vs) do
Enum.reduce(vs, g, &add_vertex(&2, &1))
end
@doc """
Updates the labels for the given vertex.
If no such vertex exists in the graph, `{:error, {:invalid_vertex, v}}` is returned.
## Example
iex> g = Graph.new |> Graph.add_vertex(:a, :foo)
...> [:foo] = Graph.vertex_labels(g, :a)
...> g = Graph.label_vertex(g, :a, :bar)
...> Graph.vertex_labels(g, :a)
[:foo, :bar]
iex> g = Graph.new |> Graph.add_vertex(:a)
...> g = Graph.label_vertex(g, :a, [:foo, :bar])
...> Graph.vertex_labels(g, :a)
[:foo, :bar]
"""
@spec label_vertex(t, vertex, term) :: t | {:error, {:invalid_vertex, vertex}}
def label_vertex(
%__MODULE__{vertices: vs, vertex_labels: labels, vertex_identifier: vertex_identifier} =
g,
v,
vlabels
)
when is_list(vlabels) do
with v_id <- vertex_identifier.(v),
true <- Map.has_key?(vs, v_id),
old_vlabels <- Map.get(labels, v_id),
new_vlabels <- old_vlabels ++ vlabels,
labels <- Map.put(labels, v_id, new_vlabels) do
%__MODULE__{g | vertex_labels: labels}
else
_ -> {:error, {:invalid_vertex, v}}
end
end
def label_vertex(g, v, vlabel) do
label_vertex(g, v, [vlabel])
end
@doc """
iex> graph = Graph.new |> Graph.add_vertex(:a, [:foo, :bar])
...> [:foo, :bar] = Graph.vertex_labels(graph, :a)
...> graph = Graph.remove_vertex_labels(graph, :a)
...> Graph.vertex_labels(graph, :a)
[]
iex> graph = Graph.new |> Graph.add_vertex(:a, [:foo, :bar])
...> [:foo, :bar] = Graph.vertex_labels(graph, :a)
...> Graph.remove_vertex_labels(graph, :b)
{:error, {:invalid_vertex, :b}}
"""
@spec remove_vertex_labels(t, vertex) :: t | {:error, {:invalid_vertex, vertex}}
def remove_vertex_labels(
%__MODULE__{
vertices: vertices,
vertex_labels: vertex_labels,
vertex_identifier: vertex_identifier
} = graph,
vertex
) do
graph.vertex_labels
|> Map.put(vertex, [])
with vertex_id <- vertex_identifier.(vertex),
true <- Map.has_key?(vertices, vertex_id),
labels <- Map.put(vertex_labels, vertex_id, []) do
%__MODULE__{graph | vertex_labels: labels}
else
_ -> {:error, {:invalid_vertex, vertex}}
end
end
@doc """
Replaces `vertex` with `new_vertex` in the graph.
## Example
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :b}, {:b, :c}, {:c, :a}, {:c, :d}])
...> [:a, :b, :c, :d] = Graph.vertices(g)
...> g = Graph.replace_vertex(g, :a, :e)
...> [:b, :c, :d, :e] = Graph.vertices(g)
...> Graph.edges(g)
[%Graph.Edge{v1: :b, v2: :c}, %Graph.Edge{v1: :c, v2: :d}, %Graph.Edge{v1: :c, v2: :e}, %Graph.Edge{v1: :e, v2: :b}]
"""
@spec replace_vertex(t, vertex, vertex) :: t | {:error, :no_such_vertex}
def replace_vertex(
%__MODULE__{out_edges: oe, in_edges: ie, edges: em, vertex_identifier: vertex_identifier} =
g,
v,
rv
) do
vs = g.vertices
labels = g.vertex_labels
with v_id <- vertex_identifier.(v),
true <- Map.has_key?(vs, v_id),
rv_id <- vertex_identifier.(rv),
vs <- Map.put(Map.delete(vs, v_id), rv_id, rv) do
oe =
for {from_id, to} = e <- oe, into: %{} do
fid = if from_id == v_id, do: rv_id, else: from_id
cond do
MapSet.member?(to, v_id) ->
{fid, MapSet.put(MapSet.delete(to, v_id), rv_id)}
from_id != fid ->
{fid, to}
:else ->
e
end
end
ie =
for {to_id, from} = e <- ie, into: %{} do
tid = if to_id == v_id, do: rv_id, else: to_id
cond do
MapSet.member?(from, v_id) ->
{tid, MapSet.put(MapSet.delete(from, v_id), rv_id)}
to_id != tid ->
{tid, from}
:else ->
e
end
end
meta =
em
|> Stream.map(fn
{{^v_id, ^v_id}, meta} -> {{rv_id, rv_id}, meta}
{{^v_id, v2_id}, meta} -> {{rv_id, v2_id}, meta}
{{v1_id, ^v_id}, meta} -> {{v1_id, rv_id}, meta}
edge -> edge
end)
|> Enum.into(%{})
labels =
case Map.get(labels, v_id) do
nil -> labels
label -> Map.put(Map.delete(labels, v_id), rv_id, label)
end
%__MODULE__{
g
| vertices: vs,
out_edges: oe,
in_edges: ie,
edges: meta,
vertex_labels: labels
}
else
_ -> {:error, :no_such_vertex}
end
end
@doc """
Removes a vertex from the graph, as well as any edges which refer to that vertex. If the vertex does
not exist in the graph, it is a no-op.
## Example
iex> g = Graph.new |> Graph.add_vertex(:a) |> Graph.add_vertex(:b) |> Graph.add_edge(:a, :b)
...> [:a, :b] = Graph.vertices(g)
...> [%Graph.Edge{v1: :a, v2: :b}] = Graph.edges(g)
...> g = Graph.delete_vertex(g, :b)
...> [:a] = Graph.vertices(g)
...> Graph.edges(g)
[]
"""
@spec delete_vertex(t, vertex) :: t
def delete_vertex(
%__MODULE__{out_edges: oe, in_edges: ie, edges: em, vertex_identifier: vertex_identifier} =
g,
v
) do
vs = g.vertices
ls = g.vertex_labels
with v_id <- vertex_identifier.(v),
true <- Map.has_key?(vs, v_id),
oe <- Map.delete(oe, v_id),
ie <- Map.delete(ie, v_id),
vs <- Map.delete(vs, v_id),
ls <- Map.delete(ls, v_id) do
oe = for {id, ns} <- oe, do: {id, MapSet.delete(ns, v_id)}, into: %{}
ie = for {id, ns} <- ie, do: {id, MapSet.delete(ns, v_id)}, into: %{}
em = for {{id1, id2}, _} = e <- em, v_id != id1 && v_id != id2, do: e, into: %{}
%__MODULE__{g | vertices: vs, vertex_labels: ls, out_edges: oe, in_edges: ie, edges: em}
else
_ -> g
end
end
@doc """
Like `delete_vertex/2`, but takes a list of vertices to delete from the graph.
## Example
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c]) |> Graph.delete_vertices([:a, :b])
...> Graph.vertices(g)
[:c]
"""
@spec delete_vertices(t, [vertex]) :: t
def delete_vertices(%__MODULE__{} = g, vs) when is_list(vs) do
Enum.reduce(vs, g, &delete_vertex(&2, &1))
end
@doc """
Like `add_edge/3` or `add_edge/4`, but takes a `Graph.Edge` struct created with
`Graph.Edge.new/2` or `Graph.Edge.new/3`.
## Example
iex> g = Graph.new |> Graph.add_edge(Graph.Edge.new(:a, :b))
...> [:a, :b] = Graph.vertices(g)
...> Graph.edges(g)
[%Graph.Edge{v1: :a, v2: :b}]
"""
@spec add_edge(t, Edge.t()) :: t
def add_edge(%__MODULE__{} = g, %Edge{v1: v1, v2: v2, label: label, weight: weight}) do
add_edge(g, v1, v2, label: label, weight: weight)
end
@doc """
Adds an edge connecting `v1` to `v2`. If either `v1` or `v2` do not exist in the graph,
they are automatically added. Adding the same edge more than once does not create multiple edges,
each edge is only ever stored once.
Edges have a default weight of 1, and an empty (nil) label. You can change this by passing options
to this function, as shown below.
## Example
iex> g = Graph.new |> Graph.add_edge(:a, :b)
...> [:a, :b] = Graph.vertices(g)
...> Graph.edges(g)
[%Graph.Edge{v1: :a, v2: :b, label: nil, weight: 1}]
iex> g = Graph.new |> Graph.add_edge(:a, :b, label: :foo, weight: 2)
...> [:a, :b] = Graph.vertices(g)
...> Graph.edges(g)
[%Graph.Edge{v1: :a, v2: :b, label: :foo, weight: 2}]
"""
@spec add_edge(t, vertex, vertex) :: t
@spec add_edge(t, vertex, vertex, Edge.edge_opts()) :: t | no_return
def add_edge(g, v1, v2, opts \\ [])
def add_edge(%__MODULE__{type: :undirected} = g, v1, v2, opts) when is_list(opts) do
if v1 > v2 do
do_add_edge(g, v2, v1, opts)
else
do_add_edge(g, v1, v2, opts)
end
end
def add_edge(%__MODULE__{} = g, v1, v2, opts) when is_list(opts) do
do_add_edge(g, v1, v2, opts)
end
defp do_add_edge(%__MODULE__{vertex_identifier: vertex_identifier} = g, v1, v2, opts) do
v1_id = vertex_identifier.(v1)
v2_id = vertex_identifier.(v2)
%__MODULE__{in_edges: ie, out_edges: oe, edges: meta} =
g = g |> add_vertex(v1) |> add_vertex(v2)
out_neighbors =
case Map.get(oe, v1_id) do
nil -> MapSet.new([v2_id])
ms -> MapSet.put(ms, v2_id)
end
in_neighbors =
case Map.get(ie, v2_id) do
nil -> MapSet.new([v1_id])
ms -> MapSet.put(ms, v1_id)
end
edge_meta = Map.get(meta, {v1_id, v2_id}, %{})
{label, weight} = Edge.options_to_meta(opts)
edge_meta = Map.put(edge_meta, label, weight)
%__MODULE__{
g
| in_edges: Map.put(ie, v2_id, in_neighbors),
out_edges: Map.put(oe, v1_id, out_neighbors),
edges: Map.put(meta, {v1_id, v2_id}, edge_meta)
}
end
@doc """
This function is like `add_edge/3`, but for multiple edges at once, it also accepts edge specifications
in a few different ways to make it easy to generate graphs succinctly.