ralgorithms 0.2.1 → 0.3.0

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.
@@ -0,0 +1,135 @@
1
+ module Searching
2
+ class SkipList
3
+
4
+ class Node
5
+ attr_accessor :key
6
+ attr_accessor :value
7
+ attr_reader :forward
8
+ def initialize(max_level, key, value)
9
+ self.key = key
10
+ self.value = value
11
+ @forward = Array.new max_level
12
+ @forward.map!{|e| NIL_NODE}
13
+ end
14
+ end
15
+
16
+ class NilKey
17
+ def <(*args)
18
+ false
19
+ end
20
+ end
21
+
22
+ DEFAULT_MAX_LEVEL = 32
23
+ DEFAULT_PROBABILITY = 0.5
24
+ NIL_NODE = Node.new 0, NilKey.new, nil
25
+ attr_reader :size
26
+ attr_reader :current_level
27
+
28
+ def initialize(max_level=DEFAULT_MAX_LEVEL, probability=DEFAULT_PROBABILITY)
29
+ @max_level = max_level - 1
30
+ @probability = probability
31
+ @current_level = 0
32
+ @header = Node.new @max_level, nil, nil
33
+ @size = 0
34
+ end
35
+
36
+ def <<(value)
37
+ self[value] = value
38
+ end
39
+
40
+ def [](key)
41
+ element = find key
42
+ element.key == key ? element.value : nil
43
+ end
44
+
45
+ def []=(key, value)
46
+ update = []
47
+ element = find key, update
48
+ if element.key == key
49
+ element.value = value
50
+ else
51
+ target_level = random_level
52
+ if target_level > @current_level
53
+ ((@current_level+1)..target_level).each do |level|
54
+ update[level] = @header
55
+ end
56
+ @current_level = target_level
57
+ end
58
+ target_element = Node.new @max_level, key, value
59
+ (0..target_level).each do |level|
60
+ target_element.forward[level] = update[level].forward[level]
61
+ update[level].forward[level] = target_element
62
+ end
63
+ @size += 1
64
+ end
65
+ end
66
+
67
+ def delete(key)
68
+ update = []
69
+ element = find key, update
70
+ if element.key == key
71
+ (1..@current_level).each do |level|
72
+ update[level].forward[level] = element.forward[level] if update[level].forward[level] == element
73
+ @current_level -= 1 if @header.forward[level] == NIL_NODE
74
+ end
75
+ @size -= 1
76
+ end
77
+ end
78
+
79
+ def to_s
80
+ str = ""
81
+ @current_level.downto(0).each do |level|
82
+ str << "(#{level}): #{to_a(level).inspect}"
83
+ end
84
+ str
85
+ end
86
+
87
+ def to_a(level=0)
88
+ element = @header
89
+ result = []
90
+ while (forward = element.forward[level]) != NIL_NODE
91
+ element = forward
92
+ result << [element.key, element.value]
93
+ end
94
+ result
95
+ end
96
+
97
+ protected
98
+ def random_level
99
+ [(Math.log(1-rand)/Math.log(@probability)).to_i, @max_level].min
100
+ end
101
+
102
+ def find(key, update=nil)
103
+ element = @header
104
+ @current_level.downto(0).each do |level|
105
+ while (forward = element.forward[level]).key < key
106
+ element = forward
107
+ end
108
+ update[level] = element if update
109
+ end
110
+ element.forward[0]
111
+ end
112
+
113
+ end
114
+ end
115
+ if __FILE__ == $0
116
+ COUNT = 100_000
117
+ SEARCH_COUNT = 10000
118
+ s = Searching::SkipList.new
119
+ now = Time.now
120
+ COUNT.times{rand}
121
+ rand_cost = Time.now - now
122
+ now = Time.now
123
+ COUNT.times{s << rand}
124
+ insert_cost = Time.now - now - rand_cost
125
+ puts "SkipList: time cost for inserting #{COUNT} items: #{insert_cost.to_f*1000}ms"
126
+ samples = s.to_a.sample SEARCH_COUNT
127
+ now = Time.now
128
+ samples.each {|x| s[x[0]]}
129
+ search_cost = Time.now - now
130
+ puts "SkipList: time cost for searching #{SEARCH_COUNT} items from #{COUNT} items: #{search_cost.to_f*1000}ms"
131
+ now = Time.now
132
+ samples.each {|x| s.delete x[0]}
133
+ delete_cost = Time.now - now
134
+ puts "SkipList: time cost for deleting #{SEARCH_COUNT} items from #{COUNT} items: #{delete_cost.to_f*1000}ms"
135
+ end
@@ -0,0 +1,36 @@
1
+ require File.join(File.dirname(File.realpath(__FILE__)), "helper")
2
+ module Sorting
3
+ class BubbleSort
4
+ extend Helper
5
+ TEST_DATA_SIZE=1000
6
+
7
+ # Bubble Sort
8
+ # Comparison sort
9
+ # Exchanging
10
+ # Stable
11
+ # Time complexity: Ω(n), Ө(n2), O(n2)
12
+ # Space complexity: O(n) total, O(1) auxiliary
13
+ # Tiny code size
14
+ def self.sort!(data)
15
+ max_i = data.size - 1
16
+ (0...max_i).each do |i|
17
+ swapped = false
18
+ (0...(max_i-i)).each do |j|
19
+ if data[j] > data[j+1]
20
+ data[j], data[j+1] = data[j+1], data[j]
21
+ swapped = true
22
+ end
23
+ end
24
+ break if !swapped
25
+ end
26
+ nil
27
+ end
28
+
29
+ end
30
+ end
31
+
32
+ if __FILE__ == $0
33
+ require File.join(File.dirname(File.realpath(__FILE__)), "test_helper")
34
+ Sorting::TestHelper.test __FILE__
35
+ end
36
+
@@ -0,0 +1,65 @@
1
+ require File.join(File.dirname(File.realpath(__FILE__)), "helper")
2
+ module Sorting
3
+ class HeapSort
4
+ extend Helper
5
+ TEST_DATA_SIZE=100_000
6
+
7
+ class << self
8
+
9
+ # Heap sort
10
+ # Comparison sort
11
+ # Selection
12
+ # Unstable
13
+ # Time complexity: Ω(nlogn), Ө(nlogn), O(nlogn)
14
+ # Space complexity: O(n) total, O(1) auxiliary
15
+ # Strongly random access, poor locality of reference
16
+ def sort!(data, l=0, r=data.size-1)
17
+ build_max_heap data
18
+ r.downto(l).each do |i|
19
+ data[l], data[i] = data[i], data[l]
20
+ rebalance_max_heap data, l, l, i - 1
21
+ end
22
+ nil
23
+ end
24
+
25
+ protected
26
+
27
+ def build_max_heap(data, l=0, r=data.size-1)
28
+ get_parent(l, r).downto(l).each do |i|
29
+ rebalance_max_heap data, l, i, r
30
+ end
31
+ end
32
+
33
+ def rebalance_max_heap(data, start, i, last_i)
34
+ while (child = get_left_child(start, i)) <= last_i
35
+ if child < last_i #has two children
36
+ right_sibling = child + 1
37
+ child = right_sibling if data[right_sibling] > data[child]
38
+ end
39
+ if data[i] > data[child]
40
+ break
41
+ else
42
+ data[i], data[child] = data[child], data[i]
43
+ i = child
44
+ end
45
+ end
46
+ end
47
+
48
+ def get_parent(start, i)
49
+ i == start ? start : (i - start) / 2
50
+ end
51
+
52
+ def get_left_child(start, i)
53
+ (i-start)*2 + 1
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+ end
60
+
61
+
62
+ if __FILE__ == $0
63
+ require File.join(File.dirname(File.realpath(__FILE__)), "test_helper")
64
+ Sorting::TestHelper.test __FILE__
65
+ end
@@ -0,0 +1,11 @@
1
+ module Sorting
2
+ module Helper
3
+
4
+ def sort(data)
5
+ data = data.dup
6
+ self.sort! data
7
+ data
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,73 @@
1
+ require File.join(File.dirname(File.realpath(__FILE__)), "helper")
2
+ module Sorting
3
+ class InsertionSort
4
+ extend Helper
5
+ TEST_DATA_SIZE=1000
6
+ BINARY_SEARCH_THRESHOLD=80
7
+ BINARY_SEARCH_TEST_DISTANCE=[5, BINARY_SEARCH_THRESHOLD].min
8
+
9
+ # Insertion sort
10
+ # Comparison sort
11
+ # Insertion
12
+ # Stable
13
+ # Time complexity: Ω(n), Ө(n2), O(n2)
14
+ # Space complexity: O(n) total, O(1) auxiliary
15
+ # Simple, Adaptive, Online
16
+ # More efficient in practice than bubble sort or selection sort
17
+ # Write O(n2) times
18
+ def self.sort!(data, l=0, r=data.size-1, start=l+1)
19
+ (start..r).each do |i|
20
+ value = data[i]
21
+ j = i
22
+ while j > l && value < (previous = data[j-1])
23
+ data[j] = previous
24
+ j -= 1
25
+ end
26
+ data[j] = value
27
+ end
28
+ nil
29
+ end
30
+
31
+ def self.binary_sort!(data, l=0, r=data.size-1, start=l+1)
32
+ (start..r).each do |i|
33
+ value = data[i]
34
+ if (distance = i - l) > BINARY_SEARCH_THRESHOLD && value < data[i-BINARY_SEARCH_TEST_DISTANCE]
35
+ low = l
36
+ high = i-1
37
+ found = nil
38
+ while low <= high
39
+ mid = low + (high-low)/2
40
+ if data[mid] < value
41
+ low = mid + 1
42
+ else
43
+ found = mid
44
+ high = mid - 1
45
+ end
46
+ end
47
+ if found
48
+ length = i-found
49
+ data[found+1, length] = data[found,length]
50
+ data[found] = value
51
+ end
52
+ else
53
+ j = i
54
+ while j > l && value < (previous = data[j-1])
55
+ data[j] = previous
56
+ j -= 1
57
+ end
58
+ data[j] = value
59
+ end
60
+ end
61
+ nil
62
+ end
63
+
64
+
65
+ end
66
+ end
67
+
68
+ require File.join(File.dirname(File.realpath(__FILE__)), "load_ext.rb")
69
+
70
+ if __FILE__ == $0
71
+ require File.join(File.dirname(File.realpath(__FILE__)), "test_helper")
72
+ Sorting::TestHelper.test __FILE__
73
+ end
@@ -0,0 +1,72 @@
1
+ require File.join(File.dirname(File.realpath(__FILE__)), "heap_sort")
2
+ require File.join(File.dirname(File.realpath(__FILE__)), "insertion_sort")
3
+ require File.join(File.dirname(File.realpath(__FILE__)), "helper")
4
+ module Sorting
5
+ class IntroSort
6
+ extend Helper
7
+ TEST_DATA_SIZE=100_000
8
+ SIZE_FOR_INSERTION=12
9
+
10
+ # Introsort or introspective sort
11
+ # Comparison sort
12
+ # Partitioning & Selection
13
+ # Unstable
14
+ # Time complexity: O(nlogn), Ө(nlogn), O(nlogn)
15
+ # Space complexity: O(logn)
16
+ # Support parallelization
17
+ # Better to median-of-3 killer
18
+ def self.sort!(data)
19
+ introsort(data)
20
+ nil
21
+ end
22
+
23
+ protected
24
+
25
+ def self.introsort(data, l=0, r=data.size-1, depth_limit=2*Math.log((r-l), 2).to_i)
26
+ while (distance = (r - l)) > 0
27
+ if distance < SIZE_FOR_INSERTION
28
+ Sorting::InsertionSort.sort! data, l, r
29
+ return
30
+ elsif depth_limit == 0
31
+ Sorting::HeapSort.sort! data, l, r
32
+ return
33
+ else
34
+ depth_limit -= 1
35
+ q = partition(data, l, r)
36
+ introsort(data, q+1, r, depth_limit)
37
+ r = q - 1
38
+ end
39
+ end
40
+ end
41
+
42
+ def self.partition(data, p, r)
43
+ median_of_3 data, p, r
44
+ pivot = data[r]
45
+ i = p - 1
46
+ p.upto(r-1) do |j|
47
+ if data[j] <= pivot
48
+ i = i+1
49
+ data[i], data[j] = data[j],data[i]
50
+ end
51
+ end
52
+ data[i+1],data[r] = data[r],data[i+1]
53
+ return i + 1
54
+ end
55
+
56
+ def self.median_of_3(data, p, r)
57
+ m = p + (r-p)/2
58
+ pivot_candidates_hash = {data[p] => p, data[m] => m, data[r] => r}
59
+ pivot_candidates = pivot_candidates_hash.keys
60
+ pivot = (pivot_candidates - pivot_candidates.minmax)[0] || pivot_candidates.last
61
+ pivot_index = pivot_candidates_hash[pivot]
62
+ data[pivot_index], data[r] = data[r], pivot if pivot_index != r
63
+ end
64
+
65
+ end
66
+ end
67
+
68
+ if __FILE__ == $0
69
+ require File.join(File.dirname(File.realpath(__FILE__)), "test_helper")
70
+ Sorting::TestHelper.test __FILE__
71
+ end
72
+
@@ -0,0 +1,80 @@
1
+ require File.join(File.dirname(File.realpath(__FILE__)), "helper")
2
+ require File.join(File.dirname(File.realpath(__FILE__)), "insertion_sort")
3
+ module Sorting
4
+ class LibrarySort
5
+ extend Helper
6
+ TEST_DATA_SIZE=100_000
7
+
8
+ GAP = 8
9
+
10
+ # Library sort
11
+ # Comparison sort
12
+ # Insertion
13
+ # Stable
14
+ # Time complexity: Ω(n), Ө(nlogn), O(n2)
15
+ # Space complexity: O(n)
16
+ # Very fast, Adaptive, Online
17
+ # Requires extra space
18
+ def self.sort(data)
19
+ sorting_data = Array.new data.size
20
+ steps = build_steps data
21
+ step_counts = {}
22
+ data.each do |e|
23
+ step = binary_search_first_bigger_value steps, e
24
+ step_counts[step] = (step_counts[step] || 0) + 1
25
+ end
26
+ step_locations = {}
27
+ last_location = 0
28
+ last_step_item_count = 0
29
+ steps.each do |step|
30
+ last_location += last_step_item_count
31
+ step_locations[step] = last_location
32
+ last_step_item_count = step_counts[step]
33
+ end
34
+ data.each do |e|
35
+ step = binary_search_first_bigger_value steps, e
36
+ sorting_data[step_locations[step]] = e
37
+ step_locations[step] += 1
38
+ end
39
+ InsertionSort.sort! sorting_data
40
+ sorting_data
41
+ end
42
+
43
+ protected
44
+
45
+ def self.build_steps(data)
46
+ min, max = data.minmax
47
+ gap = (GAP * (max - min + 1).to_f/data.size).to_i
48
+ steps = []
49
+ step = min - 1
50
+ while step < max
51
+ step += gap
52
+ steps << step
53
+ end
54
+ steps
55
+ end
56
+
57
+ def self.binary_search_first_bigger_value(data, item)
58
+ low = 0
59
+ high = data.size - 1
60
+ found = nil
61
+ while low <= high
62
+ mid = low + (high-low)/2
63
+ value = data[mid]
64
+ if value < item
65
+ low = mid + 1
66
+ else
67
+ found = value
68
+ high = mid - 1
69
+ end
70
+ end
71
+ found
72
+ end
73
+
74
+ end
75
+ end
76
+
77
+ if __FILE__ == $0
78
+ require File.join(File.dirname(File.realpath(__FILE__)), "test_helper")
79
+ Sorting::TestHelper.test __FILE__
80
+ end