union_find 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3d86fb59ebdb3b6b5f55e175db95bd7dccf0126b
4
- data.tar.gz: 8dfbcfdff8f2a1f20e808df6404312bbd2793967
3
+ metadata.gz: 92740adf9b4c5f2dc335cbdfdc6bf7b2d938f89b
4
+ data.tar.gz: b7b1940afc61cc57b3145f0305086e72f53061b7
5
5
  SHA512:
6
- metadata.gz: fee3e76ee13f5c64e8b8f278e8c42ac0ebb979603bc3b1e19b4e76980ba8128c9c1fd594c31d04006f61149afbf86b364cd71d0f59a2206aa7140d3350f13096
7
- data.tar.gz: 280542a0d35dc79eb7343f3efd350e8ea8629a47377cb9b04ae58076b9e3da45f983ffa5ce522518515bbef974746a097b16dfd8b87db09f8b93bbb1928b98b5
6
+ metadata.gz: 258902dec4177b88a4299b89111e01bb7187d629fb590536451854ef7d6293ed757260a43a0772ba67e2a0b2330b44cb26adbc5f0f28d41fd20e618385924e71
7
+ data.tar.gz: 0c464da3df237b751c43dd9c26d4e1d9ebf5687a9b1d0d9877c94f91cfd58f6162f083e4681c2a8d7d4d361c8b29b23343baeb8133f81164947d1f26a784598f
data/README.md CHANGED
@@ -12,9 +12,7 @@ Possible applications where we might want to find out whether two items are conn
12
12
 
13
13
  Click [here](https://www.cs.princeton.edu/~rs/AlgsDS07/01UnionFind.pdf) for more information.
14
14
 
15
- The running time of this algorithm is linear.
16
-
17
- This a Ruby implementation of [Robert Sedgewick](http://www.cs.princeton.edu/~rs/)'s and [Kevin Wayne](http://www.cs.princeton.edu/~wayne/contact/)'s [weighted quick-union algorithm with path compression](http://algs4.cs.princeton.edu/15uf/UF.java.html). Credit goes to these two authors of the book [Algorithms](http://www.amazon.com/gp/product/032157351X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&tag=algs4-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=032157351X) and to the many computer scientists that have contributed to this algorithm in the past decades.
15
+ This a Ruby implementation of a modified version of [Robert Sedgewick](http://www.cs.princeton.edu/~rs/)'s and [Kevin Wayne](http://www.cs.princeton.edu/~wayne/contact/)'s [weighted quick-union algorithm with path compression](http://algs4.cs.princeton.edu/15uf/UF.java.html). Credit goes to these two authors of the book [Algorithms](http://www.amazon.com/gp/product/032157351X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&tag=algs4-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=032157351X) and to the many computer scientists that have contributed to this algorithm in the past decades.
18
16
 
19
17
  ## Installation
20
18
 
@@ -32,15 +30,32 @@ Or install it yourself as:
32
30
 
33
31
  ## Usage
34
32
 
35
- Create a new instance of `UnionFind` and pass in an array of items:
33
+ Create a new instance of `UnionFind` and pass in a `Set` of items:
36
34
 
37
35
  ```ruby
38
- union_find = UnionFind::UnionFind.new(['Grandfather', 'Father', 'Daughter', 'Single Person'])
36
+ require 'set'
37
+ people = Set.new ['Grandfather', 'Father', 'Daughter', 'Single Person']
38
+ union_find = UnionFind::UnionFind.new(people)
39
+ ```
40
+
41
+ A `Set` is used instead of an `Array` because `Sets` filter out duplicate entries without having to call an expensive `#uniq!` method. If your data comes in form of an `Array`, you can convert it to a `Set` like so:
42
+
43
+ ```ruby
44
+ require 'set'
45
+ array = ['Grandfather', 'Father', 'Daughter', 'Single Person']
46
+ set = array.to_set
47
+ ```
48
+
49
+ Add more items on the fly:
50
+
51
+ ```ruby
52
+ union_find.add('Grandmother')
39
53
  ```
40
54
 
41
55
  Connect items (in any order):
42
56
 
43
57
  ```ruby
58
+ union_find.union('Grandfather', 'Grandmother')
44
59
  union_find.union('Grandfather', 'Father')
45
60
  union_find.union('Father', 'Daughter')
46
61
  ```
@@ -56,13 +71,21 @@ union_find.connected?('Grandfather', 'Single Person')
56
71
  => false
57
72
  ```
58
73
 
59
- Check how many isolated items there are. In this example, there are 2, namely the family the family tree (Grandfather - Father - Daugther) and the Single Person:
74
+ Check how many isolated items there are. In this example, there are 2, namely the family (Grandfather - Grandmother - Father - Daugther) and the Single Person:
60
75
 
61
76
  ```ruby
62
77
  union_find.count_isolated_components
63
78
  => 2
64
79
  ```
65
80
 
81
+ ## Performance
82
+
83
+ Initializing a data structure takes constant time: θ(1).
84
+
85
+ Afterwards, the `union()` and `connected?()` operations take logarithmic time in the worst case: O(log n).
86
+
87
+ The `count_isolated_components()` operation takes constant time: θ(1).
88
+
66
89
  ## Contributing
67
90
 
68
91
  1. Fork it ( https://github.com/[my-github-username]/union_find/fork )
@@ -12,7 +12,7 @@ module UnionFind
12
12
  # This implementation uses weighted quick union by rank with path compression
13
13
  # by halving.
14
14
 
15
- # Initializing a data structure with number_of_components sites takes linear time.
15
+ # Initializing a data structure takes linear time.
16
16
  # Afterwards, the union, find, and connected
17
17
  # operations take logarithmic time (in the worst case) and the
18
18
  # count operation takes constant time.
@@ -24,23 +24,36 @@ module UnionFind
24
24
  # @see http://algs4.cs.princeton.edu/15uf/
25
25
  class UnionFind
26
26
 
27
+ attr_accessor :components
28
+
27
29
  # Initializes an empty union-find data structure with
28
30
  # n isolated components 0 through n-1.
29
31
  # @param components [Array] components
30
32
  # @raise [ArgumentError] if components.length < 1 or if components is not an Array
31
33
  def initialize(components)
32
- raise ArgumentError, 'input is not an Array' unless components.class == Array
34
+ # Unexpected behaviour,
35
+ # for Sets the @components does not copy the content of components
36
+ # rather it points to the components variable and any changes to the components
37
+ # variables are reflected in @components.
38
+ # Force copy instead.
39
+ @components = components.dup
40
+
41
+ raise ArgumentError, 'input is not a Set' unless @components.is_a? Set
42
+
43
+ @number_of_isolated_components = @components.length
33
44
 
34
- components = components.uniq # remove duplicates
35
- @number_of_isolated_components = components.length
36
-
37
45
  raise ArgumentError, 'number of components is < 1' if @number_of_isolated_components < 1
38
46
 
39
- @parent = {} # parent of i
40
- @tree_size = {} # size of tree rooted at i (cannot be more than 31)
41
- components.each do |component|
42
- @parent[component] = component
43
- @tree_size[component] = 1
47
+ @parent = {} # parent of component
48
+ @tree_size = {} # size of tree rooted at component (cannot be more than 31)
49
+ end
50
+
51
+ # Dynamically adds component.
52
+ # @return [Component] component
53
+ def add(component)
54
+ if @components.add?(component)
55
+ # if component does not already exist
56
+ @number_of_isolated_components += 1
44
57
  end
45
58
  end
46
59
 
@@ -50,32 +63,18 @@ class UnionFind
50
63
  @number_of_isolated_components
51
64
  end
52
65
 
53
- # Returns the root of a component.
54
- # @param component_id [Integer] the integer representing one component
55
- # @return [Component] the root of the component
56
- # @raise [IndexError] unless component exists
57
- def find_root(component)
58
- raise IndexError, 'component does not exist' unless @parent[component]
59
-
60
- while component != @parent[component] # stop at the top node where component id == parent id
61
- @parent[component] = @parent[@parent[component]] # path compression by halving
62
- component = @parent[component]
63
- end
64
-
65
- return component
66
- end
67
-
68
66
  # Connect root of component 1 with root of component 2
69
67
  # by attaching smaller subtree root node with larger tree.
70
68
  # If both trees have the same size, the root of the second
71
69
  # component becomes a child of the root of the first component.
72
- # @param component_1_id [Integer] the integer representing one component
73
- # @param component_2_id [Integer] the integer representing the other component
70
+ # @param component_1 [Component] component
71
+ # @param component_2 [Component] component
74
72
  # @return [Component, NilClass] the root of the larger tree or the root of the first component if both have the same tree size or nil if no connection has been made
75
73
  def union(component_1, component_2)
76
74
  root_component_1 = find_root(component_1)
77
75
  root_component_2 = find_root(component_2)
78
76
 
77
+ # exit if already connected
79
78
  return nil if root_component_1 == root_component_2
80
79
 
81
80
  # make smaller root point to larger one
@@ -95,12 +94,42 @@ class UnionFind
95
94
  end
96
95
 
97
96
  # Do two components share the same root?
98
- # @param component_1 [Integer] the integer representing one component
99
- # @param component_2 [Integer] the integer representing the other component
97
+ # @param component_1 [Component] component
98
+ # @param component_2 [Component] component
100
99
  # @return [Boolean]
101
100
  def connected?(component_1, component_2)
102
101
  find_root(component_1) == find_root(component_2)
103
102
  end
103
+
104
+ private
105
+
106
+ # Returns the root of a component.
107
+ # @param component [Component] component
108
+ # @return [Component] the root of the component
109
+ # @raise [IndexError] unless component exists
110
+ def find_root(component)
111
+ raise IndexError, 'component does not exist' unless @components.include? component
112
+
113
+ set_parent_and_tree_size(component)
114
+
115
+ while component != @parent[component] # stop at the top node where component id == parent id
116
+ @parent[component] = @parent[@parent[component]] # path compression by halving
117
+ component = @parent[component]
118
+ end
119
+
120
+ return component
121
+ end
122
+
123
+ # Sets parent and tree size for each component.
124
+ # To prevent initialize method from taking linear time,
125
+ # this method is used to do this assignment on demand.
126
+ # @param component [Component] component
127
+ # @return [Component] component
128
+ def set_parent_and_tree_size(component)
129
+ @parent[component] ||= component
130
+ @tree_size[component] ||= 1
131
+ component
132
+ end
104
133
  end
105
134
 
106
135
  end
@@ -1,3 +1,3 @@
1
1
  module UnionFind
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -1,5 +1,4 @@
1
1
  require 'union_find'
2
- require 'pry' # to use binding.pry
3
2
 
4
3
  RSpec.configure do |config|
5
4
  # Run specs in random order to surface order dependencies. If you find an
@@ -9,7 +8,7 @@ RSpec.configure do |config|
9
8
  config.order = 'random'
10
9
 
11
10
  # when a focus tag is present in RSpec, only run tests with focus tag: http://railscasts.com/episodes/285-spork
12
- config.filter_run focus: true
11
+ config.filter_run :focus
13
12
  config.run_all_when_everything_filtered = true
14
13
  end
15
14
 
@@ -1,9 +1,8 @@
1
1
  require 'spec_helper'
2
- random = Random.new
3
2
 
4
3
  describe UnionFind::UnionFind do
5
4
  # 1 family and 1 single person
6
- people = ['Grandfather', 'Father', 'Mother', 'Son', 'Daughter', 'Single']
5
+ people = Set.new ['Grandfather', 'Father', 'Mother', 'Son', 'Daughter', 'Single']
7
6
 
8
7
  describe '#initialize' do
9
8
  context 'when no components are provided' do
@@ -19,31 +18,17 @@ describe UnionFind::UnionFind do
19
18
  end
20
19
  end
21
20
 
22
- describe '#find_root' do
23
- union_find = UnionFind::UnionFind.new(people)
24
-
25
- context 'when component does not exist' do
26
- it 'raises an exception' do
27
- expect {union_find.find_root('Some Person')}.to raise_exception(IndexError)
28
- end
29
- end
30
-
31
- context 'when component exists' do
32
- context 'when component has no parent' do
33
- it 'returns same component' do
34
- expect(union_find.find_root('Single')).to eq 'Single'
35
- end
36
- end
37
-
38
- context 'when component has parent' do
39
- create_family_tree(union_find)
40
-
41
- it 'returns parent' do
42
- expect(union_find.find_root('Daughter')).to eq 'Grandfather'
43
- end
21
+ describe '#add' do
22
+ union_find = UnionFind::UnionFind.new(people)
23
+
24
+ context 'when component does not yet exist' do
25
+ it 'adds component' do
26
+ union_find.add('Other Single')
27
+ expect(union_find.connected?('Grandfather', 'Other Single')).to be_falsey
28
+ expect(union_find.count_isolated_components).to eq people.length + 1
44
29
  end
45
- end
46
- end
30
+ end
31
+ end
47
32
 
48
33
  describe '#union' do
49
34
  context 'when one component gets connected to itself' do
@@ -68,9 +53,7 @@ describe UnionFind::UnionFind do
68
53
 
69
54
  it 'connects and returns the root of the larger tree' do
70
55
  expect(union_find.union('Single', 'Father')).to eq 'Grandfather'
71
- expect(union_find.connected?('Father', 'Single')).to be_truthy
72
- expect(union_find.find_root('Father')).to eq 'Grandfather'
73
- expect(union_find.find_root('Single')).to eq 'Grandfather'
56
+ expect(union_find.connected?('Father', 'Single')).to be_truthy
74
57
  end
75
58
  end
76
59
  end
@@ -115,5 +98,14 @@ describe UnionFind::UnionFind do
115
98
  expect(union_find.count_isolated_components).to eq 2
116
99
  end
117
100
  end
101
+
102
+ context 'when same connections have been made multiple times' do
103
+ union_find = UnionFind::UnionFind.new(people)
104
+ 2.times { create_family_tree(union_find) }
105
+
106
+ it 'returns number of components' do
107
+ expect(union_find.count_isolated_components).to eq 2
108
+ end
109
+ end
118
110
  end
119
111
  end
@@ -21,5 +21,4 @@ Gem::Specification.new do |spec|
21
21
  spec.add_development_dependency "bundler", "~> 1.6"
22
22
  spec.add_development_dependency "rake"
23
23
  spec.add_development_dependency "rspec"
24
- spec.add_development_dependency "pry"
25
24
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: union_find
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Imstepf
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-29 00:00:00.000000000 Z
11
+ date: 2014-07-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,20 +52,6 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: pry
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
55
  description: Union Find is an algorithm that uses a disjoint-set data structure. It
70
56
  allows us to efficiently connect any items of a given list and to efficiently check
71
57
  whether two items of this list are connected (any degree of separation) or not.