Git Product home page Git Product logo

libgraph's Introduction

libgraph

Master Hex.pm Version Coverage Status

Documentation

About

This library provides:

  • An implementation of a graph datastructure, Graph, designed for both directed and undirected graphs. The API supports undirected graphs, but I'm still getting the tests updated to cover properties of undirected graphs.
  • A priority queue implementation PriorityQueue, oriented towards graphs (it prioritizes lower integer values over high), it is the fastest priority queue I know of which allows arbitrary priorities, and is more or less at parity with pqueue3 from the pqueue library, which supports priorities from 0 to 65535.
  • An idiomatic Elixir API for creating, modifying, and querying its graph structure. Creating and modifying a graph can be done in a single pipeline, and all queries take a Graph as their first parameter (one of my complaints with :digraph is that there is some inconsistency with the API between :digraph and :digraph_utils for no apparent reason).
  • Two "Reducer" implementations for mapping/reducing over a graph. I am trying to figure out the best way to make these extendable and part of the API, so that you can drop in your own shortest path algorithms, etc - but I have yet to come up with an approach that feels good on that front.
  • A Serializer behaviour, for defining custom serialization of graphs, with a Graphviz DOT format serializer provided out of the box.

It is backed by a large suite of tests, including several QuickCheck properties for the graph model. Its API shares some similarity with :digraph, but diverges in favor of a more idiomatic Elixir interface. In addition, over time I'm adding new functions to query the graph in ways not previously supported via :digraph, and introducing support for classifying a graph as undirected if so desired, so that queries over such graphs become easier.

If you are interested in reading more about how you can make use of libgraph, there is an excellent blog post written by Tony Hammond which is a very helpful walkthrough of the library and what can be built with it.

Installation

If available in Hex, the package can be installed by adding libgraph to your list of dependencies in mix.exs:

def deps do
  [{:libgraph, "~> 0.16.0"}]
end

Rationale

The original motivation for me to start working on this library is the fact that :digraph requires a minimum of 3 ETS tables per graph, and up to 6 depending on the operations you are performing on the graph. If you are working with a lot of graphs concurrently, as I am, this means you can find yourself in a situation where you hit the system limit for the maximum number of ETS table, and bring your system down. Seeing as how it is ridiculous that trying to use a simple graph could potentially kill my system, and not wanting to hack around the problem, I decided to see if I could build an alternative which was competitive performance-wise, without requiring any ETS tables at all.

The result turned out better than I hoped - it is possible to build a graph datastructure without ETS that is both equally performant (and in many of my benchmarks, better performing), and supports all of the same functionality.

Additionally, I also had a few other things I wanted to address:

  • Inconsistency with argument order in the API between :digraph and :digraph_utils
  • The fact that there are two modules to work with the same datastructure to begin with, and trying to remember what lives where.
  • The lack of extensibility, for example, there is no API with which you can implement your own traversal algorithms. This means you are stuck with whatever way the Erlang maintainers decided was ideal, regardless of whether it suits your use case or not. A great example is single-source shortest path algorithms, where you may want a simple breadth-first search, or perhaps you want to use Dijkstra's algorithm - you are stuck with just one approach with :digraph, which as I understand it, is a breadth-first search.
  • :digraph as the name implies, only supports directed graphs
  • :digraph graphs are unweighted, with no way to supported weighted graphs
  • :digraph graphs are not "inspect-friendly", you get a tuple with the underlying ETS table ids, but that's it, not necessarily a big deal, but it's nice for playing around in the shell if you can see how your code affects the structure.

My speculation as to why :digraph is the way it is, is that when :digraph was originally written, there was no efficient key/value datastructure in Erlang that could support large numbers of keys. At that time, maps weren't even a speck in the eye of the language maintainers. Even after the initial introduction of maps in OTP 18, maps still weren't efficient enough to work with large numbers of keys. It wasn't until OTP 19 that the performance of maps with millions of keys became reasonable. So, it's not that :digraph sucks - it was the best possible implementation at the time; but now that the language has come so far, we can take advantage of some of the new hotness and reinvent it from the ground up :).

Benchmarks

Feel free to take a look under the bench folder in the project root. There a few benchmarks I threw together to keep an eye on a few key areas I wanted to ensure parity with :digraph on. You can run them yourself as well, but I would encourage you to use them as a template to construct a benchmark based on your own use case, and compare them that way, as it will give you a better basis to make your decision on. However, if you do find that libgraph is behind :digraph with a benchmark, please let me know so that I can improve the library!

NOTE: While this library is primarily focused on the Graph data structure it defines, it also contains an implementation of a priority queue (you can find it under the PriorityQueue module), designed for use with graphs specifically, as it considers lower integer values higher priority, which is perfect for the kinds of graph algorithms you need a priority queue for.

Contributing

To run the test suite you will need to run mix eqc.install --mini once you've cloned the repo and fetched dependencies.

If you have changes in mind that are significant or potentially time consuming, please open a RFC-style PR first, where we can discuss your plans first. I don't want you to spend all your time crafting a PR that I ultimately reject because I don't think it's a good fit or is too large for me to review. Not that I plan to reject PRs in general, but I have to be careful to balance features with maintenance burden, or I will quickly be unable to manage the project.

Please ensure that you adhere to a commit style where logically related changes are in a single commit, or broken up in a way that eases review if necessary. Keep commit subject lines informative, but short, and provide additional detail in the extended message text if needed. If you can, mention relevant issue numbers in either the subject or the extended message.

Roadmap

Please open an issue if you have a feature request!

License

MIT (See the LICENSE file)

libgraph's People

Contributors

akoutmos avatar andersonmcook avatar bitwalker avatar ctbucha avatar dogwynn avatar georgewhewell avatar guidotripaldi avatar hauleth avatar kianmeng avatar lostkobrakai avatar machinesareus avatar minh-khang avatar princemaple avatar quinnwilton avatar renaudlenne avatar rlipscombe avatar robindaumann avatar rupurt avatar sevenseacat avatar stavro avatar stevegolton avatar stevensonmt avatar tgrk avatar toadjamb avatar wojtekmach avatar yonkeltron avatar zblanco avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

libgraph's Issues

Suspected memory leak

Hello, I am getting some behavior that makes me suspect there is a memory leak happening when removing and adding labels to a graph.

I have some code similar to the following:

[id] = MyApp.Server.get_by_label(label_val) # genserver callback w/ graph reducer
[label] = MyApp.Server.get_label(id) # wraps vertex_labels
new_label = Map.put(label,"foo",true) # update label
MyApp.Server.rm_label(id) # wraps remove_vertex_labels
MyApp.Server.add_label(id,new_label) # wraps label_vertex

This causes BEAM to run out of memory after a few hundred calls to this function.
Let me know if you'd like a self-running example or if theres any tracing/debugging I can do to get more info.

Lazy traversal

Does it make sense to allow pathfinding in a lazy way? Where the nodes and the edges can be added whilst traversing.

Or is this a silly idea?

Unexpectedly endless pathfinding from the nth size.

Machine: Intel Core i5-8250U (1.60GHz), 16gb RAM
OS: Ubuntu 22.04
Elixir version: Elixir 1.15.0-rc.0
OTP version: 25.0
libgraph version: 0.16.0

I use graph to accumulate pages while I crawl. The crawler is a stream of pages: above page (previous) and sub page (current). Such relationship I have to record to find paths between pages later.

I noticed that from nth node it becomes impossible to compute paths. Literally, when my crawler reached a goal and needed to collect paths from A to B pages, it took all night and there was no calculation result (the calculation was definitely still going on, judging by the load).

I decided to log every thousandth relationship recorded and try to find all paths from the starting page to the current page (I record page to subpage relationship).

The results are as follows (relation a sub page to the above page -> time to find all paths from the first page to the sub page):

  • 1000 -> 99µ
  • 2000 -> 179µ
  • 3000 -> 306µ
  • 4000 -> 302µ
  • 5000 -> 745µ
  • 6000 -> 834µ
  • 7000 -> 3018µ
  • 8000 -> 3059µ
  • 9000 -> 6092µ
  • 10000 -> 66057µ
  • 11000 -> infinity

I used this to record relationship:

graph
|> Graph.add_vertex(abv_ref, abv)
|> Graph.add_vertex(sub_ref, sub)
|> Graph.add_edge(abv_ref, sub_ref)

Where abv and sub are structs, abv_ref and sub_ref are strings.

Tested like this:

IO.puts("Task now: #{x}")
if rem(x, 1000) === 0 do
  IO.puts("Trying to get all paths from the start to current urls...")

  {d, result} = :timer.tc(fn ->
    Graph.get_paths(graph, start_ref, sub_ref)
  end)

  IO.puts("Got result in #{d}µ (#{div(d, 1_000_000)}s): #{inspect(result)}")
end

Where result is a list of strings, start_ref is string as well.


I don't know if my problem is unique or if it's a general property of the library graph. Is there a benchmark that tests such a large amount of relations? Send links if available. If not, my suggestion is to make one.

Graph.edges/2 broken?

Hello @bitwalker , thanks for this lib.

I have been using it and found that Graph.edges/2 does not return edges attached to vertices that don't have only inbound or outbound vertices (but not both).

Example

iex> g = Graph.new |> Graph.add_edges([{:a, :b}, {:b, :c}])
#Graph<type: directed, vertices: [:a, :b, :c], edges: [:a -> :b, :b -> :c]>

All edges are present, as expected.

iex> Graph.edges(g)
[%Graph.Edge{label: nil, v1: :a, v2: :b, weight: 1},
 %Graph.Edge{label: nil, v1: :b, v2: :c, weight: 1}]

I expected this to return [%Graph.Edge{label: nil, v1: :a, v2: :b, weight: 1}] but it returns an empty list

iex> Graph.edges(g, :a)
[ ]

Same thing here

iex> Graph.edges(g, :c)
[ ]

Is this expected behaviour? (Using version 0.11.1)

Feature: Deserializers

It might be neat to have a deserializer behavior for converting from edgelists or DOT files or JSON. Is this something you've considered?

Edge weight integer

Hi,

I want to do path finding between multiple vertexes. I'm adding edges but my current weight values are decimal. The library returns the following message:

(ArgumentError) invalid value for :weight, must be an integer

I understand that integers are faster and don't have rounding errors. What is the reason that weights must be an integer?

ps: thanks for the lib :-)

Functions delegate to Graph.Directed for undirected graph

Aside from pathfinding issues #37 and #11, several of the Graph functions delegate to Graph.Directed in a way that is inapproriate for undirected graphs:

  • is_acyclic?
  • components
  • strong_components
  • reachable
  • reachable_neighbors
  • reaching
  • reaching_neighbors
  • preorder
  • postorder
  • loop_vertices

Incorrect A* pathfinding with undirected graph

Hi there, I am just getting started with libgraph but I have noticed that I seem to be getting wrong results for the a_star function.

What I am doing basically:

g = Graph.new(type: :undirected)
|> Graph.add_edges([{:dp1, :dp2, [weight: 3]}, {:dp2, :dp3, [weight: 6]}, {:dp3, :dp4, [weight: 5]}])
|> Graph.add_edges([{:dp4, :dp5, [weight: 4]}, {:dp4, :dp6, [weight: 5]}, {:dp5, :dp6, [weight: 6]}])
|> Graph.add_edges([{:dp6, :dp7, [weight: 5]}, {:dp7, :dp8, [weight: 4]}, {:dp8, :dp9, [weight: 2]}])
|> Graph.add_edges([{:dp9, :dp10, [weight: 6]}, {:dp3, :dp5, [weight: 3]}, {:dp5, :dp1, [weight: 7]}])
|> Graph.add_edges([{:dp6, :dp3, [weight: 7]}])

iex(1)> Graph.a_star(g, :dp1, :dp6, fn v -> 0 end)

The result I get is:

[:dp1, :dp2, :dp3, :dp4, :dp6]

However, the correct result would be:

[:dp1, :dp2, :dp3, :dp6]

Thanks in advance.

PS.: If you need a picture of the graph created above just ask.

ArgumentError in Graph.degeneracy/1 for version 0.10.2

Encountered this error (appears to be from ETS) when processing a smallish graph.

** (ArgumentError) argument error
      (stdlib) :ets.update_counter(#Reference<0.2437733789.3691642882.33749>, "heller.name", {2, -1})
    (libgraph) lib/graph.ex:1470: anonymous fn/4 in Graph.decompose_cores/4
      (elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3
    (libgraph) lib/graph.ex:1469: anonymous fn/6 in Graph.decompose_cores/4
      (elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3
    (libgraph) lib/graph.ex:1467: Graph.decompose_cores/4
    (libgraph) lib/graph.ex:1451: Graph.decompose_cores/1
    (libgraph) lib/graph.ex:1395: Graph.degeneracy/1
...snip...

Using Graph.info tells me that it looks like this: %{num_edges: 201, num_vertices: 101, size_in_bytes: 69504, type: :directed} while the printer representation gives me #Graph<type: directed, num_vertices: 101, num_edges: 201>. Neither of these enable me to send you the graph I'm working with. Any tips on how to serialize the graph for transmission to you?

Happy to provide more info since everything's bogus and simulated anyhow!

Thanks,
+Jonathan

RFC: Implement k-core identification

I love this library and have used it for graph analysis and manipulation at work! I was hoping to request k-core detection for graphs. I'd love to be able to do something like Graph.k_cores(some_graph, 5) for finding 5-cores. I was considering a signature like the following:

@spec k_cores(Graph.t(), integer()) :: [Graph.t()]
def k_cores(g, k) do
  ...
end

Can I provide additional information or clarification? Thanks again for making such a wonderful library!

Duplicated edges from Graph.edges(g, v) when v has a self-reference?

Hi and first of all, thanks for maintaining this library. It has turned out to be essential to me since I started using it.

I'm a big fan of pattern matching over edges in the graph and it's a very intuitive way to write complicated case handling. However I just discovered that I'm getting duplicated edges when I am matching on, eg.

case graph |> Graph.edges(metric_key) do
    %{v1: ^metric_key, v2: ^metric_key} -> ...
    ... other cases
end

From a glance at the code, a possible cause could be if a self-referencing edge could be described by both v_in and v_out:

v_all = MapSet.union(v_in, v_out)

But the description would have to differ, otherwise the MapSet would make it unique.. Do you think this is a correct cause and if so, is it an intended one?

I don't suppose there is any inherent disadvantage in just matching instead on all edges as such:

case graph |> Graph.edges() do
    %{v1: ^metric_key, v2: ^metric_key} -> ...
    ... other cases
    _ -> [] # not the stuff we're interested in
end |> List.flatten()

Looking at the code, it seems to be pretty much the same amount of work being done -- and in my situation, I always need to flat_map anyway in what I am doing.

However, someone else may be tripped up on it and have a situation where getting duplicates could cause confusion down the line.

Best regards,
Dennis

Graph.edges/2 does not work with bidirectional edges between vertices

Graph.edges/2 only returns a single direction of edges from one vertex :a to another vertex :b instead of both directions from the vertex :a to the other vertex :b.

iex(1)> g = Graph.new() |> Graph.add_edges([{:a, :b, label: "label1"}, {:a, :b, label: "label2"}, {:b, :a, label: "label3"}]) |> Graph.edges(:a)
[
  %Graph.Edge{label: "label1", v1: :a, v2: :b, weight: 1},
  %Graph.Edge{label: "label2", v1: :a, v2: :b, weight: 1}
]

Expected:

[
  %Graph.Edge{label: "label1", v1: :a, v2: :b, weight: 1},
  %Graph.Edge{label: "label2", v1: :a, v2: :b, weight: 1},
  %Graph.Edge{label: "label3", v1: :b, v2: :a, weight: 1}
]

Feature: Multigraphs

Hi bitwalker,
Thank you for this amazing hex package, am looking to create multigraph(multi edges between same ends with diff edges data) and as well label edge coloring.

is there any future plans to support it?

remove vertex labels

There does not seem to be a way to remove/reset/overwrite vertex labels. The only method that appears to be an option is Graph.label_vertex.

graph = Graph.new
graph = Graph.add_vertex(graph, :v1)
graph = Graph.label_vertex(graph, :v1, [:label1])
Graph.vertex_labels(graph, :v1) #=> [:label1]
graph = Graph.label_vertex(graph, :v1, [:label1, :label2])
Graph.vertex_labels(graph, :v1) #=> [:label1, :label1, :label2]

If label_vertex is the only method supplied, I would expect it to overwrite the labels, but it does not. It always adds a new label even if the label already exists. The most correct fix in my opinion is to update label_vertex to simply set the labels for a vertex to the ones passed in, but that may break existing code, so the next best solution may be to add a remove_vertex_labels that would remove all labels for a given vertex. Would you be willing to accept a pull request for either of these approaches and if so, do you have a preference for one over the other?

Update the behavior of Graph.label_vertex:

graph = Graph.new
graph = Graph.add_vertex(graph, :v1)

graph = Graph.label_vertex(graph, :v1, [:label1])
Graph.vertex_labels(graph, :v1) #=> [:label1]

graph = Graph.label_vertex(graph, :v1, [:label2, :label3])
Graph.vertex_labels(graph, :v1) #=> [:label2, :label3]

Add a new method (Graph.remove_vertex_labels):

graph = Graph.new
graph = Graph.add_vertex(graph, :v1)

graph = Graph.label_vertex(graph, :v1, [:label1])
Graph.vertex_labels(graph, :v1) #=> [:label1]

graph = Graph.remove_vertex_labels(graph, :v1)
Graph.vertex_labels(:v1) #=> []

Duplicate vertex ids when adding high number of vertices

When adding large number of vertices, some of my vertices weren't added to the graph.

For example

vertices = 0..250000 |> Enum.map(& &1)
Graph.add_vertices(Graph.new, vertices)
#Graph<type: directed, num_vertices: 249997, num_edges: 0>

I fixed it for myself by changing the function Graph.Utils.vertex_id(v) to
def vertex_id(v), do: v
instead of
def vertex_id(v), do: :erlang.phash2(v, @max_phash)

Apparently I had duplicate key in my graph otherwise.

New release?

The last release was almost three years ago. Is there any reason for not releasing all the latest additions?

Neo4j Serializer

Hello @bitwalker ,

I am working on exporting my libgraph graph to Neo4j. I see the way you do that for GraphViz is by using the in-built DOT serializer.

Where should new serializers live? Should they be contributed back to libgraph or should they simply implement the Graph.Serializer behaviour and live in their own libraries?

Thanks.

Question: Core in Erlang

Good Morning Bitwalker,

First of all , a great job on the library.

One question: did you ever consider porting the libgraph-core to Erlang ??
I saw you are using some erlang-dependencies, but I do not have enough Elixir know-how to deduce how much effort would be needed or if it is possible at all.

Kind regards, and thanks for the library - which might well propel me into the direction of Elixir.

finding cycles in directed graph

My graph theory's a little rusty, so it might be easy to do using the provided API, but is there an easy way to get a list of all the cycles (either as new Graphs, or as edgelists) in a directed graph?

is_cyclic/1 can tell me if there is a cycle, but I want to know what the actual cycles are (I realise this is computationally expensive, that's fine because I only care about small graphs).

Graph.edges/3 return empty list for undirected graph

For the following code:

Graph.new(type: :undirected)
|> Graph.add_vertices([:a, :b]) 
|> Graph.add_edge(:a, :b) 
|> Graph.edges(:a, :b)

works as expected [%Graph.Edge{label: nil, v1: :a, v2: :b, weight: 1}] but

Graph.new(type: :undirected) 
|> Graph.add_vertices([:a, :b]) 
|> Graph.add_edge(:a, :b) 
|> Graph.edges(:b, :a)

does return an empty list [].

Publish to Hex

The currently published version of this package on Hex is 0.13.3. There have been 2 releases since then (0.14.0 was released in 2019). Is there any reason these releases haven't been published?

Save metadata for vertices and edges

My use case is to build a Graph from data coming from a DB. When there are changes to it, I want to persist it back to DB.

For this to work, I am currently abusing the labels from vertices and edges. For vertex it is a Keyword list and for edges it is a map.

It would be much more convenient if there was a metadata field for both and a function to set it. Something like:

Graph.put_metadata(g, v, %{id: 1234, comment: "some comment"}) # sets metadata for vertex
Graph.put_metadata(g, v1, v2, l, %{id: 1234, comment: "some comment"}) # sets metadata for edge

I can create a PR if this is a worthwhile feature.

add_vertices/2 with labels?

Hello!

Thank you for your work that, cool stuff!
Any reason why not to include in add_vertices/2 API labels, like in case of add_vertex/3?

Cheers,
F.

Graph.delete_vertex/2 does not remove references in `in_edges`

Given

graph =
  Graph.new
  |> Graph.add_vertices([1, 2, 4, 6])
  |> Graph.add_edge(1, 2)
  |> Graph.add_edge(2, 4)
  |> Graph.add_edge(4, 6)
  
graph_two = 
  graph
  |> Graph.add_vertices([3, 5, 7])
  |> Graph.add_edge(1, 3)
  |> Graph.add_edge(3, 4)
  |> Graph.add_edge(3, 5)
  |> Graph.add_edge(5, 6)
  |> Graph.add_edge(5, 7)

Test

assert graph == Graph.delete_vertices(graph_two, [3, 5, 7])
# The result is false

Inspect

Map.from_struct graph_two
%{
  edges: %{
    {539485162, 2577631668} => %{nil: 1},
    {1379807339, 3239926044} => %{nil: 1},
    {2577631668, 1379807339} => %{nil: 1}
  },
  in_edges: %{
    1379807339 => #MapSet<[1186525603, 2577631668]>,  #1186525603 dangling reference
    2577631668 => #MapSet<[539485162]>,
    3239926044 => #MapSet<[676967202, 1379807339]> #676967202 dangling reference
  },
  out_edges: %{
    539485162 => #MapSet<[2577631668]>,
    1379807339 => #MapSet<[3239926044]>,
    2577631668 => #MapSet<[1379807339]>
  },
  type: :directed,
  vertex_labels: %{
    539485162 => [],
    1379807339 => [],
    2577631668 => [],
    3239926044 => []
  },
  vertices: %{539485162 => 1, 1379807339 => 4, 2577631668 => 2, 3239926044 => 6}
}

These dangling references also raises nil enumeration errors when performing future operations.

Offending line

Graph.delete_vertex/2 is missing comprehension for in edges

Fix is to add

ie = for {id, ns} <- ie, do: {id, MapSet.delete(ns, v_id)}, into: %{}

Incorrect Return Value of Preorder/Postorder

How to reproduce:

These return the same ordering. This seems to be because the traversals are relying on Key ordering of the map

Graph.new() |> Graph.add_edges([{"123456", "b"}]) |> Graph.preorder
Graph.new() |> Graph.add_edges([{"123456", "b"}]) |> Graph.postorder

These do not return same ordering

Graph.new() |> Graph.add_edges([{"12345", "b"}]) |> Graph.preorder
Graph.new() |> Graph.add_edges([{"12345", "b"}]) |> Graph.postorder

Might be better for Graph.topsort/1 to return :error and {:ok, sorted_graph}?

Dialyzer complains The test [any()] == 'false' can never evaluate to 'true' when I try to check the output of Graph.topsort/1. Apparently the typespec annotation confused it. I guess one could either change the return value to :error | {:ok, sorted_graph} or change the typespec so that Dialyzer doesn't find it problematic? Or am I doing something wrong here.

[Contributing] Fresh clones don't work out of the box.

Hello @bitwalker, thanks for your work on this lib.

I just cloned this repo, run mix deps.get and mix test and got the following error.

$ mix test
** (CompileError) test/model_test.exs:3: module :eqc_gen is not loaded and could not be found
    (eqc_ex) expanding macro: EQC.__using__/1
    test/model_test.exs:3: Graph.Model.Test (module)
    (elixir) expanding macro: Kernel.use/1
    test/model_test.exs:3: Graph.Model.Test (module)
    (eqc_ex) expanding macro: EQC.ExUnit.__using__/1
    test/model_test.exs:3: Graph.Model.Test (module)
    (elixir) expanding macro: Kernel.use/1
    test/model_test.exs:3: Graph.Model.Test (module)
    (elixir) lib/code.ex:370: Code.require_file/2
    (elixir) lib/kernel/parallel_require.ex:57: anonymous fn/2 in Kernel.ParallelRequire.spawn_requires/5

Is there anything more than mix deps.get that needs to be done in order to get up and running?

Thanks.

v0.11.0 add_vertex finds old label if new label is nil

Environment

Erlang/OTP 20 [erts-9.0] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

Elixir 1.5.1

Ubuntu 16.04

Current behavior

Test is:

  1. add a vertex with a non-nil label
  2. delete the vertex
  3. add the same vertex with nil label
  4. get the vertex's label

The comparisons at the end confirms the label is the original one.

v1 = :v1
l1a = :l1a
l1b = nil

g = Graph.new()

g = g |> Graph.add_vertex(v1, l1a)

g = g |> Graph.delete_vertex(v1)

g = g |> Graph.add_vertex(v1, l1b)

l1c = g |> Graph.vertex_label(v1)

true = (l1c == l1a)
false = (l1c == l1b)

A quick look at the graph's stuct shows the old label to have been retained in the vertex_labels after the delete_vertex.

Expected behavior

The nil label should be used.

DOT output should quote edge label text

The DOT serialization feature is really handy! I can see myself adding TGF support in the future (in my copious free time) so there could be other serialization formats coming along.

In the meantime, the label text for edges should be quoted. I have a forward slash in my labels and noticed that it causes dot to spit out an error.

...syntax error in line 10 near '/'

The label looks like [label=HTTP/80; weight=1] but should look something like [label="HTTP/80"; weight=1].

Can I provide additional information or something?

get_shortest_path doesn't work correctly on undirected graphs

Hi!

I found an example, where get_shortest_path didn't works, where it looks like it should:

g = Graph.new(type: :undirected) |> Graph.add_vertices([1, 2, 3]) |> Graph.add_edge(1, 3) |> Graph.add_edge(3, 2)
Graph.get_shortest_path(g, 1, 2) # => nil

I think, that get_shortest_path should raise on undirected graphs or calculate path correctly.

add support for leaf_vertices function?

Hi @bitwalker, thank you for this awesome library. I have a use-case where I'm building up a dependency tree and I want to visit all of the leaf nodes first to resolve those before I bother to visit nodes that I know have unresolved dependencies.

I was thinking that I would fork this repo and open a PR that adds a Graph.leaf_vertices/1 function. I'm guessing that the implementation will look something like iterating through the in_edges keys (things that we know have a dependent) and skipping any items that also appear in the out_edges map. I think that should give me all of the leaf vertices in an efficient way?

I just wanted to check with you first if this was a desirable change to the project before I do the work to open the PR

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.