union_find 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +29 -6
- data/lib/union_find.rb +58 -29
- data/lib/union_find/version.rb +1 -1
- data/spec/spec_helper.rb +1 -2
- data/spec/union_find_spec.rb +21 -29
- data/union_find.gemspec +0 -1
- metadata +2 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92740adf9b4c5f2dc335cbdfdc6bf7b2d938f89b
|
4
|
+
data.tar.gz: b7b1940afc61cc57b3145f0305086e72f53061b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
33
|
+
Create a new instance of `UnionFind` and pass in a `Set` of items:
|
36
34
|
|
37
35
|
```ruby
|
38
|
-
|
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
|
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 )
|
data/lib/union_find.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
40
|
-
@tree_size = {} # size of tree rooted at
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
73
|
-
# @param
|
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 [
|
99
|
-
# @param component_2 [
|
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
|
data/lib/union_find/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -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
|
11
|
+
config.filter_run :focus
|
13
12
|
config.run_all_when_everything_filtered = true
|
14
13
|
end
|
15
14
|
|
data/spec/union_find_spec.rb
CHANGED
@@ -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 '#
|
23
|
-
union_find = UnionFind::UnionFind.new(people)
|
24
|
-
|
25
|
-
context 'when component does not exist' do
|
26
|
-
it '
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
data/union_find.gemspec
CHANGED
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.
|
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-
|
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.
|