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.
- data/ext/sorting/extconf.rb +8 -0
- data/ext/sorting/sorting_common.c +401 -0
- data/ext/sorting/sorting_ext.c +112 -0
- data/lib/searching/skip_list.rb +135 -0
- data/lib/sorting/bubble_sort.rb +36 -0
- data/lib/sorting/heap_sort.rb +65 -0
- data/lib/sorting/helper.rb +11 -0
- data/lib/sorting/insertion_sort.rb +73 -0
- data/lib/sorting/intro_sort.rb +72 -0
- data/lib/sorting/library_sort.rb +80 -0
- data/lib/sorting/load_ext.rb +5 -0
- data/lib/sorting/merge_sort.rb +74 -0
- data/lib/sorting/quick_sort.rb +67 -0
- data/lib/sorting/selection_sort.rb +33 -0
- data/lib/sorting/shell_sort.rb +45 -0
- data/lib/sorting/smooth_sort.rb +172 -0
- data/lib/sorting/test_helper.rb +90 -0
- data/lib/sorting/tim_sort.rb +21 -0
- metadata +21 -2
@@ -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,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
|