sequence 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,272 @@
1
+ # $Id$
2
+ # Copyright (C) 2006 Caleb Clausen
3
+ # Distributed under the terms of Ruby's license.
4
+
5
+ require 'sequence'
6
+
7
+ class Sequence
8
+ # This Sequence class is used to represent a circular buffer. You can think of
9
+ # this as having no beginning/end or the current location is always both at the
10
+ # beginning/end. Because of the circular nature, the methods
11
+ # #scan_until, #modify,
12
+ # #each, #collect!, and #map!
13
+ # are not defined.
14
+ class Circular < Sequence
15
+ # Create a circular sequence from a normal finite one.
16
+ def initialize(sequence,pos=sequence.pos)
17
+ @seq = sequence
18
+ @pos=pos
19
+ @size=sequence.size
20
+ extend sequence.like
21
+
22
+ @seq.on_change_notify self
23
+ end
24
+
25
+ #the default _parse_slice_args isn't forgiving enough about large
26
+ #(positive or negative) indexes
27
+ def _normalize_pos pos, size=nil
28
+ pos
29
+ end
30
+
31
+ def change_notification(cu,first,oldsize,newsize)
32
+ assert(cu.equal?( @seq ))
33
+ @pos =_adjust_pos_on_change(@pos, first,oldsize,newsize)
34
+ @size+=newsize-oldsize
35
+ assert @size==@seq.size
36
+ notify_change(self,first,oldsize,newsize)
37
+ end
38
+
39
+ =begin
40
+ def _adjust_delete(len=1,reverse=false)
41
+ pos = _pos(false)
42
+ ret = nil
43
+ @positions.each { |p| ret = p.__send__(:_deletion,pos,len,reverse,ret) }
44
+ end
45
+ def _adjust_insert(len=1)
46
+ pos = _pos(false)
47
+ ret = nil
48
+ @positions.each { |p| ret = p.__send__(:_insertion,pos,len,ret) }
49
+ end
50
+ =end
51
+ attr_reader :pos,:size
52
+ def _pos=(pos)
53
+ #pos=0 if pos>=size
54
+ @pos=pos
55
+ end
56
+ public
57
+
58
+ # :stopdoc:
59
+ def new_data
60
+ @seq.new_data
61
+ end
62
+ =begin **
63
+ def read1next
64
+ v0 = @seq.read1next
65
+ v0.nil? && @seq.move!(true) && (v0 = @seq.read1next)
66
+ v0
67
+ end
68
+ def read1prev
69
+ v0 = @seq.read1prev
70
+ v0.nil? && @seq.move!(false) && (v0 = @seq.read1prev)
71
+ v0
72
+ end
73
+ def read1after
74
+ v0 = @seq.read1after
75
+ v0.nil? && begin! && (v0 = @seq.read1after)
76
+ v0
77
+ end
78
+ def read1before
79
+ v0 = @seq.read1before
80
+ v0.nil? && @seq.move!(false) && (v0 = @seq.read1before)
81
+ v0
82
+ end
83
+ def skip1next
84
+ @seq.skip1next || @seq.skip!(true) && @seq.skip1next
85
+ end
86
+ def skip1prev
87
+ @seq.skip1prev || @seq.skip!(false) && @seq.skip1prev
88
+ end
89
+ def skip1after
90
+ @seq.skip1after || @seq.skip!(true) && @seq.skip1after
91
+ end
92
+ def skip1before
93
+ @seq.skip1before || @seq.skip!(false) && @seq.skip1before
94
+ end
95
+ def delete1after
96
+ v0 = @seq.delete1after || @seq.skip!(true) && @seq.delete1after
97
+ v0.nil? || @positions && _adjust_delete
98
+ v0
99
+ end
100
+ def delete1before
101
+ v0 = @seq.delete1before || @seq.skip!(false) && @seq.delete1before
102
+ v0.nil? || @positions && _adjust_delete
103
+ v0
104
+ end
105
+ def delete1after?
106
+ v0 = @seq.delete1after?
107
+ v0.nil? && @seq.skip!(true) && (v0 = @seq.delete1after?)
108
+ v0.nil? || @positions && _adjust_delete
109
+ v0
110
+ end
111
+ def delete1before?
112
+ v0 = @seq.delete1before?
113
+ v0.nil? && @seq.skip!(false) && (v0 = @seq.delete1before?)
114
+ v0.nil? || @positions && _adjust_delete
115
+ v0
116
+ end
117
+ def write1next(v)
118
+ @seq.write1next(v) || @seq.skip!(true) && @seq.write1next(v)
119
+ end
120
+ def write1prev(v)
121
+ @seq.write1prev(v) || @seq.skip!(false) && @seq.write1prev(v)
122
+ end
123
+ def write1after(v)
124
+ @seq.write1after(v) || @seq.skip!(true) && @seq.write1after(v)
125
+ end
126
+ def write1before(v)
127
+ @seq.write1before(v) || @seq.skip!(false) && @seq.write1before(v)
128
+ end
129
+ def write1next?(v)
130
+ v0 = @seq.write1next?(v)
131
+ v0.nil? && @seq.skip!(true) && (v0 = @seq.write1next?(v))
132
+ v0
133
+ end
134
+ def write1prev?(v)
135
+ v0 = @seq.write1prev?(v)
136
+ v0.nil? && @seq.skip!(false) && (v0 = @seq.write1prev?(v))
137
+ v0
138
+ end
139
+ def write1after?(v)
140
+ v0 = @seq.write1after?(v)
141
+ v0.nil? && @seq.skip!(true) && (v0 = @seq.write1after?(v))
142
+ v0
143
+ end
144
+ def write1before?(v)
145
+ v0 = @seq.write1before?(v)
146
+ v0.nil? && @seq.skip!(false) && (v0 = @seq.write1before?(v))
147
+ v0
148
+ end
149
+ def insert1before(v)
150
+ @positions && _adjust_insert
151
+ @seq.insert1before(v)
152
+ end
153
+ def insert1after(v)
154
+ @positions && _adjust_insert
155
+ @seq.insert1after(v)
156
+ end
157
+ def scan1next(v)
158
+ v0 = read1next
159
+ (v0.nil? || v==v0) ? v0 : (skip1prev;nil)
160
+ end
161
+ def scan1prev(v)
162
+ v0 = read1prev
163
+ (v0.nil? || v==v0) ? v0 : (skip1next;nil)
164
+ end
165
+ def modify1next(r)
166
+ v0 = read1after
167
+ (v0.nil? || (v = r[v0]).nil?) ? nil : (write1next!(v);v0)
168
+ end
169
+ def modify1prev(r)
170
+ v0 = read1before
171
+ (v0.nil? || (v = r[v0]).nil?) ? nil : (write1prev!(v);v0)
172
+ end
173
+ =end
174
+ # :startdoc:
175
+ # read over one pass of the data to return where you started
176
+ def read!(reverse=false)
177
+ unless reverse
178
+ read(size)
179
+ else
180
+ readback(-size)
181
+ end
182
+ end
183
+ # skip over one pass of the data to return where you started
184
+ def move!(reverse=false)
185
+ size
186
+ end
187
+ alias end! begin!
188
+ alias end begin
189
+ # Compare to +other+.
190
+ def <=>(other)
191
+ position?(other) and pos<=>other.pos
192
+ end
193
+
194
+ =begin ***
195
+ # insert an element before the position and return self
196
+ def << (value)
197
+ insert1before(value)
198
+ self
199
+ end
200
+ # insert an element after the position and return self
201
+ def >> (value)
202
+ insert1after(value)
203
+ self
204
+ end
205
+ =end
206
+ # :stopdoc:
207
+
208
+ def data
209
+ @seq
210
+ end
211
+
212
+ def each
213
+ po=position
214
+ yield read1 until self==position
215
+ po.close
216
+ end
217
+
218
+ def eof?; false end
219
+
220
+ def readahead(len)
221
+ result=@seq[@pos%size,len]
222
+ len-=result.size
223
+ len.zero? and return result
224
+ loops=len/size
225
+
226
+ result+=@seq[0...size]*loops if loops.nonzero?
227
+
228
+ len%=size
229
+
230
+ len.zero? and return result
231
+
232
+ result+=@seq[0,len]
233
+ end
234
+
235
+ def read len
236
+ result=readahead len
237
+ move len
238
+ result
239
+ end
240
+
241
+ def modify(*args)
242
+ data=args.last
243
+ first,len,only1=_parse_slice_args(*args[0...-1])
244
+ first %= size
245
+
246
+ len>size and raise( ArgumentError, "dst len too long")
247
+ first+len>size and raise( ArgumentError, "wraparound modify in circular")
248
+
249
+ @seq.modify(*args)
250
+ end
251
+
252
+ #when reversed and circular, always put the Circular outermost.
253
+ def reverse
254
+ Circular.new @seq.reverse
255
+ end
256
+
257
+ def nearbegin(len,at=pos)
258
+ false
259
+ end
260
+
261
+ def nearend(len,at=pos)
262
+ false
263
+ end
264
+
265
+ def closed?
266
+ super or @seq.closed?
267
+ end
268
+ end
269
+ end
270
+
271
+
272
+
@@ -0,0 +1,260 @@
1
+ # Copyright (C) 2006 Caleb Clausen
2
+ # Distributed under the terms of Ruby's license.
3
+ require 'thread'
4
+ require 'sequence/arraylike'
5
+
6
+ =begin discussion
7
+
8
+ Sequence::Enum is pretty much the same thing as Generator from the
9
+ ruby standard library, but faster.
10
+ (Newer versions of the standard library include an improved
11
+ Generator, which should be about as fast.)
12
+
13
+ The key insight is to realize that Continuations were used in the
14
+ original because an independant call stack is needed (to run #each
15
+ in), separate from the call stack of the user of Generator. However,
16
+ Continuations are only one way to get another call stack; you could
17
+ also use a Thread (plus a Queue, to communicate back the results),
18
+ which doesn't have the same performance problems in ruby.
19
+
20
+ In order to implement #readahead1, I had to invent Queue#peek,
21
+ which tells you what the next element is without taking it from the
22
+ Queue.
23
+
24
+ Actually, I'm using a SizedQueue, not a plain Queue. Otherwise, the
25
+ memory used by the queue could grow without bounds.
26
+
27
+ #begin! was also a small challenge, until I realized that you could
28
+ just restart the Thread. (Hopefully, the enum or block will return
29
+ the same results the second time through.)
30
+
31
+ It's not allowed in Generator, but Sequence::Enum permits you to
32
+ pass in both an enum and a block. The results of the
33
+ block are passed up once the enum is exhausted.
34
+
35
+ #<< allows you to
36
+ add more items to the Sequence::Enum once it has been created. At first,
37
+ this was also aliased to #yield, because I didn't quite realize that
38
+ #yield should only be used inside the constructor's block. Once I
39
+ straightened out #yield, I decided that adding items after
40
+ construction was a cool feature, so I left the capability in.
41
+
42
+ It's clear that this version is faster than the original callcc-
43
+ based Generator, but I'm not sure how much. I was unable to run
44
+ the relevant benchmark to completion on my machine. Even after reducing
45
+ the number of loops in the test by 10x, the callcc version was
46
+ still taking more than 2 hours. (I don't know how much more because
47
+ at that point I grew impatient and gave it the ^C.) My own version
48
+ finishes in about a second or less.
49
+
50
+ I also found that bumping the queue size up to 400 from the original
51
+ 32 made about a 4x difference in running time. This implies to me
52
+ that context switches in ruby are rather more expensive than I
53
+ expected.
54
+ =end
55
+
56
+ if true
57
+
58
+ require 'sequence/generator'
59
+
60
+ class Sequence
61
+ class Enum < Sequence
62
+ include ArrayLike
63
+
64
+ def initialize(enum=nil,&block)
65
+ @gen=Generator.new(enum,&block)
66
+ end
67
+
68
+ def eof?; @gen.end? end
69
+ def pos; @gen.index end
70
+ def begin!; @gen.rewind; 0 end
71
+
72
+ def each &block
73
+ @gen.dup.each &block
74
+ end
75
+
76
+ def read len
77
+ return [] if len.zero? or eof?
78
+ result=[]
79
+ begin
80
+ len.times{ result<<@gen.next }
81
+ rescue EOFError:
82
+ end
83
+
84
+ return result
85
+ end
86
+
87
+ def readahead1
88
+ @gen.current
89
+ rescue EOFError:
90
+ return nil
91
+ end
92
+
93
+ def read1
94
+ @gen.next
95
+ rescue EOFError:
96
+ return nil
97
+ end
98
+
99
+ alias size pos
100
+
101
+
102
+
103
+
104
+ %w[pos= _pos= scan scan_until write [] []=
105
+ holding holding? holding! position
106
+ ].each{|mname| undef_method mname}
107
+ end
108
+ end
109
+
110
+
111
+ else
112
+
113
+
114
+ class Queue
115
+ # Retrieves next data from the queue, without pulling it off the queue.
116
+ # If the queue is empty, the calling thread is
117
+ # suspended until data is pushed onto the queue.
118
+ # If +non_block+ is true, the
119
+ # thread isn't suspended, and an exception is raised.
120
+ def peek(non_block=false)
121
+ raise ThreadError, "queue empty" if non_block and empty?
122
+ Thread.pass while (empty?)
123
+ Thread.critical=true
124
+ result=@que.first
125
+ Thread.critical=false
126
+ result
127
+ end
128
+
129
+ def read(len)
130
+ Thread.critical=true
131
+ result=@que.slice![0...len]
132
+ Thread.critical=false
133
+ result
134
+ end
135
+ end
136
+
137
+
138
+ class Sequence
139
+ class Enum < Sequence
140
+ include ArrayLike
141
+
142
+ def initialize(enum=[],qsize=400,&block)
143
+ @extras=[]
144
+ @extrasmutex=Mutex.new
145
+ init(enum,qsize,&block)
146
+ end
147
+
148
+ def init(enum,qsize,&block)
149
+ @block=block
150
+ @pos=0
151
+ @enum=enum
152
+ @q=q=SizedQueue.new(qsize)
153
+ @thread=Thread.new{
154
+ enum.each{|item| q<<item }
155
+ block[self] if block
156
+ i=0
157
+ while i<@extras.size
158
+ q.push @extrasmutex.synchronize { @extras[i] }
159
+ i+=1
160
+ end
161
+ }
162
+ end
163
+
164
+ #should only be called from inside constructor's block
165
+ def yield(item)
166
+ @q.push item
167
+ end
168
+
169
+ def <<(item)
170
+ @extrasmutex.synchronize { @extras<<item }
171
+ end
172
+
173
+ def begin!
174
+ @thread.kill
175
+ init(@enum,@q.max,&@block)
176
+ 0
177
+ end
178
+
179
+ def readahead1
180
+ current
181
+ rescue EOFError:
182
+ return nil
183
+ end
184
+
185
+ def read1
186
+ self.next
187
+ rescue EOFError:
188
+ return nil
189
+ end
190
+
191
+ def current
192
+ raise EOFError if eof?
193
+ @q.peek
194
+ rescue ThreadError:
195
+ raise EOFError
196
+ end
197
+
198
+ def next
199
+ result=@q.pop
200
+ raise EOFError if !result && eof?
201
+ @pos+=1
202
+ result
203
+ rescue ThreadError:
204
+ raise EOFError
205
+ end
206
+
207
+ def read(len)
208
+ len.zero? and return []
209
+ raise ThreadError if @thread.status=="sleep" and @q.empty?
210
+ result=[]
211
+ begin #loop
212
+ len.times{ result<<@q.pop(true) }
213
+ #feh, should fetch more at a time... Queue needs a #read
214
+ rescue ThreadError:
215
+ len-=result.length
216
+ end until @q.empty? and result.length.zero?
217
+
218
+ ensure
219
+ @pos+=result.length
220
+ return result
221
+ end
222
+
223
+ def size
224
+ @pos+@q.size
225
+ end
226
+
227
+ def eof?
228
+ Thread.pass while @q.empty? and @thread.alive?
229
+ @q.empty? and !@thread.alive?
230
+ end
231
+
232
+ def each(&block)
233
+ copy=dup
234
+ copy.begin!
235
+ until(copy.eof?)
236
+ block.call copy.read1
237
+ end
238
+ end
239
+
240
+ attr :pos
241
+
242
+ #methods for Generator compatibility:
243
+ def rewind; begin!; self end
244
+ alias end? eof?
245
+ alias index pos
246
+ def next?; !end? end
247
+
248
+ %w[pos= _pos= scan scan_until write [] []=
249
+ holding holding? holding! position
250
+ ].each{|mname| undef_method mname}
251
+ end
252
+ end
253
+
254
+ end
255
+
256
+ module Enumerable
257
+ def to_sequence
258
+ Sequence::Enum.new(self)
259
+ end
260
+ end
@@ -0,0 +1,172 @@
1
+ # Copyright (C) 2006 Caleb Clausen
2
+ # Distributed under the terms of Ruby's license.
3
+ # $Id$
4
+
5
+ require 'sequence'
6
+ require 'sequence/stringlike'
7
+
8
+ class Sequence
9
+ # This class treats an IO (or StringIO) as an Sequence. An IO is already
10
+ # like an Sequence, but with a differing interface.
11
+ # Actually, we assume that the IO is capable of seeking, so it most likely
12
+ # must be a File.
13
+ # delete/insert at arbitrary location is not supported.
14
+ class File < Sequence
15
+ # include UseNext
16
+ include StringLike
17
+ def initialize(file,mode="r")
18
+
19
+ case file
20
+ when Integer: file=IO.new(file,mode)
21
+ when String: file=File.new(file,mode)
22
+ else #do nothing, file is of a right type (we hope) already
23
+ end
24
+
25
+ @io = file
26
+ end
27
+ # :stopdoc:
28
+ def new_data
29
+ ''
30
+ end
31
+ def data; @io end
32
+ def read1
33
+ @io.getc
34
+ end
35
+ =begin ***
36
+ def write1next(v)
37
+ @io.eof? ? nil : (@io.putc(v);true)
38
+ end
39
+ def write1next!(v)
40
+ @io.putc(v)
41
+ true
42
+ end
43
+ def skip1prev
44
+ @io.pos.nonzero? && (@io.seek(-1,::IO::SEEK_CUR);true)
45
+ end
46
+ =end
47
+ def readahead1
48
+ v0 = @io.getc
49
+ v0 && @io.ungetc(v0)
50
+ v0
51
+ end
52
+ =begin ***
53
+ def write1after!(v)
54
+ @io.putc(v)
55
+ @io.seek(-1,::IO::SEEK_CUR)
56
+ true
57
+ end
58
+ def skip1next
59
+ @io.getc ? true : nil
60
+ end
61
+ def skip1after
62
+ @io.eof? ? nil : true
63
+ end
64
+ def skip1before
65
+ @io.pos.zero? ? nil : true
66
+ end
67
+ def scan1next(v)
68
+ v0 = @io.getc
69
+ (v0.nil? || v==v0) ? v0 : (@io.ungetc(v0);nil)
70
+ end
71
+ =end
72
+ def size
73
+ @io.stat.size
74
+ end
75
+ def read(len)
76
+ @io.read(len) or ""
77
+ end
78
+ def readahead(len)
79
+ buffer1 = read(len)
80
+ @io.seek(-buffer1.size,::IO::SEEK_CUR)
81
+ buffer1
82
+ end
83
+ def readback(len)
84
+ result=readbehind(len)
85
+ @io.seek(-result.size,::IO::SEEK_CUR)
86
+ result
87
+ end
88
+ def readbehind(len)
89
+ p = @io.pos
90
+ len>p and len=p
91
+ @io.seek(-len,::IO::SEEK_CUR)
92
+ @io.read(len)
93
+ end
94
+ def read!(reverse=false)
95
+ if reverse
96
+ len = @io.pos.nonzero? or return ""
97
+ @io.seek(0, ::IO::SEEK_SET)
98
+ #@io.pos = 0 # BUGGY in v1.8.2
99
+ buffer1 = @io.read(len) || ""
100
+ @io.seek(0, ::IO::SEEK_SET)
101
+ else
102
+ buffer1 = @io.read(nil)
103
+ end
104
+ buffer1
105
+ end
106
+
107
+ def eof?; @io.eof? end
108
+
109
+ def _pos=(p)
110
+ @io.seek(p,::IO::SEEK_SET)
111
+ p
112
+ end
113
+
114
+
115
+ def modify(*args)
116
+ data=args.pop
117
+ first,len,only1=_parse_slice_args(*args)
118
+ if first+len==size #working at end of data?
119
+ holding{
120
+ @io.truncate first if len.nonzero?
121
+ goto first
122
+ @io.write data
123
+ }
124
+ elsif len==data.size #inserted data is same size?
125
+ holding{
126
+ goto first
127
+ @io.write data
128
+ }
129
+ else
130
+ raise ArgumentError,"replace data must be same size or modification must be at very end"
131
+ end
132
+ notify_change(self,first,len,data.size)
133
+ data
134
+ end
135
+
136
+
137
+
138
+ def append(str)
139
+ Integer===str and str=str.chr
140
+ first=nil
141
+ holding{
142
+ end!
143
+ first=pos
144
+ @io.write str
145
+ }
146
+ notify_change(self,first,0,str.size)
147
+ self
148
+ end
149
+
150
+ def close
151
+ @io.close
152
+ super
153
+ end
154
+
155
+ def pos; @io.tell end
156
+ # :startdoc:
157
+ end
158
+ end
159
+
160
+ class File
161
+ # convert a File to a seq
162
+ def to_sequence
163
+ Sequence::File.new(self)
164
+ end
165
+ end
166
+
167
+ class StringIO
168
+ # convert an StringIO to a seq
169
+ def to_sequence
170
+ Sequence::File.new(self)
171
+ end
172
+ end