skiplist 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.
- data/History.txt +3 -0
- data/Manifest.txt +14 -0
- data/README.rdoc +49 -0
- data/Rakefile +29 -0
- data/ext/skiplist/extconf.rb +3 -0
- data/ext/skiplist/skiplist_mlink.c +150 -0
- data/lib/skiplist.rb +375 -0
- data/lib/skiplist/loop.rb +35 -0
- data/lib/skiplist/sentinelelement.rb +66 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/test_helper.rb +3 -0
- data/test/test_skiplist.rb +11 -0
- metadata +110 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.rdoc
|
4
|
+
Rakefile
|
5
|
+
ext/skiplist/extconf.rb
|
6
|
+
ext/skiplist/skiplist_mlink.c
|
7
|
+
lib/skiplist.rb
|
8
|
+
lib/skiplist/loop.rb
|
9
|
+
lib/skiplist/sentinelelement.rb
|
10
|
+
script/console
|
11
|
+
script/destroy
|
12
|
+
script/generate
|
13
|
+
test/test_helper.rb
|
14
|
+
test/test_skiplist.rb
|
data/README.rdoc
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
= skiplist
|
2
|
+
|
3
|
+
* http://github.com/metanest/ruby-skiplist
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Lock-free skip list implementation by ruby.
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* FIX (list of features or problems)
|
12
|
+
|
13
|
+
== SYNOPSIS:
|
14
|
+
|
15
|
+
FIX (code sample of usage)
|
16
|
+
|
17
|
+
== REQUIREMENTS:
|
18
|
+
|
19
|
+
* ruby 1.9
|
20
|
+
|
21
|
+
== INSTALL:
|
22
|
+
|
23
|
+
* (sudo) gem install
|
24
|
+
|
25
|
+
== LICENSE:
|
26
|
+
|
27
|
+
(other than loop.rb )
|
28
|
+
(The MIT License)
|
29
|
+
|
30
|
+
Copyright (c) 2010 KISHIMOTO, Makoto
|
31
|
+
|
32
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
33
|
+
a copy of this software and associated documentation files (the
|
34
|
+
'Software'), to deal in the Software without restriction, including
|
35
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
36
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
37
|
+
permit persons to whom the Software is furnished to do so, subject to
|
38
|
+
the following conditions:
|
39
|
+
|
40
|
+
The above copyright notice and this permission notice shall be
|
41
|
+
included in all copies or substantial portions of the Software.
|
42
|
+
|
43
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
44
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
45
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
46
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
47
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
48
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
49
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require './lib/skiplist'
|
6
|
+
|
7
|
+
Hoe.plugin :newgem
|
8
|
+
# Hoe.plugin :website
|
9
|
+
# Hoe.plugin :cucumberfeatures
|
10
|
+
|
11
|
+
# Generate all the Rake tasks
|
12
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
13
|
+
$hoe = Hoe.spec 'skiplist' do
|
14
|
+
self.developer 'KISHIMOTO, Makoto', 'ksmakoto@dd.iij4u.or.jp'
|
15
|
+
# self.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
|
16
|
+
self.rubyforge_name = self.name # TODO this is default value
|
17
|
+
# self.extra_deps = [['activesupport','>= 2.0.2']]
|
18
|
+
|
19
|
+
self.spec_extras = {
|
20
|
+
:extensions => ['ext/skiplist/extconf.rb'],
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'newgem/tasks'
|
25
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
26
|
+
|
27
|
+
# TODO - want other tests/tasks run by default? Add them to the list
|
28
|
+
# remove_task :default
|
29
|
+
# task :default => [:spec, :features]
|
@@ -0,0 +1,150 @@
|
|
1
|
+
/* vi:set ts=3 sw=3:
|
2
|
+
* vim:set sts=0 noet:
|
3
|
+
*/
|
4
|
+
/*
|
5
|
+
* Copyright (c) 2010 KISHIMOTO, Makoto
|
6
|
+
*
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
12
|
+
* furnished to do so, subject to the following conditions:
|
13
|
+
*
|
14
|
+
* The above copyright notice and this permission notice shall be included in
|
15
|
+
* all copies or substantial portions of the Software.
|
16
|
+
*
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23
|
+
* THE SOFTWARE.
|
24
|
+
*/
|
25
|
+
#include <ruby.h>
|
26
|
+
|
27
|
+
static __inline__ int
|
28
|
+
cas(VALUE *addr, VALUE oldp, VALUE newp)
|
29
|
+
{
|
30
|
+
return __sync_bool_compare_and_swap(addr, oldp, newp);
|
31
|
+
}
|
32
|
+
|
33
|
+
struct mlink {
|
34
|
+
VALUE link;
|
35
|
+
};
|
36
|
+
|
37
|
+
static void
|
38
|
+
mlink_mark(struct mlink *lnkp)
|
39
|
+
{
|
40
|
+
VALUE p = lnkp->link & ~1;
|
41
|
+
|
42
|
+
rb_gc_mark(p);
|
43
|
+
}
|
44
|
+
|
45
|
+
static VALUE
|
46
|
+
mlink_alloc(VALUE klass)
|
47
|
+
{
|
48
|
+
struct mlink *lnkp;
|
49
|
+
VALUE obj = Data_Make_Struct(klass, struct mlink, mlink_mark, -1, lnkp);
|
50
|
+
|
51
|
+
lnkp->link = 0;
|
52
|
+
|
53
|
+
return obj;
|
54
|
+
}
|
55
|
+
|
56
|
+
static VALUE
|
57
|
+
mlink_initialize(VALUE obj, VALUE link)
|
58
|
+
{
|
59
|
+
struct mlink *lnkp;
|
60
|
+
|
61
|
+
if (link & 1) {
|
62
|
+
rb_raise(rb_eArgError, "link must not be a Fixnum");
|
63
|
+
}
|
64
|
+
|
65
|
+
Data_Get_Struct(obj, struct mlink, lnkp);
|
66
|
+
|
67
|
+
lnkp->link = link;
|
68
|
+
|
69
|
+
return Qnil;
|
70
|
+
}
|
71
|
+
|
72
|
+
static VALUE
|
73
|
+
mlink_compare_and_set(VALUE obj, VALUE oldlink, VALUE newlink, VALUE oldmark, VALUE newmark)
|
74
|
+
{
|
75
|
+
struct mlink *lnkp;
|
76
|
+
VALUE olink, nlink;
|
77
|
+
|
78
|
+
if ((oldlink & 1) || (newlink & 1)) {
|
79
|
+
rb_raise(rb_eArgError, "link must not be a Fixnum");
|
80
|
+
}
|
81
|
+
if (((oldmark != Qfalse) && (oldmark != Qtrue)) ||
|
82
|
+
((newmark != Qfalse) && (newmark != Qtrue))) {
|
83
|
+
rb_raise(rb_eArgError, "mark must be a boolean");
|
84
|
+
}
|
85
|
+
|
86
|
+
Data_Get_Struct(obj, struct mlink, lnkp);
|
87
|
+
|
88
|
+
olink = oldlink | !!oldmark;
|
89
|
+
nlink = newlink | !!newmark;
|
90
|
+
|
91
|
+
return cas(&lnkp->link, olink, nlink) ? Qtrue : Qfalse ;
|
92
|
+
}
|
93
|
+
|
94
|
+
static VALUE
|
95
|
+
mlink_get(VALUE obj)
|
96
|
+
{
|
97
|
+
struct mlink *lnkp;
|
98
|
+
VALUE link, mark;
|
99
|
+
|
100
|
+
Data_Get_Struct(obj, struct mlink, lnkp);
|
101
|
+
|
102
|
+
link = lnkp->link;
|
103
|
+
mark = link & 1 ? Qtrue : Qfalse ;
|
104
|
+
link &= ~1;
|
105
|
+
|
106
|
+
return rb_ary_new3(2, link, mark);
|
107
|
+
}
|
108
|
+
|
109
|
+
static VALUE
|
110
|
+
mlink_get_link(VALUE obj)
|
111
|
+
{
|
112
|
+
struct mlink *lnkp;
|
113
|
+
|
114
|
+
Data_Get_Struct(obj, struct mlink, lnkp);
|
115
|
+
|
116
|
+
return lnkp->link & ~1;
|
117
|
+
}
|
118
|
+
|
119
|
+
static VALUE
|
120
|
+
mlink_print_debug(VALUE obj)
|
121
|
+
{
|
122
|
+
VALUE link_mark, arg[1], link, mark;
|
123
|
+
|
124
|
+
link_mark = mlink_get(obj);
|
125
|
+
arg[0] = INT2FIX(0);
|
126
|
+
link = rb_ary_aref(1, arg, link_mark);
|
127
|
+
arg[0] = INT2FIX(1);
|
128
|
+
mark = rb_ary_aref(1, arg, link_mark);
|
129
|
+
|
130
|
+
rb_funcall(obj, rb_intern("puts"), 1,
|
131
|
+
rb_funcall(rb_str_new2("#<MLink: @mark = %s, @link = 0x%014x>"), rb_intern("%"), 1,
|
132
|
+
rb_ary_new3(2, mark, rb_funcall(link, rb_intern("object_id"), 0)) ));
|
133
|
+
|
134
|
+
return Qnil;
|
135
|
+
}
|
136
|
+
|
137
|
+
void
|
138
|
+
Init_skiplist_mlink(void)
|
139
|
+
{
|
140
|
+
VALUE cSkipList = rb_define_class("SkipList", rb_cObject);
|
141
|
+
VALUE cMLink = rb_define_class_under(cSkipList, "MLink", rb_cObject);
|
142
|
+
|
143
|
+
rb_define_alloc_func(cMLink, &mlink_alloc);
|
144
|
+
|
145
|
+
rb_define_method(cMLink, "initialize", &mlink_initialize, 1);
|
146
|
+
rb_define_method(cMLink, "compare_and_set", &mlink_compare_and_set, 4);
|
147
|
+
rb_define_method(cMLink, "get", &mlink_get, 0);
|
148
|
+
rb_define_method(cMLink, "get_link", &mlink_get_link, 0);
|
149
|
+
rb_define_method(cMLink, "print_debug", &mlink_print_debug, 0);
|
150
|
+
}
|
data/lib/skiplist.rb
ADDED
@@ -0,0 +1,375 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
# vi:set ts=3 sw=3:
|
3
|
+
# vim:set sts=0 noet:
|
4
|
+
|
5
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
6
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
7
|
+
|
8
|
+
require "skiplist/loop"
|
9
|
+
require "skiplist/sentinelelement"
|
10
|
+
require "skiplist/skiplist_mlink"
|
11
|
+
|
12
|
+
=begin
|
13
|
+
Copyright (c) 2010 KISHIMOTO, Makoto
|
14
|
+
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
16
|
+
of this software and associated documentation files (the "Software"), to deal
|
17
|
+
in the Software without restriction, including without limitation the rights
|
18
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
19
|
+
copies of the Software, and to permit persons to whom the Software is
|
20
|
+
furnished to do so, subject to the following conditions:
|
21
|
+
|
22
|
+
The above copyright notice and this permission notice shall be included in
|
23
|
+
all copies or substantial portions of the Software.
|
24
|
+
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
26
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
27
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
28
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
29
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
30
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
31
|
+
THE SOFTWARE.
|
32
|
+
=end
|
33
|
+
|
34
|
+
#
|
35
|
+
#= Lock-Free Skip List
|
36
|
+
#
|
37
|
+
#Authors:: KISHIMOTO, Makoto
|
38
|
+
#Version:: 0.0.1 2010-Aug-13
|
39
|
+
#Copyright:: Copyright (c) 2010 KISHIMOTO, Makoto
|
40
|
+
#License:: (other than loop.rb ) X License
|
41
|
+
#
|
42
|
+
#=== References
|
43
|
+
#
|
44
|
+
#- The Art of Multiprocessor Programming, Chap. 14
|
45
|
+
#
|
46
|
+
class SkipList
|
47
|
+
VERSION = '0.0.1'
|
48
|
+
|
49
|
+
#
|
50
|
+
# Node of SkipList, inner use only
|
51
|
+
#
|
52
|
+
class Node
|
53
|
+
attr_accessor :toplevel, :key, :val
|
54
|
+
|
55
|
+
def initialize toplevel, key, val
|
56
|
+
@toplevel = toplevel
|
57
|
+
@key = key
|
58
|
+
@val = val
|
59
|
+
@links = Array.new(@toplevel + 1)
|
60
|
+
end
|
61
|
+
|
62
|
+
def print_debug
|
63
|
+
puts "object_id = 0x%014x" % [object_id]
|
64
|
+
puts "@toplevel = #{@toplevel}"
|
65
|
+
puts "@key = #{@key}"
|
66
|
+
puts "@val = #{@val}"
|
67
|
+
puts "@links = "
|
68
|
+
@links.each_index{|i|
|
69
|
+
print "[#{i}] "
|
70
|
+
@links[i].print_debug
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def [] level
|
75
|
+
@links[level]
|
76
|
+
end
|
77
|
+
|
78
|
+
def []= level, node
|
79
|
+
@links[level] = node
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
attr_reader :size
|
84
|
+
|
85
|
+
#
|
86
|
+
# Create a SkipList object.
|
87
|
+
#
|
88
|
+
# level_max :: If this list will have approximately N elements, you sholud set this log_2 N.
|
89
|
+
#
|
90
|
+
def initialize level_max, cmp_op=:<=>, max_element=nil
|
91
|
+
if level_max < 0 then
|
92
|
+
raise ArgumentError.new "level_max must not be negative"
|
93
|
+
end
|
94
|
+
@level_max = level_max
|
95
|
+
@cmp_op = cmp_op
|
96
|
+
max = if max_element then max_element else SentinelElement::MAX end
|
97
|
+
@tail = Node.new @level_max, max, nil
|
98
|
+
(0).upto(@level_max){|i|
|
99
|
+
@tail[i] = MLink.new nil
|
100
|
+
}
|
101
|
+
@head = Node.new @level_max, nil, nil
|
102
|
+
(0).upto(@level_max){|i|
|
103
|
+
@head[i] = MLink.new @tail
|
104
|
+
}
|
105
|
+
@randgen = Random.new
|
106
|
+
@size_lock = Mutex.new
|
107
|
+
@size = 0
|
108
|
+
end
|
109
|
+
|
110
|
+
# for debug use
|
111
|
+
def print_debug
|
112
|
+
puts "@level_max = #{@level_max}"
|
113
|
+
puts "@cmp_op = #{@cmp_op}"
|
114
|
+
puts "@randgen = #{@randgen}"
|
115
|
+
puts "@size = #{@size}"
|
116
|
+
puts ""
|
117
|
+
puts "Nodes"
|
118
|
+
puts ""
|
119
|
+
p = @head
|
120
|
+
while p do
|
121
|
+
p.print_debug
|
122
|
+
puts ""
|
123
|
+
p = p[0].get_link
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# this returns one of 0..ex, approx of 0 is 1/2, 1 is 1/4, 2 is 1/8, ...
|
128
|
+
def rand2exp ex
|
129
|
+
mask = 1 << (ex + 1)
|
130
|
+
r = 1 + @randgen.rand(mask - 1)
|
131
|
+
mask >>= 1
|
132
|
+
i = 0
|
133
|
+
while r & mask == 0 do
|
134
|
+
mask >>= 1
|
135
|
+
i += 1
|
136
|
+
end
|
137
|
+
i
|
138
|
+
end
|
139
|
+
private :rand2exp
|
140
|
+
|
141
|
+
#
|
142
|
+
# call-seq:
|
143
|
+
# lst.empty? -> true or false
|
144
|
+
#
|
145
|
+
# Returns <code>true</code> if <i>lst</i> contains no elements.
|
146
|
+
#
|
147
|
+
def empty?
|
148
|
+
@head[0].get_link.equal? @tail
|
149
|
+
end
|
150
|
+
|
151
|
+
#
|
152
|
+
# call-seq:
|
153
|
+
# lst.to_a -> array
|
154
|
+
#
|
155
|
+
# Converts <i>lst</i> to a array of <code>[</code> <i>key, value</i>
|
156
|
+
# <code>]</code> arrays.
|
157
|
+
#
|
158
|
+
# lst = SkipList.new 5
|
159
|
+
# lst["foo"] = 1
|
160
|
+
# lst["bar"] = 2
|
161
|
+
# lst["baz"] = 3
|
162
|
+
# lst.to_a #=> [["bar", 2], ["baz", 3], ["foo", 1]]
|
163
|
+
#
|
164
|
+
def to_a
|
165
|
+
arr = []
|
166
|
+
p, m = @head[0].get
|
167
|
+
while p do
|
168
|
+
if not m then
|
169
|
+
arr.push [p.key, p.val]
|
170
|
+
end
|
171
|
+
p, m = p[0].get
|
172
|
+
end
|
173
|
+
arr.pop
|
174
|
+
arr
|
175
|
+
end
|
176
|
+
|
177
|
+
# for inner use
|
178
|
+
def find key, before_list, after_list
|
179
|
+
Loop.loop {|tag|
|
180
|
+
pp = nil
|
181
|
+
p = @head
|
182
|
+
@level_max.downto(0){|level|
|
183
|
+
pp = p[level].get_link
|
184
|
+
loop {
|
185
|
+
ppp, mark = pp[level].get
|
186
|
+
while mark do
|
187
|
+
snip = p[level].compare_and_set pp, ppp, false, false
|
188
|
+
unless snip then
|
189
|
+
tag.next
|
190
|
+
end
|
191
|
+
#pp = ppp # ?
|
192
|
+
pp = p[level].get_link
|
193
|
+
ppp, mark = pp[level].get
|
194
|
+
end
|
195
|
+
if pp.key < key then
|
196
|
+
p = pp
|
197
|
+
pp = ppp
|
198
|
+
else
|
199
|
+
break
|
200
|
+
end
|
201
|
+
}
|
202
|
+
before_list[level] = p
|
203
|
+
after_list[level] = pp
|
204
|
+
}
|
205
|
+
if pp.key == key then
|
206
|
+
tag.break pp
|
207
|
+
else
|
208
|
+
tag.break nil
|
209
|
+
end
|
210
|
+
}
|
211
|
+
end
|
212
|
+
private :find
|
213
|
+
|
214
|
+
#
|
215
|
+
# call-seq:
|
216
|
+
# lst[key] = val -> val
|
217
|
+
#
|
218
|
+
# Insert new node that key is <i>key</i> and value is <i>val</i>.
|
219
|
+
# If already exist the node with key == <i>key</i>, assign new <i>val</i>.
|
220
|
+
# This method returns <i>val</i>.
|
221
|
+
#
|
222
|
+
# lst = SkipList.new 5
|
223
|
+
# lst["foo"] = 1
|
224
|
+
# lst["bar"] = 2
|
225
|
+
# lst["baz"] = 3
|
226
|
+
# lst.to_a #=> [["bar", 2], ["baz", 3], ["foo", 1]]
|
227
|
+
#
|
228
|
+
def []= key, val
|
229
|
+
before_list = Array.new(@level_max + 1)
|
230
|
+
after_list = Array.new(@level_max + 1)
|
231
|
+
loop {
|
232
|
+
if p = find(key, before_list, after_list) then
|
233
|
+
p.val = val
|
234
|
+
break
|
235
|
+
end
|
236
|
+
toplevel = rand2exp @level_max
|
237
|
+
node = Node.new toplevel, key, val
|
238
|
+
(0).upto(toplevel){|level|
|
239
|
+
node[level] = MLink.new after_list[level]
|
240
|
+
}
|
241
|
+
unless before_list[0][0].compare_and_set after_list[0], node, false, false then
|
242
|
+
next
|
243
|
+
end
|
244
|
+
@size_lock.synchronize {
|
245
|
+
@size += 1
|
246
|
+
}
|
247
|
+
(1).upto(toplevel){|level|
|
248
|
+
loop {
|
249
|
+
if before_list[level][level].compare_and_set after_list[level], node, false, false then
|
250
|
+
break
|
251
|
+
end
|
252
|
+
find key, before_list, after_list
|
253
|
+
}
|
254
|
+
}
|
255
|
+
break
|
256
|
+
}
|
257
|
+
val
|
258
|
+
end
|
259
|
+
|
260
|
+
#
|
261
|
+
# call-seq:
|
262
|
+
# lst[key] -> value
|
263
|
+
#
|
264
|
+
# Element reference.
|
265
|
+
#
|
266
|
+
# lst = SkipList.new 5
|
267
|
+
# lst["foo"] = 1
|
268
|
+
# lst["bar"] = 2
|
269
|
+
# lst["baz"] = 3
|
270
|
+
# lst["foo"] #=> 1
|
271
|
+
# lst["bar"] #=> 2
|
272
|
+
# lst["baz"] #=> 3
|
273
|
+
#
|
274
|
+
def [] key
|
275
|
+
pp = nil
|
276
|
+
p = @head
|
277
|
+
@level_max.downto(0){|level|
|
278
|
+
pp = p[level].get_link # ?
|
279
|
+
loop {
|
280
|
+
ppp, mark = pp[level].get
|
281
|
+
while mark do
|
282
|
+
#pp = ppp # ?
|
283
|
+
pp = pp[level].get_link
|
284
|
+
ppp, mark = pp[level].get
|
285
|
+
end
|
286
|
+
if pp.key < key then
|
287
|
+
p = pp
|
288
|
+
pp = ppp
|
289
|
+
else
|
290
|
+
break
|
291
|
+
end
|
292
|
+
}
|
293
|
+
}
|
294
|
+
if pp.key == key then
|
295
|
+
pp.val
|
296
|
+
else
|
297
|
+
nil
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
#
|
302
|
+
# call-seq:
|
303
|
+
# lst.delete[key] -> value
|
304
|
+
#
|
305
|
+
# Element deletion. Returns a value of deleted element.
|
306
|
+
#
|
307
|
+
# lst = SkipList.new 5
|
308
|
+
# lst["foo"] = 1
|
309
|
+
# lst["foo"] #=> 1
|
310
|
+
# lst.delete["foo"] #=> 1
|
311
|
+
# lst["foo"] #=> nil
|
312
|
+
# lst.delete["bar"] #=> nil
|
313
|
+
#
|
314
|
+
def delete key
|
315
|
+
before_list = Array.new(@level_max + 1)
|
316
|
+
after_list = Array.new(@level_max + 1)
|
317
|
+
unless p = find(key, before_list, after_list) then
|
318
|
+
return nil
|
319
|
+
end
|
320
|
+
p.toplevel.downto(1){|level|
|
321
|
+
pp, mark = p[level].get
|
322
|
+
until mark do
|
323
|
+
p[level].compare_and_set pp, pp, false, true
|
324
|
+
pp, mark = p[level].get
|
325
|
+
end
|
326
|
+
}
|
327
|
+
pp, mark = p[0].get
|
328
|
+
loop {
|
329
|
+
i_marked_it = p[0].compare_and_set pp, pp, false, true
|
330
|
+
pp, mark = p[0].get
|
331
|
+
if i_marked_it then
|
332
|
+
@size_lock.synchronize {
|
333
|
+
@size -= 1
|
334
|
+
}
|
335
|
+
#find key, before_list, after_list
|
336
|
+
break p.val
|
337
|
+
elsif mark then
|
338
|
+
break nil
|
339
|
+
end
|
340
|
+
}
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
#
|
345
|
+
|
346
|
+
if $0 == __FILE__ then
|
347
|
+
puts "------------------------"
|
348
|
+
list = SkipList.new 5
|
349
|
+
list["foo"] = "foo"
|
350
|
+
list["bar"] = "bar"
|
351
|
+
list["baz"] = "baz"
|
352
|
+
list.print_debug
|
353
|
+
puts "------------------------"
|
354
|
+
list = SkipList.new 5
|
355
|
+
list["foo"] = "foo"
|
356
|
+
list["bar"] = "bar"
|
357
|
+
list["baz"] = "baz"
|
358
|
+
list.delete "foo"
|
359
|
+
list.print_debug
|
360
|
+
puts "------------------------"
|
361
|
+
list = SkipList.new 5
|
362
|
+
list["foo"] = "foo"
|
363
|
+
list["bar"] = "bar"
|
364
|
+
list["baz"] = "baz"
|
365
|
+
list.delete "bar"
|
366
|
+
list.print_debug
|
367
|
+
puts "------------------------"
|
368
|
+
list = SkipList.new 5
|
369
|
+
list["foo"] = "foo"
|
370
|
+
list["bar"] = "bar"
|
371
|
+
list["baz"] = "baz"
|
372
|
+
list.delete "baz"
|
373
|
+
list.print_debug
|
374
|
+
puts "------------------------"
|
375
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
# vi:set ts=3 sw=3:
|
3
|
+
# vim:set sts=0 noet:
|
4
|
+
|
5
|
+
# by Nobuyoshi Nakada [ruby-dev:41909]
|
6
|
+
|
7
|
+
class Loop
|
8
|
+
def loop
|
9
|
+
begin
|
10
|
+
t, val = catch(self){
|
11
|
+
yield self
|
12
|
+
true
|
13
|
+
}
|
14
|
+
end while t
|
15
|
+
val
|
16
|
+
end
|
17
|
+
|
18
|
+
def next val=nil
|
19
|
+
throw self, [true, val]
|
20
|
+
end
|
21
|
+
|
22
|
+
def break val=nil
|
23
|
+
throw self, [false, val]
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.loop &block
|
27
|
+
new.loop(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def self.new
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
# vi:set ts=3 sw=3:
|
3
|
+
# vim:set sts=0 noet:
|
4
|
+
|
5
|
+
=begin
|
6
|
+
Copyright (c) 2010 KISHIMOTO, Makoto
|
7
|
+
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
10
|
+
in the Software without restriction, including without limitation the rights
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
13
|
+
furnished to do so, subject to the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be included in
|
16
|
+
all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
24
|
+
THE SOFTWARE.
|
25
|
+
=end
|
26
|
+
|
27
|
+
class SentinelElement
|
28
|
+
include Comparable
|
29
|
+
|
30
|
+
MAX = new
|
31
|
+
MIN = new
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def self.new
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class << SentinelElement::MAX
|
41
|
+
def <=> other
|
42
|
+
if equal? other then
|
43
|
+
0
|
44
|
+
else
|
45
|
+
1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def coerce other
|
50
|
+
[MIN, self]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class << SentinelElement::MIN
|
55
|
+
def <=> other
|
56
|
+
if equal? other then
|
57
|
+
0
|
58
|
+
else
|
59
|
+
-1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def coerce other
|
64
|
+
[MAX, self]
|
65
|
+
end
|
66
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/skiplist.rb'}"
|
9
|
+
puts "Loading skiplist gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: skiplist
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- KISHIMOTO, Makoto
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-08-13 00:00:00 +09:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rubyforge
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 2
|
30
|
+
- 0
|
31
|
+
- 4
|
32
|
+
version: 2.0.4
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: hoe
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 2
|
45
|
+
- 6
|
46
|
+
- 1
|
47
|
+
version: 2.6.1
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id002
|
50
|
+
description: Lock-free skip list implementation by ruby.
|
51
|
+
email:
|
52
|
+
- ksmakoto@dd.iij4u.or.jp
|
53
|
+
executables: []
|
54
|
+
|
55
|
+
extensions:
|
56
|
+
- ext/skiplist/extconf.rb
|
57
|
+
extra_rdoc_files:
|
58
|
+
- History.txt
|
59
|
+
- Manifest.txt
|
60
|
+
files:
|
61
|
+
- History.txt
|
62
|
+
- Manifest.txt
|
63
|
+
- README.rdoc
|
64
|
+
- Rakefile
|
65
|
+
- ext/skiplist/extconf.rb
|
66
|
+
- ext/skiplist/skiplist_mlink.c
|
67
|
+
- lib/skiplist.rb
|
68
|
+
- lib/skiplist/loop.rb
|
69
|
+
- lib/skiplist/sentinelelement.rb
|
70
|
+
- script/console
|
71
|
+
- script/destroy
|
72
|
+
- script/generate
|
73
|
+
- test/test_helper.rb
|
74
|
+
- test/test_skiplist.rb
|
75
|
+
has_rdoc: true
|
76
|
+
homepage: http://github.com/metanest/ruby-skiplist
|
77
|
+
licenses: []
|
78
|
+
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options:
|
81
|
+
- --main
|
82
|
+
- README.rdoc
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
segments:
|
99
|
+
- 0
|
100
|
+
version: "0"
|
101
|
+
requirements: []
|
102
|
+
|
103
|
+
rubyforge_project: skiplist
|
104
|
+
rubygems_version: 1.3.7
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: Lock-free skip list implementation by ruby.
|
108
|
+
test_files:
|
109
|
+
- test/test_helper.rb
|
110
|
+
- test/test_skiplist.rb
|