Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
zblanco committed Aug 27, 2024
1 parent 3b5d4c4 commit 8a23cd3
Show file tree
Hide file tree
Showing 8 changed files with 42 additions and 122 deletions.
Empty file modified .gitignore
100755 → 100644
Empty file.
17 changes: 6 additions & 11 deletions lib/edge.ex
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ defmodule Graph.Edge do
@type edge_opt ::
{:weight, integer | float}
| {:label, term}
| {:properties, map}
@type edge_opts :: [edge_opt]

@doc """
Expand All @@ -39,14 +38,11 @@ defmodule Graph.Edge do
@spec new(Graph.vertex(), Graph.vertex()) :: t
@spec new(Graph.vertex(), Graph.vertex(), [edge_opt]) :: t | no_return
def new(v1, v2, opts \\ []) when is_list(opts) do
{weight, opts} = Keyword.pop(opts, :weight, 1)
{label, opts} = Keyword.pop(opts, :label)

%__MODULE__{
v1: v1,
v2: v2,
weight: weight,
label: label,
weight: Keyword.get(opts, :weight, 1),
label: Keyword.get(opts, :label),
properties: Keyword.get(opts, :properties, %{})
}
end
Expand All @@ -55,13 +51,12 @@ defmodule Graph.Edge do
def options_to_meta(opts) when is_list(opts) do
label = Keyword.get(opts, :label)
weight = Keyword.get(opts, :weight, 1)
properties = Keyword.get(opts, :properties, %{})

case {label, %{weight: weight, properties: properties}} do
{label, %{weight: w} = meta} when is_number(w) ->
{label, meta}
case {label, weight} do
{_, w} = meta when is_number(w) ->
meta

_ ->
{_, _} ->
raise ArgumentError, message: "invalid value for :weight, must be an integer"
end
end
Expand Down
84 changes: 12 additions & 72 deletions lib/graph.ex
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ defmodule Graph do
@type edge_index_key :: label | term
@type edge_properties :: %{
label: label,
weight: edge_weight,
properties: map
weight: edge_weight
}
@type edge_value :: %{label => edge_properties()}
@type graph_type :: :directed | :undirected
Expand Down Expand Up @@ -510,12 +509,8 @@ defmodule Graph do
edge_meta when is_map(edge_meta) ->
v2 = Map.get(vs, v2_id)

for {label, meta_value} <- edge_meta do
Edge.new(v2, v,
label: label,
weight: meta_value.weight,
properties: meta_value.properties
)
for {label, weight} <- edge_meta do
Edge.new(v2, v, label: label, weight: weight)
end
end
end)
Expand All @@ -529,12 +524,8 @@ defmodule Graph do
edge_meta when is_map(edge_meta) ->
v2 = Map.get(vs, v2_id)

for {label, meta_value} <- edge_meta do
Edge.new(v2, v,
label: label,
weight: meta_value.weight,
properties: meta_value.properties
)
for {label, weight} <- edge_meta do
Edge.new(v, v2, label: label, weight: weight)
end
end
end)
Expand Down Expand Up @@ -636,8 +627,8 @@ defmodule Graph do
v2_id <- vertex_identifier.(v2),
edge_key <- {v1_id, v2_id},
{:ok, edge_meta} <- Map.fetch(meta, edge_key),
{:ok, %{weight: weight, properties: properties}} <- Map.fetch(edge_meta, label) do
Edge.new(v1, v2, label: label, weight: weight, properties: properties)
{:ok, weight} <- Map.fetch(edge_meta, label) do
Edge.new(v1, v2, label: label, weight: weight)
else
_ ->
nil
Expand Down Expand Up @@ -1043,24 +1034,8 @@ defmodule Graph do
end

edge_meta = Map.get(meta, {v1_id, v2_id}, %{})
{label, options_meta} = Edge.options_to_meta(opts)
edge_meta = Map.put(edge_meta, label, options_meta)

g =
if g.multigraph do
edge = Edge.new(v1, v2, label: label, weight: options_meta.weight, properties: opts)

partition = g.edge_indexer.(edge)
key = {v1_id, partition}
set = Map.get(g.edge_index, key, MapSet.new())

%__MODULE__{
g
| edge_index: Map.put(g.edge_index, key, MapSet.put(set, {v1_id, v2_id}))
}
else
g
end
{label, weight} = Edge.options_to_meta(opts)
edge_meta = Map.put(edge_meta, label, weight)

%__MODULE__{
g
Expand All @@ -1075,7 +1050,7 @@ defmodule Graph do
in a few different ways to make it easy to generate graphs succinctly.
Edges must be provided as a list of `Edge` structs, `{vertex, vertex}` pairs, or
`{vertex, vertex, edge_opts :: [label: term, weight: integer, properties: map]}`.
`{vertex, vertex, edge_opts :: [label: term, weight: integer]}`.
See the docs for `Graph.Edge.new/2` or `Graph.Edge.new/3` for more info on creating Edge structs, and
`add_edge/3` for information on edge options.
Expand Down Expand Up @@ -2267,12 +2242,8 @@ defmodule Graph do
Enum.flat_map(v_out, fn v2_id ->
v2 = Map.get(vs, v2_id)

Enum.map(Map.get(meta, {v_id, v2_id}), fn {label, edge_meta} ->
Edge.new(v, v2,
label: label,
weight: edge_meta.weight,
properties: edge_meta.properties
)
Enum.map(Map.get(meta, {v_id, v2_id}), fn {label, weight} ->
Edge.new(v, v2, label: label, weight: weight)
end)
end)
else
Expand All @@ -2281,37 +2252,6 @@ defmodule Graph do
end
end

def out_edges(
%__MODULE__{
vertices: vs,
edges: edges,
multigraph: true,
edge_index: edge_index,
vertex_identifier: vertex_identifier,
edge_indexer: edge_indexer
},
v,
partition
) do
v1_id = vertex_identifier.(v)
key = {v1_id, partition}
# only return out_edges for which the index key returns a subset
edge_index
|> Map.get(key, MapSet.new())
|> Enum.flat_map(fn {_v1_id, v2_id} = edge_key ->
v2 = Map.get(vs, v2_id)

edges
|> Map.get(edge_key, [])
|> Enum.map(fn {label, edge_meta} ->
Edge.new(v, v2, label: label, weight: edge_meta.weight, properties: edge_meta.properties)
end)
|> Enum.filter(fn edge ->
edge_indexer.(edge) == partition
end)
end)
end

@doc """
Builds a maximal subgraph of `g` which includes all of the vertices in `vs` and the edges which connect them.
Expand Down
3 changes: 1 addition & 2 deletions lib/graph/pathfinding.ex
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ defmodule Graph.Pathfinding do

@type heuristic_fun :: (Graph.vertex() -> integer)

@spec bellman_ford(Graph.t(), Graph.vertex()) ::
%{Graph.vertex() => integer() | :infinity} | nil
@spec bellman_ford(Graph.t, Graph.vertex) :: %{Graph.vertex() => integer() | :infinity} | nil
def bellman_ford(g, a), do: Graph.Pathfindings.BellmanFord.call(g, a)

@doc """
Expand Down
4 changes: 1 addition & 3 deletions lib/graph/serializers/dot.ex
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ defmodule Graph.Serializers.DOT do

defp serialize_nodes(%Graph{vertices: vertices} = g) do
Enum.reduce(vertices, "", fn {id, v}, acc ->
acc <>
Serializer.indent(1) <>
"#{id}" <> "[label=" <> Serializer.get_vertex_label(g, id, v) <> "]\n"
acc <> Serializer.indent(1) <> "#{id}" <> "[label=" <> Serializer.get_vertex_label(g, id, v) <> "]\n"
end)
end

Expand Down
6 changes: 3 additions & 3 deletions lib/graph/utils.ex
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ defmodule Graph.Utils do

def edge_weight(%Graph{type: :directed, edges: meta}, a, b) do
Map.fetch!(meta, {a, b})
|> Enum.map(fn {_label, %{weight: weight}} -> weight end)
|> Enum.map(fn {_label, weight} -> weight end)
|> Enum.min()
end

Expand All @@ -96,13 +96,13 @@ defmodule Graph.Utils do

edge_meta when is_map(edge_meta) ->
edge_meta
|> Enum.map(fn {_label, %{weight: weight}} -> weight end)
|> Enum.map(fn {_label, weight} -> weight end)
|> Enum.min()
end

edge_meta when is_map(edge_meta) ->
edge_meta
|> Enum.map(fn {_label, %{weight: weight}} -> weight end)
|> Enum.map(fn {_label, weight} -> weight end)
|> Enum.min()
end
end
Expand Down
26 changes: 8 additions & 18 deletions test/graph_test.exs
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -31,39 +31,29 @@ defmodule GraphTest do
{:b, :c, weight: 3},
{:b, :a, label: {:complex, :label}}
])
|> IO.inspect(structs: false)

assert Enum.count(Graph.out_edges(graph, :a)) == 3
assert [%Edge{label: :foo}] = Graph.out_edges(graph, :a, :foo)
assert [%Edge{label: :foo}] = Graph.in_edges(graph, :b, :foo)
assert [%Edge{label: :bar}] = Graph.out_edges(graph, :a, :bar)
assert [%Edge{label: nil}] = Graph.out_edges(graph, :a, nil)
assert [%Edge{label: :nil}] = Graph.out_edges(graph, :a, nil)
assert [] == Graph.out_edges(graph, :a, :foobar)
end

test "custom edge indexing function" do
graph =
Graph.new(multigraph: true, edge_indexer: fn edge -> edge.weight end)
|> Graph.add_edges([
{:a, :b},
{:a, :b, label: :foo},
{:a, :b, label: :bar},
{:b, :c, weight: 3},
{:b, :a, weight: 6}
])
test "custom vertex indexing function on edge labels" do

assert Enum.count(Graph.out_edges(graph, :b)) == 2
assert [%Edge{weight: 6}] = Graph.out_edges(graph, :b, 6)
assert [%Edge{weight: 6}] = Graph.out_edges(graph, :b, 3)
end

test "traversal using indexed keys" do
test "traversal using indexed labels" do

end

test "edge properties" do
test "updating edge properties" do

end

test "removing edges" do

end
end

Expand Down Expand Up @@ -683,7 +673,7 @@ defmodule GraphTest do
end

defp build_complex_signed_graph do
Graph.new()
Graph.new
|> Graph.add_edge(:a, :b, weight: -1)
|> Graph.add_edge(:b, :e, weight: 2)
|> Graph.add_edge(:e, :d, weight: -3)
Expand Down
24 changes: 11 additions & 13 deletions test/reducer_test.exs
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,17 @@ defmodule Graph.Reducer.Test do
end

test "can walk a graph breadth-first, when the starting points had their in-edges deleted" do
g =
Graph.new()
|> Graph.add_vertices([:a, :b, :c, :d, :e, :f, :g])
|> Graph.add_edge(:a, :b)
|> Graph.add_edge(:a, :d)
|> Graph.add_edge(:b, :c)
|> Graph.add_edge(:b, :d)
|> Graph.add_edge(:c, :e)
|> Graph.add_edge(:d, :f)
|> Graph.add_edge(:f, :g)
# Add this edge and then remove it
|> Graph.add_edge(:b, :a)
|> Graph.delete_edge(:b, :a)
g = Graph.new
|> Graph.add_vertices([:a, :b, :c, :d, :e, :f, :g])
|> Graph.add_edge(:a, :b)
|> Graph.add_edge(:a, :d)
|> Graph.add_edge(:b, :c)
|> Graph.add_edge(:b, :d)
|> Graph.add_edge(:c, :e)
|> Graph.add_edge(:d, :f)
|> Graph.add_edge(:f, :g)
|> Graph.add_edge(:b, :a) # Add this edge and then remove it
|> Graph.delete_edge(:b, :a)

expected = [:a, :b, :d, :c, :f, :e, :g]
assert ^expected = Graph.Reducers.Bfs.map(g, fn v -> v end)
Expand Down

0 comments on commit 8a23cd3

Please sign in to comment.