iterable 0.0.6.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ require_relative './iterable_array.rb'
2
+
@@ -0,0 +1,94 @@
1
+ # Note: Currently this uses a super sloppy/cluttered approach to adding and removing methods.
2
+ # I'd put the methods in modules and extend the IterableArray instance with the appropriate
3
+ # module except that removing/unextending a module would require the mixology gem, which currently
4
+ # does not work in Rubinius. :(
5
+ # Or the remix gem, maybe.
6
+ require 'forwardable'
7
+
8
+ require_relative './swapable.rb'
9
+ require_relative './iterable_array/special_accessors.rb'
10
+ require_relative './iterable_array/iterators.rb'
11
+ require_relative './iterable_array/iterator_specials.rb'
12
+ require_relative './iterable_array/special_modifiers_noniterating.rb'
13
+ # The following are not yet modules.
14
+ require_relative './iterable_array/special_modifiers_iterating.rb'
15
+ require_relative './iterable_array/plain_modifiers.rb'
16
+
17
+ class IterableArray
18
+ extend Forwardable
19
+
20
+ # include self::SpecialAccessors
21
+ # include self::Iterators
22
+ include self::IteratorSpecials
23
+ include self::SpecialModifiersNoniterating
24
+ # include self::SpecialModifiersIterating
25
+ # include self::PlainModifiers
26
+
27
+ # Most Enumerable instance methods have not been tested to ensure atomic modification
28
+ include Enumerable
29
+
30
+ @@iterator_specials = [ :tracking, :tracking=, :invert_tracking, ]
31
+
32
+ @@plain_accessors = [ :frozen?, :==, :[]=, :size, :length, :to_a, :to_s, :to_json, :to_enum, :include?, :hash, :to_ary, :fetch, :inspect, :at, :join, :empty?, :member?, :pack, :shelljoin, ]
33
+ @@special_accessors = [ :<<, :concat, :&, :|, :*, :+, :-, :[], :drop, :dup, :compact, :sample, :slice, :<=>, :eql?, :indices, :indexes, :values_at, :assoc, :rassoc, :first, :sort, :last, :flatten, :reverse, :shuffle, :push, :replace, :rotate, :swap, :swap_indices, :take, :transpose, :uniq, ]
34
+
35
+ @@plain_modifiers = [ :delete, :delete_at, ]
36
+ @@special_modifiers = [ :clear, :compact!, :flatten!, :insert, :move, :move_from, :shift, :shuffle!, :sort!, :sort_by!, :unshift, :pop, :reverse!, :rotate!, :slice!, :swap!, :swap_indices!, :uniq!, ]
37
+
38
+ @@iterators = [ :each, :reverse_each, :rindex, :collect, :collect!, :map, :map!, :combination, :cycle, :delete_if, :drop_while, :each_index, :index, :find_index, :keep_if, :each_with_index, :reject!, :reject, :select!, :select, :take_while, :count, :fill, :permutation, :repeated_permutation, :repeated_combination, :product, :zip, ]
39
+
40
+ # Enumerable methods not covered by Array => [:sort_by, :grep, :find, :detect, :find_all, :flat_map, :collect_concat, :inject, :reduce, :partition, :group_by, :all?, :any?, :one?, :none?, :min, :max, :minmax, :min_by, :max_by, :minmax_by, :each_entry, :each_slice, :each_cons, :each_with_object, :chunk, :slice_before]
41
+
42
+ def_delegators :@array, *@@plain_accessors
43
+ def_delegators :@array, *@@plain_modifiers
44
+
45
+ private
46
+
47
+ def initialize *args
48
+ @array = Array.new(*args).extend Swapable
49
+ @progenitor_binding = binding
50
+ end
51
+
52
+ def bastardize
53
+ @array = self.new_with_binding @array
54
+ @tracking = 0.5
55
+ class << self
56
+ def_delegators :@array, *@@special_accessors
57
+ def_delegators :@array, *@@iterators
58
+ end
59
+ define_plain_modifiers
60
+ define_special_modifiers_iterating
61
+ end
62
+
63
+ def debastardize
64
+ @tracking = nil
65
+ @backward_index, @current_index, @forward_index = nil, nil, nil
66
+ @array = @array.to_a
67
+ remove_methods *@@special_accessors
68
+ remove_methods *@@iterators
69
+ remove_methods *@@plain_modifiers
70
+ remove_methods *@@special_modifiers
71
+ end
72
+
73
+ def remove_methods *ary
74
+ ary.each do |meth|
75
+ singleton_class.class_exec { remove_method meth }
76
+ end
77
+ end
78
+
79
+ protected
80
+
81
+ # Necessary for allowing nested iteration with modifying iterators (like
82
+ # :delete_if)
83
+ def new_with_binding array
84
+ iter_ary = IterableArray.new array
85
+ iter_ary.take_progenitor_binding @progenitor_binding
86
+ iter_ary
87
+ end
88
+
89
+ def take_progenitor_binding progenitor_binding
90
+ @progenitor_binding = progenitor_binding
91
+ class << self; undef_method :take_progenitor_binding; end
92
+ end
93
+ end
94
+
@@ -0,0 +1,68 @@
1
+ class IterableArray
2
+ module IteratorSpecials
3
+ # specials
4
+ # TODO: either ensure that the following methods are being undefined
5
+ # during #debastardize or make them private... currently they aren't
6
+ # being undefined.
7
+ # TODO: Also, these currently fail in a nested iteration block if they're
8
+ # called from the main instance of IterableArray...they'll only affect
9
+ # the outermost iteration block when called by a user (but they should
10
+ # work fine when called internally)
11
+ public
12
+ # untested
13
+ def invert_tracking
14
+ @tracking *= -1
15
+ tracking
16
+ end
17
+
18
+ # Need to add some minimal documentation on tracking.
19
+ # untested
20
+ def invert_tracking_at nesting_level = -1
21
+ direct_to nesting_level, &:invert_tracking
22
+ end
23
+
24
+ # untested
25
+ def tracking
26
+ case @tracking < 0
27
+ when true then :aft
28
+ when false then :fore
29
+ end
30
+ end
31
+
32
+ # untested
33
+ def tracking_at nesting_level = -1
34
+ direct_to nesting_level, &:tracking
35
+ end
36
+
37
+ # untested
38
+ def tracking= orient
39
+ @tracking = case orient
40
+ when :aft then @tracking.abs * -1
41
+ when :fore then @tracking.abs
42
+ end
43
+ tracking
44
+ end
45
+
46
+ # untested
47
+ def tracking_at= orient, nesting_level = -1
48
+ direct_to nesting_level do |obj|
49
+ obj.tracking = orient
50
+ end
51
+ end
52
+
53
+ protected
54
+ # untested
55
+ def direct_to nesting_level
56
+ case
57
+ when nesting_level == 0
58
+ yield self
59
+ when nesting_level < 0
60
+ @array.kind_of? Array ?
61
+ direct_to(0) :
62
+ @array.direct_to(-1)
63
+ else
64
+ @array.direct_to(nesting_level - 1)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,445 @@
1
+ class IterableArray
2
+ # module Iterators
3
+ private
4
+ def catch_a_break
5
+ result = nil
6
+ begin
7
+ bastardize
8
+ result = yield
9
+ ensure
10
+ debastardize
11
+ end
12
+ result
13
+ end
14
+
15
+ def increment_indices
16
+ @current_index = @forward_index
17
+ @forward_index = @current_index + 1
18
+ @backward_index = @current_index - 1
19
+ end
20
+
21
+ public
22
+
23
+ # untested and not a pretty sight in general
24
+ # When used without a block, `#fill` is not iteration aware
25
+ # as its default behavior is similar to that of `#replace`---
26
+ # which is a clobberor.
27
+ def fill *args # obj=nil, start_or_range=nil, lengther=nil
28
+ return @array.fill *args unless block_given?
29
+
30
+ #######################
31
+ # Argument processing #
32
+ #######################
33
+ args[0] = 0 if args.empty?
34
+
35
+ raise ArgumentError if
36
+ args.size > 2 or
37
+ (args.first.kind_of? Range and args.size != 1)
38
+
39
+ if args.first.kind_of? Range then
40
+ args[1] = args.first.exclude_end? ?
41
+ args.first.last - 1 :
42
+ args.first.last
43
+ args[0] = args.first.first
44
+ end
45
+
46
+ ##################################################
47
+ # Translating argument array to iteration bounds #
48
+ ##################################################
49
+ starter = args.first
50
+ the_final = args.at 1
51
+ ender = -> { the_final or length }
52
+
53
+ #############
54
+ # Iteration #
55
+ #############
56
+ catch_a_break do
57
+ @current_index = starter
58
+ @backward_index, @forward_index = @current_index - 1, @current_index + 1
59
+ while @current_index < ender[]
60
+ index_to_fill = @current_index
61
+ self[index_to_fill] = yield @current_index
62
+ increment_indices
63
+ end
64
+ self
65
+ end
66
+ end
67
+
68
+ # untested
69
+ def drop_while
70
+ return @array.to_enum :drop_while unless block_given?
71
+
72
+ out = IterableArray.new
73
+ dropping = true
74
+ each do |x|
75
+ dropping = yield x if dropping
76
+ out << x unless dropping
77
+ end
78
+ out
79
+ end
80
+
81
+ # untested
82
+ def select
83
+ return @array.to_enum(:select) unless block_given?
84
+ out = IterableArray.new
85
+ each { |x| out << x if yield x }
86
+ out
87
+ end
88
+
89
+ # untested
90
+ def select!
91
+ return @array.to_enum(:select) unless block_given?
92
+ original = @array.to_a.dup
93
+ delete_if { |x| not yield x }
94
+ return nil if self == original
95
+ self
96
+ end
97
+
98
+ # untested
99
+ def keep_if
100
+ return @array.to_enum(:keep_if) unless block_given?
101
+ delete_if { |x| not yield x }
102
+ end
103
+
104
+ # untested
105
+ def count arg = :undefined
106
+ if arg == :undefined
107
+ return @array.length unless block_given?
108
+ counter = 0
109
+ each { |x| counter += 1 if yield x }
110
+ counter
111
+ else
112
+ @array.to_a.count arg
113
+ end
114
+ end
115
+
116
+ def each
117
+ return @array.to_enum(:each) unless block_given?
118
+
119
+ catch_a_break do
120
+ @backward_index, @current_index, @forward_index = -1, 0, 1
121
+ while @current_index < @array.size
122
+ yield @array.at(@current_index)
123
+ increment_indices
124
+ end
125
+
126
+ self
127
+ end
128
+ end
129
+
130
+ def each_index
131
+ return @array.to_enum(:each_index) unless block_given?
132
+
133
+ catch_a_break do
134
+ @backward_index, @current_index, @forward_index = -1, 0, 1
135
+ while @current_index < @array.size
136
+ yield @current_index
137
+ increment_indices
138
+ end
139
+
140
+ self
141
+ end
142
+ end
143
+
144
+ def each_with_index
145
+ return @array.to_enum(:each_with_index) unless block_given?
146
+
147
+ catch_a_break do
148
+ @backward_index, @current_index, @forward_index = -1, 0, 1
149
+ while @current_index < @array.size
150
+ yield @array.at(@current_index), @current_index
151
+ increment_indices
152
+ end
153
+
154
+ self
155
+ end
156
+ end
157
+
158
+ # Should delete_if be smarter / more magical than this?
159
+ # I had considered having it test for whether an element
160
+ # it is going to delete has already been deleted while
161
+ # it was yielded to the iteration block. Doing so would
162
+ # be something of a wild-goose chase but then so is this
163
+ # entire project. I don't know if making it more 'magical'
164
+ # would make its behavior more or less predictable to the
165
+ # end user.
166
+ def delete_if # Rubinius includes `&block` as an argument, but I don't know why
167
+ return @array.to_enum(:delete_if) unless block_given?
168
+
169
+ catch_a_break do
170
+ @backward_index, @current_index, @forward_index = -1, 0, 1
171
+ while @current_index < @array.size
172
+
173
+ # Usage of binding is necessary since this current :delete_if
174
+ # call might be operating inside several levels of nested
175
+ # iteration. If we just used :delete_at here, those higher
176
+ # iteration levels would not be able to adjust their indices
177
+ # to account for the change in array size.
178
+ if yield @array.at(@current_index)
179
+ @progenitor_binding.eval "self.delete_at #{@current_index}"
180
+ end
181
+ increment_indices
182
+ end
183
+
184
+ self
185
+ end
186
+ end
187
+
188
+ # untested
189
+ def reject!
190
+ return @array.to_enum(:reject!) unless block_given?
191
+ original = @array.to_a.dup
192
+ delete_if { |x| yield x }
193
+ return nil if self == original
194
+ self
195
+ end
196
+
197
+ # untested
198
+ def reject
199
+ return @array.to_enum(:reject) unless block_given?
200
+ out = IterableArray.new
201
+ each { |x| out << x unless yield x }
202
+ out
203
+ end
204
+
205
+ # untested
206
+ # need to fix default argument to allow folks to search for nil
207
+ def rindex obj = nil
208
+ if obj.nil?
209
+ return @array.to_enum(:rindex) unless block_given?
210
+
211
+ catch_a_break do
212
+ invert_tracking
213
+ @current_index = @array.size - 1
214
+ @backward_index, @forward_index = @current_index - 1, @current_index + 1
215
+ while @current_index >= 0
216
+ return @current_index if yield @array.at @current_index
217
+
218
+ @current_index = @backward_index
219
+ @forward_index = @current_index + 1
220
+ @backward_index = @current_index - 1
221
+ end
222
+ nil
223
+ end
224
+ else
225
+ @array.rindex obj
226
+ end
227
+ end
228
+
229
+ def reverse_each
230
+ return @array.to_enum(:reverse_each) unless block_given?
231
+
232
+ catch_a_break do
233
+ invert_tracking
234
+ @current_index = @array.size - 1
235
+ @backward_index, @forward_index = @current_index - 1, @current_index + 1
236
+ while @current_index >= 0
237
+ yield @array.at @current_index
238
+
239
+ @current_index = @backward_index
240
+ @forward_index = @current_index + 1
241
+ @backward_index = @current_index - 1
242
+ end
243
+
244
+ self
245
+ end
246
+ end
247
+
248
+ # untested
249
+ def take_while
250
+ return @array.to_enum(:take_while) unless block_given?
251
+ out = IterableArray.new
252
+ each { |x| break unless yield x; out << x }
253
+ out
254
+ end
255
+
256
+ def map
257
+ return @array.dup unless block_given?
258
+ out = IterableArray.new
259
+
260
+ catch_a_break do
261
+ @backward_index, @current_index, @forward_index = -1, 0, 1
262
+ while @current_index < @array.size
263
+ out << yield(@array.at @current_index)
264
+ increment_indices
265
+ end
266
+
267
+ out
268
+ end
269
+ end
270
+
271
+ alias_method :collect, :map
272
+
273
+ def map!
274
+ return to_enum(:map!) unless block_given?
275
+
276
+ catch_a_break do
277
+ @backward_index, @current_index, @forward_index = -1, 0, 1
278
+ while @current_index < @array.size
279
+ # The following verbosity required so that @current_index will be
280
+ # evaluated after any modifications to it by the block.
281
+ temp_value = yield(@array.at @current_index)
282
+ @array[@current_index] = temp_value
283
+ increment_indices
284
+ end
285
+
286
+ self
287
+ end
288
+ end
289
+
290
+ alias_method :collect!, :map!
291
+
292
+ def cycle n = nil, &block
293
+ return @array.to_enum(:cycle, n) unless block_given?
294
+
295
+ catch_a_break do
296
+ if n.nil?
297
+ until @array.empty?
298
+ @backward_index, @current_index, @forward_index = -1, 0, 1
299
+ while @current_index < @array.size
300
+ yield @array.at(@current_index)
301
+ increment_indices
302
+ end
303
+ end
304
+ else
305
+ n.times do
306
+ @backward_index, @current_index, @forward_index = -1, 0, 1
307
+ while @current_index < @array.size
308
+ yield @array.at(@current_index)
309
+ increment_indices
310
+ end
311
+ end
312
+ end
313
+
314
+ nil
315
+ end
316
+ end
317
+
318
+ def index obj = :undefined
319
+ unless block_given?
320
+ # What should I change :undefined to?
321
+ # I can't use null or nil or anything in case the index
322
+ # of null or nil needs to be looked up.
323
+ # Rubinius uses a plain (non-symbol) 'undefined' keyword,
324
+ # but that doesn't seem to work in 1.9.2.
325
+ return @array.index(obj) unless obj == :undefined
326
+ return @array.to_enum(:index)
327
+ end
328
+
329
+ catch_a_break do
330
+ @backward_index, @current_index, @forward_index = -1, 0, 1
331
+ while @current_index < @array.size
332
+ return @current_index if yield @array.at(@current_index)
333
+ increment_indices
334
+ end
335
+
336
+ nil
337
+ end
338
+ end
339
+
340
+ # Have not tested aliased iterators to ensure
341
+ # maximal efficacitic beneficial results
342
+ alias_method :find_index, :index
343
+
344
+ # untested
345
+ def zip *args
346
+ if block_given?
347
+ i = 0
348
+ each do |x|
349
+ out = [x]
350
+ args.each { |arg| out.push arg.at i }
351
+ i += 1
352
+ yield self.class.new out
353
+ end
354
+ nil
355
+ else
356
+ self.class.new(to_a.zip(*args).map { |x| self.class.new x })
357
+ end
358
+ end
359
+
360
+ # untested
361
+ def product *arrays
362
+ return IterableArray.new @array.product(*arrays) unless block_given?
363
+
364
+ each do |x|
365
+ basket = [x].product *arrays
366
+ # means of skipping a whole chunk of yields
367
+ catch :product_next do
368
+ basket.each { |y| yield IterableArray.new y }
369
+ end
370
+ end
371
+ end
372
+
373
+ # Currently only defined for arrays `a` where `a.uniq == a`
374
+ def permutation n = @array.size, &block
375
+ comb_perm_helper :permutation, n, &block
376
+ end
377
+
378
+ # Currently only defined for arrays `a` where `a.uniq == a`
379
+ def combination n, &block
380
+ comb_perm_helper :combination, n, &block
381
+ end
382
+
383
+ # Currently only defined for arrays `a` where `a.uniq == a`
384
+ def repeated_permutation n = @array.size, &block
385
+ comb_perm_helper :repeated_permutation, n, &block
386
+ end
387
+
388
+ # Currently only defined for arrays `a` where `a.uniq == a`
389
+ def repeated_combination n, &block
390
+ comb_perm_helper :repeated_combination, n, &block
391
+ end
392
+
393
+ private
394
+ def comb_perm_helper methd, n
395
+ return @array.to_enum(methd, n) unless block_given?
396
+ @backward_index, @current_index, @forward_index = 0, 0, 0
397
+
398
+ catch_a_break do
399
+ queue = comb_perm_generator methd, to_a, n
400
+ history = []
401
+ until queue.empty?
402
+ element = queue.shift
403
+ previous = to_a.sort
404
+ yield element
405
+ history.push element
406
+ queue = diff_handler queue, history, previous, methd, n unless to_a.sort == previous
407
+ end
408
+ self
409
+ end
410
+ end
411
+
412
+ def comb_perm_generator methd, ary, n
413
+ out = []
414
+ ary.method(methd)[ n, &(->(item) {
415
+ out << item
416
+ }) ]
417
+ out
418
+ end
419
+
420
+ # only for :permutation/:combination and their cousins
421
+ def diff_handler queue, history, previous, methd, n
422
+ deleted_items = previous - self
423
+ new_items = self - previous
424
+ queue = remove_deleted_items queue, deleted_items
425
+ queue = add_new_items queue, history, new_items, methd, n
426
+ queue
427
+ end
428
+
429
+ # only for :permutation/:combination and their cousins
430
+ def add_new_items queue, history, items, methd, n
431
+ new_elements = comb_perm_generator methd, to_a, n
432
+ new_elements -= history
433
+ queue += new_elements
434
+ queue
435
+ end
436
+
437
+ # only for :permutation/:combination and their cousins
438
+ def remove_deleted_items queue, items
439
+ items.each do |item|
440
+ queue.delete_if { |x| x.include? item }
441
+ end
442
+ queue
443
+ end
444
+ # end
445
+ end
@@ -0,0 +1,31 @@
1
+ class IterableArray
2
+ # module PlainModifiers
3
+ private
4
+ def define_plain_modifiers
5
+ class << self
6
+ def delete_at location
7
+ # Flip to positive and weed out out of bounds
8
+ location += @array.size if location < 0
9
+ return nil unless location.between? 0, @array.size.pred
10
+
11
+ @forward_index -= 1 if location < @forward_index
12
+ @current_index -= 1 if location < @current_index - @tracking
13
+ @backward_index -= 1 if location < @backward_index
14
+
15
+ @array.delete_at location
16
+ end
17
+
18
+ # currently untested
19
+ def delete obj
20
+ n = count obj
21
+ if n.zero?
22
+ return yield obj if block_given?
23
+ nil
24
+ else
25
+ n.times { delete_at index obj }
26
+ obj
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,194 @@
1
+ class IterableArray
2
+ # module SpecialAccessors
3
+ def << arg
4
+ @array << arg
5
+ self
6
+ end
7
+
8
+ def concat arg
9
+ arg.each { |x| @array << x }
10
+ self
11
+ end
12
+
13
+ def [](arg1, arg2=nil)
14
+ case arg1
15
+
16
+ when Fixnum
17
+ return @array.at(arg1) unless arg2
18
+
19
+ IterableArray.new @array[arg1, arg2]
20
+
21
+ when Range
22
+ IterableArray.new @array[arg1]
23
+ end
24
+ end
25
+
26
+ alias_method :slice, :[]
27
+
28
+ def drop n
29
+ IterableArray.new @array.drop n
30
+ end
31
+
32
+ # untested
33
+ def dup
34
+ IterableArray.new @array.to_a.dup
35
+ end
36
+
37
+ def first n = nil
38
+ return @array.first if n == nil
39
+ IterableArray.new @array.first(n)
40
+ end
41
+
42
+ def last n = nil
43
+ return @array.last if n == nil
44
+ IterableArray.new @array.last(n)
45
+ end
46
+
47
+ def push *args
48
+ @array.push *args
49
+ self
50
+ end
51
+
52
+ # untested
53
+ def compact
54
+ IterableArray.new @array.compact
55
+ end
56
+
57
+ # It looks like Array#assoc and Array#rassoc return
58
+ # Array versions of their elements when they find a
59
+ # match. I'm not sure why that is... You'd think it
60
+ # would be more duck-typish to maintain the object's
61
+ # class, right? I dunno why it has to be so sad.
62
+ def assoc obj
63
+ @array.each do |x|
64
+ if x.respond_to? :at and x.at(0) == obj
65
+ return x
66
+ end
67
+ end
68
+
69
+ nil
70
+ end
71
+
72
+ # See note above assoc.
73
+ def rassoc obj
74
+ @array.each do |elem|
75
+ if elem.respond_to? :at and elem.at(1) == obj
76
+ return elem
77
+ end
78
+ end
79
+
80
+ nil
81
+ end
82
+
83
+ # Not defined since Array#nitems is not defined in 1.9.2+
84
+ # def nitems
85
+ # end
86
+
87
+ def & arg
88
+ IterableArray.new(@array & arg.to_a)
89
+ end
90
+
91
+ def | arg
92
+ IterableArray.new(@array | arg.to_a)
93
+ end
94
+
95
+ def + arg
96
+ IterableArray.new(@array + arg)
97
+ end
98
+
99
+ def - arg
100
+ IterableArray.new(@array - arg)
101
+ end
102
+
103
+ def * arg
104
+ return IterableArray.new @array * arg if arg.kind_of? Fixnum
105
+ @array * arg
106
+ end
107
+
108
+ def <=>(other)
109
+ return @array <=> other.to_a if other.kind_of? IterableArray
110
+ @array <=> other
111
+ end
112
+
113
+ def values_at *args
114
+ out = IterableArray.new
115
+ args.each do |arg|
116
+ out += @array.values_at arg
117
+ end
118
+ out
119
+ end
120
+
121
+ alias_method :indices, :values_at
122
+ alias_method :indexes, :values_at
123
+
124
+ # TODO need Mspec test...ran into problems with this one
125
+ def sample arg = nil
126
+ return IterableArray.new(@array.sample arg) unless arg.nil?
127
+ @array.sample
128
+ end
129
+
130
+ # :eql? returns true only when array contents are the same and
131
+ # both objects are IterableArray instances
132
+ def eql? arg
133
+ (arg.class == IterableArray) and
134
+ (self == arg.to_a)
135
+ end
136
+
137
+ # untested
138
+ def flatten level = -1
139
+ IterableArray.new @array.flatten(level)
140
+ end
141
+
142
+ # untested
143
+ def reverse
144
+ IterableArray.new @array.reverse
145
+ end
146
+
147
+ # untested
148
+ def rotate n = 1
149
+ IterableArray.new @array.rotate(n)
150
+ end
151
+
152
+ # untested
153
+ def replace *args
154
+ @array.replace *args
155
+ self
156
+ end
157
+
158
+ def sort
159
+ IterableArray.new @array.sort
160
+ end
161
+
162
+ def shuffle
163
+ IterableArray.new @array.shuffle
164
+ end
165
+
166
+ def swap *args
167
+ IterableArray.new @array.swap(*args)
168
+ end
169
+
170
+ def swap_indices *args
171
+ IterableArray.new @array.swap_indices(*args)
172
+ end
173
+
174
+ # untested
175
+ def take n
176
+ IterableArray.new @array.take(n)
177
+ end
178
+
179
+ # untested
180
+ # Note: All internal arrays will also be IterableArrays.
181
+ # Any point at all to this? One doubts...
182
+ def transpose
183
+ # So. Pointless. Blerg.
184
+ IterableArray.new(@array.transpose.map do |x|
185
+ IterableArray.new x
186
+ end)
187
+ end
188
+
189
+ # untested
190
+ def uniq
191
+ IterableArray.new @array.uniq
192
+ end
193
+ # end
194
+ end
@@ -0,0 +1,226 @@
1
+ class IterableArray
2
+ # module SpecialModifiersIterating
3
+ private
4
+ def define_special_modifiers_iterating
5
+ class << self
6
+ def clear
7
+ @backward_index -= @current_index
8
+ @forward_index -= @current_index
9
+ @current_index = 0
10
+ @array.clear
11
+ end
12
+
13
+ # untested
14
+ def compact!
15
+ delete_if &:nil?
16
+ self
17
+ end
18
+
19
+ def shift n = nil
20
+ return self.delete_at(0) if n == nil
21
+
22
+ out = IterableArray.new
23
+ [ n, @array.length ].min.times { out << self.delete_at(0) }
24
+ out
25
+ end
26
+
27
+ def insert location, *items
28
+ location = size + location if location < 0
29
+
30
+ @array.insert location, *items # I put this here rather than at the end
31
+ # so that any possible location error is raised
32
+ # before modifying the indices.
33
+
34
+ center_indices_at(@current_index + items.size) if location <= @current_index
35
+
36
+ self
37
+ end
38
+
39
+ # untested
40
+ def move element, to
41
+ from = index element
42
+ move_from from, to
43
+ end
44
+
45
+ # untested
46
+ def move_from from, to
47
+ if from == @current_index then
48
+ center_indices_at to
49
+ elsif from < @current_index and to >= @current_index
50
+ center_indices_at(@current_index - 1)
51
+ elsif from > @current_index and to <= @current_index
52
+ center_indices_at(@current_index + 1)
53
+ end
54
+ @array.move_from from, to
55
+ end
56
+
57
+ # untested
58
+ def flatten! level = -1
59
+ return self if level.zero?
60
+ @forward_index = to_a[0...@forward_index].flatten(level).size
61
+ @backward_index = @forward_index - 1
62
+ @current_index = (@backward_index + @tracking.abs + @tracking).to_int
63
+ @array.flatten! level
64
+ self
65
+ end
66
+
67
+ def slice! start, length=:undefined
68
+ if length.equal? :undefined
69
+ if start.kind_of? Range
70
+ out = IterableArray.new @array.slice(start)
71
+ first, last = start.first, start.last
72
+ first += @array.length if first < 0
73
+ last += @array.length if last < 0
74
+ if first <= last
75
+ length = last - first
76
+ length += 1 unless start.exclude_end?
77
+ length.times { delete_at first }
78
+ end
79
+ else
80
+ out = delete_at start
81
+ end
82
+ else
83
+ out = IterableArray.new @array.slice(start, length)
84
+ start += @array.length if start < 0
85
+ length.times { delete_at start }
86
+ end
87
+ out
88
+ end
89
+
90
+ def unshift *args
91
+ insert 0, *args
92
+ self
93
+ end
94
+
95
+ def pop n = nil
96
+ return delete_at(size - 1) if n.nil?
97
+
98
+ out = IterableArray.new []
99
+ n.times { out.unshift pop }
100
+ out
101
+ end
102
+
103
+ def reverse!
104
+ @backward_index, @forward_index =
105
+ @array.size - 1 - @forward_index,
106
+ @array.size - 1 - @backward_index
107
+
108
+ # @current_index = @backward_index + 1
109
+ @current_index = @array.size - 1 - @current_index
110
+
111
+ @current_index = case @current_index
112
+ when @backward_index then @forward_index
113
+ when @forward_index then @backward_index
114
+ else @current_index
115
+ end
116
+
117
+ invert_tracking
118
+
119
+ @array.reverse!
120
+ self
121
+ end
122
+
123
+ # partially tested (not tested nested)
124
+ def rotate! n = 1
125
+ n %= size
126
+ return self if n.zero?
127
+ new_index = (@current_index - n) % size
128
+ center_indices_at new_index
129
+ @array.rotate! n
130
+ end
131
+
132
+ # Still need to implement with block
133
+ # `#sort!` modification is iterative-aware, but the instance
134
+ # of IterableArray should not be modified from within a `#sort!`
135
+ # block:
136
+ # quoth the pickaxe: _arr_ is effectively frozen while a sort is in progress
137
+ def sort!
138
+ return @array.sort! unless @current_index.between? 0, @array.size.pred
139
+
140
+ current_item = @array.at @current_index
141
+ offset = @array.to_a[0...@current_index].count current_item
142
+
143
+ @array.sort!
144
+
145
+ center_indices_at (@array.to_a.index(current_item) + offset)
146
+
147
+ self
148
+ end
149
+
150
+ # untested
151
+ # `#sort_by!` modification is iterative-aware, but the instance
152
+ # of IterableArray should not be modified from within a `#sort_by!`
153
+ # block:
154
+ # quoth the pickaxe: _arr_ is effectively frozen while a sort is in progress
155
+ def sort_by! &block
156
+ return @array.to_enum(:sort_by!) unless block_given?
157
+
158
+ current_item = @array.at @current_index
159
+ offset = @array.to_a[0...@current_index].count current_item
160
+
161
+ @array.sort_by! &block
162
+
163
+ center_indices_at (@array.to_a.index(current_item) + offset)
164
+
165
+ self
166
+ end
167
+
168
+ def shuffle!
169
+ return @array.shuffle! unless @current_index.between? 0, @array.size.pred
170
+
171
+ current_item = @array.at @current_index
172
+ count = @array.to_a.count current_item
173
+
174
+ @array.shuffle!
175
+
176
+ locations = []
177
+ @array.to_a.each_with_index { |item, location| locations << location if item == current_item }
178
+
179
+ center_indices_at locations.sample
180
+
181
+ self
182
+ end
183
+
184
+ def swap! *args
185
+ args.map! { |x| index x }
186
+ swap_indices! *args
187
+ end
188
+
189
+ def swap_indices! *args
190
+ args.inject { |i1, i2| swap_2_indices!(i1, i2); i2 }
191
+ self
192
+ end
193
+ alias_method :swap_indexes!, :swap_indices!
194
+
195
+ # untested
196
+ def uniq!
197
+ basket = []
198
+ delete_if do |x|
199
+ if basket.include? x
200
+ true
201
+ else
202
+ basket << x
203
+ false
204
+ end
205
+ end
206
+ self
207
+ end
208
+
209
+ protected
210
+ def swap_2_indices! arg1, arg2
211
+ temp_holder = @current_index
212
+ center_indices_at arg1 if temp_holder == arg2
213
+ center_indices_at arg2 if temp_holder == arg1
214
+ @array.swap_indices! arg1, arg2
215
+ end
216
+
217
+ private
218
+ def center_indices_at new_index
219
+ movement = @current_index - new_index
220
+ @current_index -= movement
221
+ @forward_index -= movement
222
+ @backward_index -= movement
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,104 @@
1
+ class IterableArray
2
+ module SpecialModifiersNoniterating
3
+ # untested
4
+ def compact!
5
+ @array.compact!
6
+ self
7
+ end
8
+
9
+ def clear
10
+ @array.clear
11
+ self
12
+ end
13
+
14
+ def shift n = nil
15
+ return @array.shift if n.nil?
16
+ IterableArray.new @array.shift(n)
17
+ end
18
+
19
+ def insert location, *items
20
+ @array.insert location, *items
21
+ self
22
+ end
23
+
24
+ # untested
25
+ def move element, to
26
+ @array.move element, to
27
+ self
28
+ end
29
+
30
+ # untested
31
+ def move_from from, to
32
+ @array.move_from from, to
33
+ self
34
+ end
35
+
36
+ # untested
37
+ def flatten! level = -1
38
+ @array.flatten! level
39
+ self
40
+ end
41
+
42
+ def pop n = nil
43
+ return @array.pop if n.nil?
44
+ IterableArray.new(@array.pop n)
45
+ end
46
+
47
+ def reverse!
48
+ @array.reverse!
49
+ self
50
+ end
51
+
52
+ # untested
53
+ def rotate! n = 1
54
+ @array.rotate! n
55
+ self
56
+ end
57
+
58
+ def sort!
59
+ @array.sort!
60
+ self
61
+ end
62
+
63
+ # untested
64
+ def sort_by! &block
65
+ return @array.to_enum(:sort_by!) unless block_given?
66
+ @array.sort_by! &block
67
+ self
68
+ end
69
+
70
+ def shuffle!
71
+ @array.shuffle!
72
+ self
73
+ end
74
+
75
+ def slice! *args
76
+ if args.length == 2 or args.at(1).kind_of? Range
77
+ IterableArray.new @array.slice!(*args)
78
+ else
79
+ @array.slice!(*args)
80
+ end
81
+ end
82
+
83
+ def unshift *args
84
+ @array.insert 0, *args
85
+ self
86
+ end
87
+
88
+ def swap! *args
89
+ @array.swap! *args
90
+ self
91
+ end
92
+
93
+ def swap_indices! *args
94
+ @array.swap_indices! *args
95
+ self
96
+ end
97
+
98
+ # untested
99
+ def uniq!
100
+ @array.uniq!
101
+ self
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,47 @@
1
+ module Swapable
2
+ def to_iter
3
+ IterableArray.new self
4
+ end
5
+
6
+ def to_a
7
+ self
8
+ end
9
+
10
+ def swap_2_indices! arg1, arg2
11
+ temper = at arg1
12
+ self[arg1] = at arg2
13
+ self[arg2] = temper
14
+ self
15
+ end
16
+ protected :swap_2_indices!
17
+
18
+ def swap! *args
19
+ args.map! { |x| index x }
20
+ swap_indices! *args
21
+ end
22
+
23
+ def swap_indices! *args
24
+ args.inject { |i1, i2| swap_2_indices! i1, i2; i2 }
25
+ self
26
+ end
27
+ alias_method :swap_indexes!, :swap_indices!
28
+
29
+ def swap *args
30
+ dup.extend(Swapable).swap! *args
31
+ end
32
+
33
+ def swap_indices *args
34
+ dup.extend(Swapable).swap_indices! *args
35
+ end
36
+ alias_method :swap_indexes, :swap_indices
37
+
38
+ def move_from from, to
39
+ element = delete_at from
40
+ insert to, element
41
+ end
42
+
43
+ def move element, to
44
+ delete_at index element
45
+ insert to, element
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: iterable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.6.pre
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Scott L Steele
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-13 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ''
15
+ email: ScottLSteele@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/iterable_array/iterator_specials.rb
21
+ - lib/iterable_array/special_modifiers_noniterating.rb
22
+ - lib/iterable_array/special_modifiers_iterating.rb
23
+ - lib/iterable_array/special_accessors.rb
24
+ - lib/iterable_array/plain_modifiers.rb
25
+ - lib/iterable_array/iterators.rb
26
+ - lib/swapable.rb
27
+ - lib/iterable.rb
28
+ - lib/iterable_array.rb
29
+ homepage: https://github.com/scooter-dangle/iterable
30
+ licenses:
31
+ - ''
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>'
46
+ - !ruby/object:Gem::Version
47
+ version: 1.3.1
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 1.8.10
51
+ signing_key:
52
+ specification_version: 3
53
+ summary: Provides a fully iterable array object
54
+ test_files: []
55
+ has_rdoc: