lib/graph/pathfindings/bellman_ford.ex

defmodule Pathfindings.BellmanFord do
  @moduledoc """
  The Bellman–Ford algorithm is an algorithm that computes shortest paths from a single
  source vertex to all of the other vertices in a weighted digraph.
  It is capable of handling graphs in which some of the edge weights are negative numbers

  Time complexity: O(VLogV)
  """
  import Graph.Utils, only: [vertex_id: 1]

  @type distance :: %{Graph.vertex_id => integer}

  @doc """
  Returns nil when graph has negative cycle.
  """
  @spec call(Graph.t, Graph.vertex) :: [Graph.vertex] | nil
  def call(%Graph{vertices: vs, edges: meta}, a) do
    distances = a |> vertex_id |> init_distances(vs)

    distances = vs |> Enum.reduce(distances, fn (_vertex, distances) ->
      meta |> Enum.reduce(distances, &update_distance(&1, &2))
    end)

    if has_negative_cycle?(distances, meta) do
      nil
    else
      distances
    end
  end

  @spec init_distances(Graph.vertex, Graph.vertices) :: distance
  defp init_distances(vertex_id, vertices) do
    Enum.reduce(vertices, Map.new, fn {id, _vertex}, dist ->
      Map.put(dist, id, init_distance_value(id, vertex_id))
    end)
  end

  defp init_distance_value(vertex_id, id) when vertex_id == id, do: 0
  defp init_distance_value(_, _), do: :infinity

  @spec update_distance(term, distance) :: distance
  defp update_distance({{u, v}, _} = edge, distances) do
    weight = edge_weight(edge)

    if distances[u] != :infinity and distances[u] + weight < distances[v] do
      Map.replace!(distances, v, distances[u] + weight)
    else
      distances
    end
  end

  @spec edge_weight(term) :: float
  defp edge_weight({_, edge_value}), do: edge_value |> Map.values |> List.first

  defp has_negative_cycle?(distances, meta) do
    meta |> Enum.reduce(false, fn({{u, v}, _} = edge, has_cycle) ->
      weight = edge_weight(edge)

      has_cycle or distances[u] != :infinity and distances[u] + weight < distances[v]
    end)
  end
end