Skip to content

Conversation

@npochhi
Copy link
Contributor

@npochhi npochhi commented May 21, 2018

Description

Implements all the basic classes

  • Relevant Issues : (required)
  • Relevant PRs : (optional)
  • Type of change :
    • New feature
    • Bug fix for existing feature
    • Code quality improvement
    • Addition or Improvement of tests
    • Addition or Improvement of documentation

@npochhi npochhi changed the base branch from master to graph-class-formation May 21, 2018 05:30
@npochhi npochhi force-pushed the graph-class branch 4 times, most recently from ee8c2c8 to 2d5762c Compare May 21, 2018 17:54
@npochhi npochhi closed this May 21, 2018
@npochhi npochhi reopened this May 21, 2018
@npochhi npochhi closed this May 21, 2018
@npochhi npochhi reopened this May 21, 2018
@npochhi npochhi closed this May 22, 2018
@npochhi npochhi reopened this May 22, 2018
def number_of_edges
num = 0
@adj.each { |_, v| num += v.length }
num
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if how this is better performance-wise, but you can probably also try it out.

def number_of_edges
  @adj.values.map(&:length).inject(:+)
end

@adj[node].length
end

# Returns the reversed version of a the graph
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a the -> the

In general, just double-check triple-check the YARD comments for any typos. 😄

# graph.to_undirected
def to_undirected
new_graph = NetworkX::Graph.new(@graph)
@nodes.each_key { |k| new_graph.add_node(k, @nodes[k]) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any specific reason for not using the more obvious each rather than each_key when we anyway have to access the value (@nodes[k]) too? Maybe change it to:

@nodes.each { |k, v| new_graph.add_node(k, v) }

@nodes.each_key { |k| new_graph.add_node(k, @nodes[k]) }
@adj.each_key do |k|
@adj[k].each_key { |v| new_graph.add_edge(v, k, @adj[k][v]) }
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, use Hash#each rather than Hash#each_key 😄

sub_graph = NetworkX::DiGraph.new(@graph)
nodes.each do |u, _|
raise KeyError, "#{u} does not exist in the current graph!" unless @nodes.key?(u)
sub_graph.add_node(u, @nodes[u])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yet again, each vs each_key 😄

end
end

# Returns subgraph conisting of given edges
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: conisting -> consisting

#
# @param edges [Array<Object, Object>] the array of edges
# @param weight [Array<Integer>] the array of weights
def add_weighted_edges(edges, weights)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The args provided in the documentation and the args of the method don't match. Have a look at this again.

#
# @param node [Object] the node to be checked
def node?(node)
@node.key?(node)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: @node -> @nodes

How did this not come up in the unit tests? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method was quite trivial like some other methods like edge? etc. Do I need to add tests for such trivial methods as well? 😥

# @param node_1 [Object] the first node of the edge to be checked
# @param node_2 [Object] the second node of the edge to be checked
def edge?(node_1, node_2)
return true if @nodes.key?(node_1) && @adj[node_1].key?(node_2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not re-use the key? method here instead of writing @nodes.key? again?

# @param node_2 [Object] the second node of the edge to be checked
def edge?(node_1, node_2)
return true if @nodes.key?(node_1) && @adj[node_1].key?(node_2)
false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the explicit true and false really required here? The method could simply be just 1-line. 😄

def edge?(node_1, node_2)
  node?(node_1) && @adj[node_1].key?(node_2)
end

#
# @param node [Object] the node whose data is to be fetched
def get_node_data(node)
return @nodes[node] if @nodes.key?(node)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, the above node? method could be used rather than re-writing @nodes.key?(node)

# @param node [Object] the node whose data is to be fetched
def get_node_data(node)
return @nodes[node] if @nodes.key?(node)
raise ArgumentError, 'No such node exists!'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the other way round would be more readable?

def get_node_data(node)
  raise ArgumentError, 'No such node exists' unless node?(node)
  @nodes[node]
end

Copy link
Member

@athityakumar athityakumar May 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow this for the next 2 methods too (with KeyError, and node? instead of @nodes.key). Let's keep the review DRY. 😉

num = 0
@adj.each { |_, v| num += v.length }
num / 2
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to the map + inject combination I had mentioned in an earlier comment.

# @example
# graph.size(true)
#
# @praram is_weighted [Bool] if we want weighted size of unweighted size
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make same documentation comment changes, as mentioned to the same method in the previous file. (Logic explanation moved to params + typo)

def new_edge_key(node_1, node_2)
return 0 if @adj[node_1][node_2].nil?
key = @adj[node_1][node_2].length
key += 1 while @adj[node_1][node_2].key?(key)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this to handle deletion on intermediate key edges? If so, you can use maintain a last_key and use it here rather than #length and loop for coming to last_key.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we would have to store the last_key attribute for every edge. Wouldn't that be an overhead? Also, we'd like to reuse the keys after deleting them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I took the reference of networkx for the implementation of this method.

raise KeyError, "#{node_2} is not a valid node" unless @nodes.key?(node_2)
raise KeyError, 'The given edge is not a valid one.' unless @adj[node_1].key?(node_2)
raise KeyError, 'The given edge is not a valid one.' unless @adj[node_1][node_2].key?(key)
@adj[node_1][node_2].delete(key)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These raise checks come up in the DiGraph class too, right? Maybe the raise checks can be put as a separate method to be called from both remove_edge methods.

super(node_1, node_2) if key.nil?
return true if @nodes.key?(node_1) && @adj[node_1].key?(node_2) && @adj[node_1][node_2].key?(key)
false
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to the "explicit true-false" comment above. :trollface:

# graph.to_undirected
def to_undirected
graph = NetworkX::Graph.new(@graph)
@nodes.each { |node, node_attr| graph.add_node(node, node_attr) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't MultiDiGraph#to_undirected supposed to written Multigraph`? Have a good look at this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, had a look at the to_multigraph method. But in networkx, the #to_undirected is intended to return MultiGraph.

Reference: https://networkx.github.io/documentation/stable/reference/classes/generated/networkx.MultiDiGraph.to_undirected.html

@MridulS @v0dro - Any comments on which one might be more intuitive?

@adj[node_1][node_2].each_key { |key| edge_attrs.merge!(@adj[node_1][node_2][key]) }
graph.add_edge(node_1, node_2, edge_attrs)
end
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember the each vs each_key comment? :trollface:

graph
end

# Returns the reversed version of a the graph
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: a the -> the

@adj.each_key do |u|
@adj[u].each_key { |v| @adj[u][v].each_key { |k| new_graph.add_edge(v, u, @adj[u][v][k]) } }
end
new_graph
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... And the each vs each_key again

in_degree_no = 0
@pred[node].each_key { |u| in_degree_no += @pred[node][u].length }
in_degree_no
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to the map + inject comment

def out_degree(node)
out_degree_no = 0
@adj[node].each_key { |u| out_degree_no += @adj[node][u].length }
out_degree_no
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to the map + inject comment above

no_of_edges = 0
@adj.each_key { |node_1| @adj[node_1].each_key { |node_2| no_of_edges += @adj[node_1][node_2].length } }
no_of_edges
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you might have guessed by now, map + inject again. 😉

end
end
end
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to the comments mentioned in the MultiDiGraph class

Copy link
Member

@athityakumar athityakumar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @npochhi. Pretty good PR, but there have been lots of suggestions as there are multiple Graph classes with very similar things to fix. To mention some,

  • map + inject
  • each vs each_key
  • Explicit true/false returns from a method
  • Typos

Also, I found a method that had a typo in it's code (variable). That's what unit tests are supposed to prevent, but Travis CI build seemingly passed. Which means, the unit-tests are not complete. Can you please add the unit-tests to all the methods?

Copy link
Member

@athityakumar athityakumar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work with this PR. 👍

@athityakumar athityakumar merged commit 2dfe5be into SciRuby:graph-class-formation Jun 9, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants