union_find 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3d86fb59ebdb3b6b5f55e175db95bd7dccf0126b
4
+ data.tar.gz: 8dfbcfdff8f2a1f20e808df6404312bbd2793967
5
+ SHA512:
6
+ metadata.gz: fee3e76ee13f5c64e8b8f278e8c42ac0ebb979603bc3b1e19b4e76980ba8128c9c1fd594c31d04006f61149afbf86b364cd71d0f59a2206aa7140d3350f13096
7
+ data.tar.gz: 280542a0d35dc79eb7343f3efd350e8ea8629a47377cb9b04ae58076b9e3da45f983ffa5ce522518515bbef974746a097b16dfd8b87db09f8b93bbb1928b98b5
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .ruby-gemset
24
+ .ruby-version
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in union_find.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Michael Imstepf
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # Weighted quick-union algorithm with path compression
2
+
3
+ Union Find is an algorithm that uses a disjoint-set data structure. It allows us to efficiently connect any items of a given list and to efficiently check whether two items of this list are connected (any degree of separation) or not.
4
+
5
+ Possible applications where we might want to find out whether two items are connected to each other are:
6
+ * Social networks
7
+ * Computers in a network
8
+ * Web pages on the Internet
9
+ * Transistors in a computer chip
10
+ * Pixels in a digital photo
11
+ * Metallic sites in a composite system
12
+
13
+ Click [here](https://www.cs.princeton.edu/~rs/AlgsDS07/01UnionFind.pdf) for more information.
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.
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ gem 'union_find'
24
+
25
+ And then execute:
26
+
27
+ $ bundle
28
+
29
+ Or install it yourself as:
30
+
31
+ $ gem install union_find
32
+
33
+ ## Usage
34
+
35
+ Create a new instance of `UnionFind` and pass in an array of items:
36
+
37
+ ```ruby
38
+ union_find = UnionFind::UnionFind.new(['Grandfather', 'Father', 'Daughter', 'Single Person'])
39
+ ```
40
+
41
+ Connect items (in any order):
42
+
43
+ ```ruby
44
+ union_find.union('Grandfather', 'Father')
45
+ union_find.union('Father', 'Daughter')
46
+ ```
47
+
48
+ Check whether two items are connected (in any order):
49
+
50
+ ```ruby
51
+ union_find.connected?('Grandfather', 'Daughter')
52
+ => true
53
+ union_find.connected?('Daughter', 'Father')
54
+ => true
55
+ union_find.connected?('Grandfather', 'Single Person')
56
+ => false
57
+ ```
58
+
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:
60
+
61
+ ```ruby
62
+ union_find.count_isolated_components
63
+ => 2
64
+ ```
65
+
66
+ ## Contributing
67
+
68
+ 1. Fork it ( https://github.com/[my-github-username]/union_find/fork )
69
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
70
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
71
+ 4. Push to the branch (`git push origin my-new-feature`)
72
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,3 @@
1
+ module UnionFind
2
+ VERSION = "0.0.1"
3
+ end
data/lib/union_find.rb ADDED
@@ -0,0 +1,106 @@
1
+ require 'union_find/version'
2
+
3
+ module UnionFind
4
+
5
+ # The UnionFind class represents a union-find data type
6
+ # (also known as the disjoint-sets data type).
7
+ # It supports the union and find operations,
8
+ # along with a connected operation for determinig whether
9
+ # two sites in the same component are connected and a count operation that
10
+ # returns the total number of components.
11
+
12
+ # This implementation uses weighted quick union by rank with path compression
13
+ # by halving.
14
+
15
+ # Initializing a data structure with number_of_components sites takes linear time.
16
+ # Afterwards, the union, find, and connected
17
+ # operations take logarithmic time (in the worst case) and the
18
+ # count operation takes constant time.
19
+
20
+ # @author Robert Sedgewick
21
+ # @author Kevin Wayne
22
+ # @author Michael Imstepf
23
+ # @see http://algs4.cs.princeton.edu/15uf/UF.java.html
24
+ # @see http://algs4.cs.princeton.edu/15uf/
25
+ class UnionFind
26
+
27
+ # Initializes an empty union-find data structure with
28
+ # n isolated components 0 through n-1.
29
+ # @param components [Array] components
30
+ # @raise [ArgumentError] if components.length < 1 or if components is not an Array
31
+ def initialize(components)
32
+ raise ArgumentError, 'input is not an Array' unless components.class == Array
33
+
34
+ components = components.uniq # remove duplicates
35
+ @number_of_isolated_components = components.length
36
+
37
+ raise ArgumentError, 'number of components is < 1' if @number_of_isolated_components < 1
38
+
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
44
+ end
45
+ end
46
+
47
+ # Returns the number of isolated components.
48
+ # @return [Interger] the number of components
49
+ def count_isolated_components
50
+ @number_of_isolated_components
51
+ end
52
+
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
+ # Connect root of component 1 with root of component 2
69
+ # by attaching smaller subtree root node with larger tree.
70
+ # If both trees have the same size, the root of the second
71
+ # 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
74
+ # @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
+ def union(component_1, component_2)
76
+ root_component_1 = find_root(component_1)
77
+ root_component_2 = find_root(component_2)
78
+
79
+ return nil if root_component_1 == root_component_2
80
+
81
+ # make smaller root point to larger one
82
+ if @tree_size[root_component_1] < @tree_size[root_component_2]
83
+ @parent[root_component_1] = root_component_2
84
+ root = root_component_2
85
+ @tree_size[root_component_2] += @tree_size[root_component_1]
86
+ else
87
+ @parent[root_component_2] = root_component_1
88
+ root = root_component_1
89
+ @tree_size[root_component_1] += @tree_size[root_component_2]
90
+ end
91
+
92
+ @number_of_isolated_components -= 1
93
+
94
+ root
95
+ end
96
+
97
+ # 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
100
+ # @return [Boolean]
101
+ def connected?(component_1, component_2)
102
+ find_root(component_1) == find_root(component_2)
103
+ end
104
+ end
105
+
106
+ end
@@ -0,0 +1,23 @@
1
+ require 'union_find'
2
+ require 'pry' # to use binding.pry
3
+
4
+ RSpec.configure do |config|
5
+ # Run specs in random order to surface order dependencies. If you find an
6
+ # order dependency and want to debug it, you can fix the order by providing
7
+ # the seed, which is printed after each run.
8
+ # --seed 1234
9
+ config.order = 'random'
10
+
11
+ # 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
13
+ config.run_all_when_everything_filtered = true
14
+ end
15
+
16
+ def create_family_tree(union_find)
17
+ union_find.union('Grandfather', 'Father')
18
+ union_find.union('Grandfather', 'Mother')
19
+ union_find.union('Mother', 'Son')
20
+ union_find.union('Father', 'Daughter')
21
+
22
+ union_find
23
+ end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+ random = Random.new
3
+
4
+ describe UnionFind::UnionFind do
5
+ # 1 family and 1 single person
6
+ people = ['Grandfather', 'Father', 'Mother', 'Son', 'Daughter', 'Single']
7
+
8
+ describe '#initialize' do
9
+ context 'when no components are provided' do
10
+ it 'raises an exception' do
11
+ expect {UnionFind::UnionFind.new()}.to raise_exception(ArgumentError)
12
+ end
13
+ end
14
+
15
+ context 'when components in form other than Array are provided' do
16
+ it 'raises an exception' do
17
+ expect {UnionFind::UnionFind.new('Some Person')}.to raise_exception(ArgumentError)
18
+ end
19
+ end
20
+ end
21
+
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
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '#union' do
49
+ context 'when one component gets connected to itself' do
50
+ union_find = UnionFind::UnionFind.new(people)
51
+
52
+ it 'returns the component' do
53
+ expect(union_find.union('Grandfather', 'Grandfather')).to be_nil
54
+ end
55
+ end
56
+
57
+ context 'when one unconnected component gets connected to another unconnected component' do
58
+ union_find = UnionFind::UnionFind.new(people)
59
+
60
+ it 'returns the first component' do
61
+ expect(union_find.union('Grandfather', 'Father')).to eq 'Grandfather'
62
+ end
63
+ end
64
+
65
+ context 'when one unconnected component gets connected to a connected component' do
66
+ union_find = UnionFind::UnionFind.new(people)
67
+ create_family_tree(union_find)
68
+
69
+ it 'connects and returns the root of the larger tree' do
70
+ 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'
74
+ end
75
+ end
76
+ end
77
+
78
+ describe '#connected?' do
79
+ union_find = UnionFind::UnionFind.new(people)
80
+ create_family_tree(union_find)
81
+
82
+ context 'when two components are not connected' do
83
+ it 'returns false' do
84
+ expect(union_find.connected?('Father', 'Single')).to be_falsey
85
+ end
86
+ end
87
+
88
+ context 'when two components are the same' do
89
+ it 'returns false' do
90
+ expect(union_find.connected?('Father', 'Father')).to be_truthy
91
+ end
92
+ end
93
+
94
+ context 'when two components are connected' do
95
+ it 'returns false' do
96
+ expect(union_find.connected?('Grandfather', 'Daughter')).to be_truthy
97
+ end
98
+ end
99
+ end
100
+
101
+ describe '#count_isolated_components' do
102
+ context 'when no connections have been made' do
103
+ union_find = UnionFind::UnionFind.new(people)
104
+
105
+ it 'returns number of components' do
106
+ expect(union_find.count_isolated_components).to eq people.size
107
+ end
108
+ end
109
+
110
+ context 'when connections have been made' do
111
+ union_find = UnionFind::UnionFind.new(people)
112
+ create_family_tree(union_find)
113
+
114
+ it 'returns number of components' do
115
+ expect(union_find.count_isolated_components).to eq 2
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'union_find/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "union_find"
8
+ spec.version = UnionFind::VERSION
9
+ spec.authors = ["Michael Imstepf"]
10
+ spec.email = ["michael.imstepf@gmail.com"]
11
+ spec.summary = %q{Weighted quick-union algorithm with path compression.}
12
+ spec.description = %q{Union Find is an algorithm that uses a disjoint-set data structure. It allows us to efficiently connect any items of a given list and to efficiently check whether two items of this list are connected (any degree of separation) or not.}
13
+ spec.homepage = "https://github.com/michaelimstepf/union-find"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "pry"
25
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: union_find
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Michael Imstepf
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
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
+ description: Union Find is an algorithm that uses a disjoint-set data structure. It
70
+ allows us to efficiently connect any items of a given list and to efficiently check
71
+ whether two items of this list are connected (any degree of separation) or not.
72
+ email:
73
+ - michael.imstepf@gmail.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".gitignore"
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - lib/union_find.rb
84
+ - lib/union_find/version.rb
85
+ - spec/spec_helper.rb
86
+ - spec/union_find_spec.rb
87
+ - union_find.gemspec
88
+ homepage: https://github.com/michaelimstepf/union-find
89
+ licenses:
90
+ - MIT
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.2.2
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Weighted quick-union algorithm with path compression.
112
+ test_files:
113
+ - spec/spec_helper.rb
114
+ - spec/union_find_spec.rb