Skip to content

Commit

Permalink
graph validator added
Browse files Browse the repository at this point in the history
  • Loading branch information
mrkaspa committed Mar 13, 2016
1 parent c7e7c89 commit 8a09c30
Show file tree
Hide file tree
Showing 9 changed files with 371 additions and 127 deletions.
83 changes: 83 additions & 0 deletions lib/exstreme/graph.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
defmodule Exstreme.Graph do
@moduledoc """
"""
alias __MODULE__

@type t :: %Graph{params: [key: term], nodes: %{key: atom}, connections: %{key: atom}}
defstruct params: [], nodes: %{}, connections: %{}

@doc """
"""
@spec count_nodes(t) :: non_neg_integer
def count_nodes(%Graph{nodes: nodes}) do
nodes
|> Map.keys
|> Enum.count
end

@doc """
"""
@spec count_connections(t) :: non_neg_integer
def count_connections(%Graph{connections: connections}) do
connections
|> Map.keys
|> Enum.count
end

@doc """
"""
@spec connections_stats(t) :: %{key: integer}
def connections_stats(graph) do
graph
|> map_to_connections
|> Enum.reduce(Map.new, fn(key, map) ->
Map.update(map, key, 1, &(&1 + 1))
end)
end

@doc """
"""
@spec find_start_node(t) :: [atom]
def find_start_node(%Graph{nodes: nodes, connections: connections}) do
is_first? =
fn(node) ->
at_first?(connections, node) and not(at_last?(connections, node))
end

nodes
|> Map.keys
|> Enum.filter(is_first?)
end

# private

@spec map_to_connections(t) :: [atom]
defp map_to_connections(%Graph{nodes: nodes, connections: connections}) do
to_connections =
fn(node) ->
case {at_first?(connections, node), at_last?(connections, node)} do
{true, true} -> :connected
{true, false} -> :begin
{false, true} -> :end
{false, false} -> :unconnected
end
end

nodes
|> Map.keys
|> Enum.map(to_connections)
end

@spec at_first?(%{key: atom}, atom) :: boolean
defp at_first?(connections, node) do
Map.has_key?(connections, node)
end

@spec at_last?(%{key: atom}, atom) :: boolean
defp at_last?(connections, node) do
connections
|> Map.values
|> List.flatten
|> Enum.member?(node)
end
end
52 changes: 37 additions & 15 deletions lib/exstreme/stream_graph.ex → lib/exstreme/graph_creator.ex
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
defmodule Exstreme.StreamGraph do
defmodule Exstreme.GraphCreator do
@moduledoc """
"""
defmodule Graph do
defstruct params: [], nodes: %{}, connections: %{}
end
alias Exstreme.Graph

@type update_map_func :: (%{key: atom} -> %{key: atom})

@doc """
"""
@spec create_graph([key: term]) :: Graph.t
def create_graph(params \\ []), do: %Graph{params: params}

@doc """
"""
@spec create_node(Graph.t, [key: term]) :: {Graph.t, atom}
def create_node(graph = %Graph{nodes: nodes}, params \\ []) do
key = next_node_key(nodes)

Expand All @@ -20,6 +22,7 @@ defmodule Exstreme.StreamGraph do

@doc """
"""
@spec create_broadcast(Graph.t, [key: term]) :: {Graph.t, atom}
def create_broadcast(graph = %Graph{nodes: nodes}, params \\ []) do
key = next_broadcast_key(nodes)

Expand All @@ -29,6 +32,7 @@ defmodule Exstreme.StreamGraph do

@doc """
"""
@spec create_funnel(Graph.t, [key: term]) :: {Graph.t, atom}
def create_funnel(graph = %Graph{nodes: nodes}, params \\ []) do
key = next_funnel_key(nodes)

Expand All @@ -38,14 +42,16 @@ defmodule Exstreme.StreamGraph do

@doc """
"""
def add_connection(graph = %Graph{nodes: nodes}, start, finish) when start != finish do
if has_node(nodes, start) && has_node(nodes, finish) do
@spec add_connection(Graph.t, atom, atom) :: Graph.t
def add_connection(graph = %Graph{nodes: nodes}, start, finish) when start !== finish do
if has_node(nodes, start) and has_node(nodes, finish) do
update_in(graph.connections, store_connection_fn(start, finish))
end
end

#private
# private

@spec store_connection_fn(atom, atom) :: update_map_func
defp store_connection_fn(start, finish) do
fn(connections) ->
add_connection = fn(keywords, start, finish) ->
Expand All @@ -58,6 +64,7 @@ defmodule Exstreme.StreamGraph do
end)
end

# validates before adding
connections
|> validate_repeated(start, finish)
|> validate_repeated(finish, start)
Expand All @@ -67,54 +74,64 @@ defmodule Exstreme.StreamGraph do
end
end

def validate_repeated(connections, start, finish) do
if connections |> Enum.any?(&(&1 == {start, finish})) do
# validations before adding a node

@spec validate_repeated(%{key: atom}, atom, atom) :: %{key: atom}
defp validate_repeated(connections, start, finish) do
if Enum.any?(connections, &(&1 == {start, finish})) do
raise ArgumentError, message: "there is already a connection like that"
else
connections
end
end

@spec validate_position(%{key: atom}, atom, :start | :end) :: %{key: atom}
defp validate_position(connections, node, position) do
case node|> Atom.to_string |> String.first do
case node |> Atom.to_string |> String.first do
"n" -> validate_position_node(connections, node, position)
"b" -> validate_position_broadcast(connections, node, position)
"f" -> validate_position_funnel(connections, node, position)
_ -> raise ArgumentError, message: "invalid node"
end
end

@spec validate_position_node(%{key: atom}, atom, :start) :: %{key: atom}
defp validate_position_node(connections, node, :start) do
validate_position_start(connections, node,"the node can't be twice at start position #{node}")
end

@spec validate_position_node(%{key: atom}, atom, :end) :: %{key: atom}
defp validate_position_node(connections, node, :end) do
validate_position_end(connections, node,"the node can't be twice at end position")
end

defp validate_position_broadcast(connections, _, :start), do: connections
@spec validate_position_broadcast(%{key: atom}, atom, :start) :: %{key: atom}
defp validate_position_broadcast(connections, _node, :start), do: connections

@spec validate_position_broadcast(%{key: atom}, atom, :end) :: %{key: atom}
defp validate_position_broadcast(connections, bct, :end) do
validate_position_end(connections, bct, "the broadcast can't be twice at end position")
end

@spec validate_position_funnel(%{key: atom}, atom, :start) :: %{key: atom}
defp validate_position_funnel(connections, node, :start) do
validate_position_start(connections, node,"the funnel can't be twice at start position #{node}")
end

defp validate_position_funnel(connections, node, :end), do: connections
@spec validate_position_funnel(%{key: atom}, atom, :end) :: %{key: atom}
defp validate_position_funnel(connections, _node, :end), do: connections

@spec validate_position_start(%{key: atom}, atom, String.t) :: %{key: atom}
defp validate_position_start(connections, node, msg) do
exist =
connections
|> Map.has_key?(node)
exist = Map.has_key?(connections, node)
if exist do
raise ArgumentError, message: msg
else
connections
end
end

@spec validate_position_end(%{key: atom}, atom, String.t) :: %{key: atom}
defp validate_position_end(connections, node, msg) do
exist =
connections
Expand All @@ -127,6 +144,7 @@ defmodule Exstreme.StreamGraph do
end
end

@spec has_node(%{key: atom}, atom) :: true
defp has_node(nodes, node) do
if Map.has_key?(nodes, node) do
true
Expand All @@ -135,18 +153,22 @@ defmodule Exstreme.StreamGraph do
end
end

@spec next_node_key(%{key: atom}) :: atom
defp next_node_key(nodes) do
next_key(nodes, "n")
end

@spec next_broadcast_key(%{key: atom}) :: atom
defp next_broadcast_key(nodes) do
next_key(nodes, "b")
end

@spec next_funnel_key(%{key: atom}) :: atom
defp next_funnel_key(nodes) do
next_key(nodes, "f")
end

@spec next_key(%{key: atom}, String.t) :: atom
defp next_key(map, letter) do
count =
map
Expand Down
62 changes: 62 additions & 0 deletions lib/exstreme/graph_validator.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
defmodule Exstreme.GraphValidator do
alias Exstreme.Graph
@moduledoc """
"""

@doc """
"""
@spec validate(Graph.t) :: :ok | {:error, []}
def validate(graph) do
with :ok <- validate_must_have_connections(graph),
:ok <- validate_start_nodes(graph),
:ok <- validate_connectivity(graph),
do: :ok
end

# private

@spec validate_must_have_connections(Graph.t) :: :ok | {:error, []}
defp validate_must_have_connections(graph) do
nodes_amount = Graph.count_nodes(graph)
connections_amount = Graph.count_connections(graph)
if nodes_amount > 0 and connections_amount == 0 do
{:error, []}
else
:ok
end
end

@spec validate_start_nodes(Graph.t) :: :ok | {:error, []}
defp validate_start_nodes(graph) do
start_nodes = Graph.find_start_node(graph)

with :ok <- validate_should_start_with_one_node(start_nodes),
:ok <- validate_should_start_with_node(start_nodes),
do: :ok
end

@spec validate_should_start_with_one_node([atom, ...]) :: :ok | {:error, []}
defp validate_should_start_with_one_node([_]), do: :ok

defp validate_should_start_with_one_node(_), do: {:error, []}

@spec validate_should_start_with_node([atom, ...]) :: :ok | {:error, []}
defp validate_should_start_with_node([start_node]) do
start_char = start_node |> Atom.to_string |> String.first
if start_char == "n" do
:ok
else
{:error, []}
end
end

@spec validate_connectivity(Graph.t) :: :ok | {:error, []}
defp validate_connectivity(graph) do
stats = Graph.connections_stats(graph)
if stats[:unconnected] == nil do
:ok
else
{:error, []}
end
end
end
4 changes: 3 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ defmodule Exstreme.Mixfile do
#
# Type "mix help deps" for more examples and options
defp deps do
[]
[
{:dialyxir, "~> 0.3", only: [:dev]}
]
end
end
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
%{"dialyxir": {:hex, :dialyxir, "0.3.3"}}
Loading

0 comments on commit 8a09c30

Please sign in to comment.