Array.extend versus instance.extend

I want to install ‘shift_until_kind_of’ in the global Array class
so that I can do like this:

ary = [1, 2, "a", 3, 4, "b", 5, 6]
assert_equal([1, 2], ary.shift_until_kind_of(String))

But extending the Array class does not work.
How should I extend the global Array class ?

module ArrayMisc
def shift_until_kind_of(klass)
res = []
loop do
data = self.first
break if (data == nil) || data.kind_of?(klass)
res << data
self.shift
end
res
end
end

def test_shift_until
#Array.extend ArrayMisc # BOOM, does not work
ary = [1, 2, “a”, 3, 4, “b”, 5, 6]
ary.extend ArrayMisc
res = ary.shift_until_kind_of(String)
assert_equal([1, 2], res)
assert_equal([“a”, 3, 4, “b”, 5, 6], ary)
end

···


Simon Strandgaard

Simon Strandgaard wrote:

I want to install ‘shift_until_kind_of’ in the global Array class
so that I can do like this:

ary = [1, 2, "a", 3, 4, "b", 5, 6]
assert_equal([1, 2], ary.shift_until_kind_of(String))

But extending the Array class does not work.
How should I extend the global Array class ?

You need to include the module for #shift_until_kind_of to be found
among the instance methods of Array.

By contrast, if you extend Array with your module, its methods will be
added as methods of the Array object itself (an instance of Class).

However, calling Array.include will not work, since include is private.
You can always get around this with something like:

Array.send :include, ArrayMisc

Or you can use class_eval (or module_eval, which is the same), or simply

class Array; include ArrayMisc; end

Here is my code so far… you welcome to rip it.
Comments would be nice :slight_smile:

ruby test_misc.rb

TestMisc#test_pop_until .
TestMisc#test_pop_until_non_existing_class .
TestMisc#test_pop_until_regex .
TestMisc#test_shift_until .
TestMisc#test_shift_until_non_existing_class .
TestMisc#test_shift_until_regex .
Time: 0.009481
OK (6/6 tests 12 asserts)

expand -t4 test_misc.rb
require ‘misc’
require ‘runit/testcase’
require ‘runit/assert’
require ‘runit/cui/testrunner’

#Array.extend ArrayMisc # BOOM, why does this not work ?
class Array; include ArrayMisc; end

class TestMisc < RUNIT::TestCase
def test_shift_until
ary = [1, 2, “a”, 3, 4, “b”, 5, 6]
res = ary.shift_until(String)
assert_equal([1, 2], res)
assert_equal([“a”, 3, 4, “b”, 5, 6], ary)
end
def test_shift_until_non_existing_class
ary = [1, 2, “a”, 3, 4, “b”, 5, 6]
res = ary.shift_until(IO)
assert_equal(, ary)
assert_equal([1, 2, “a”, 3, 4, “b”, 5, 6], res)
end
def test_shift_until_regex
ary = [1, 2, “a”, 3, 4, “b”, 5, 6]
res = ary.shift_until(/^b/)
assert_equal([“b”, 5, 6], ary)
assert_equal([1, 2, “a”, 3, 4], res)
end
def test_pop_until
ary = [1, 2, “a”, 3, 4, “b”, 5, 6]
res = ary.pop_until(String)
assert_equal([5, 6], res)
assert_equal([1, 2, “a”, 3, 4, “b”], ary)
end
def test_pop_until_non_existing_class
ary = [1, 2, “a”, 3, 4, “b”, 5, 6]
res = ary.pop_until(IO)
assert_equal(, ary)
assert_equal([1, 2, “a”, 3, 4, “b”, 5, 6], res)
end
def test_pop_until_regex
ary = [1, 2, “a”, 3, 4, “b”, 5, 6]
res = ary.pop_until(/^a/)
assert_equal([1, 2, “a”], ary)
assert_equal([3, 4, “b”, 5, 6], res)
end
end

if $0 == FILE
RUNIT::CUI::TestRunner.run(TestMisc.suite)
end

expand -t4 misc.rb
module ArrayMisc
def shift_until(klass)
res =
while length != 0 and not klass === first
res << self.shift
end
res
end
def pop_until(klass)
res =
while length != 0 and not klass === last
res.unshift(self.pop)
end
res
end
end

···


Simon Strandgaard

Here is my code so far… you welcome to rip it.
Comments would be nice :slight_smile:

Looks good to me. You might want to be consistent with either including
‘self’ as the receiver, or omitting it: i.e.

        res << self.shift

can be just “res << shift”; or else change “length” to “self.length” etc.

If this module only makes sense within an Array, then you could change

module ArrayMisc

to

class Array

i.e. just define the methods directly within it. Depends whether you come
across anything else which supports ‘shift / pop / first / last / length’
semantics and therefore might have a use for this as a module.

Cheers,

Brian.

···

On Thu, May 29, 2003 at 07:40:14PM +0900, Simon Strandgaard wrote:

Another idea, “length != 0” could be replaced with “not empty?”

Cheers,

Brian.

I take that back. Your approach is better, because it lets you add these
methods to individual arrays (using ‘extend’) without polluting the whole
Array class.

Cheers,

Brian.

···

On Thu, May 29, 2003 at 07:53:44PM +0900, Brian Candler wrote:

If this module only makes sense within an Array, then you could change

module ArrayMisc

to

class Array

Using ‘until’ instead of ‘while’ does reduce it even further :slight_smile:

module ArrayMisc
def shift_until(klass)
res =
until empty? or klass === first
res << self.shift
end
res
end
def pop_until(klass)
res =
until empty? or klass === last
res.unshift(self.pop)
end
res
end
end

···

On Thu, 29 May 2003 20:56:34 +0900, Brian Candler wrote:

Another idea, “length != 0” could be replaced with “not empty?”


Simon Strandgaard

This is faster:
module ArrayMisc2
def shift_until(klass)
p = -1
detect { |x| p += 1; klass === x }
slice!(0, p)
end
def pop_until(klass)
p = -1
reverse.detect { |x| p += 1; klass === x }
slice!(-p, p)
end
end

Guillaume.

Speed test:
[gus@comp ruby]$ cat array.rb
#! /usr/bin/ruby

module ArrayMisc
def shift_until(klass)
res =
until empty? or klass === first
res << self.shift
end
res
end
def pop_until(klass)
res =
until empty? or klass === last
res.unshift(self.pop)
end
res
end
end

module ArrayMisc2
def shift_until(klass)
p = -1
detect { |x| p += 1; klass === x }
slice!(0, p)
end
def pop_until(klass)
p = -1
reverse.detect { |x| p += 1; klass === x }
slice!(-p, p)
end
end

def equi_test(src, max, ext1, ext2)
1.upto(10) do |i|
b1 = src.dup
b2 = src.dup
b1.extend(ext1)
b2.extend(ext2)
c1 = b1.shift_until(max / i)
c2 = b2.shift_until(max / i)
puts(“shift %d: %s %s” % [max / i, b1 == b2, c1 == c2])

b1 = src.dup
b2 = src.dup
b1.extend(ext1)
b2.extend(ext2)
c1 = b1.pop_until(max / i)
c2 = b2.pop_until(max / i)
puts("pop %d: %s %s" % [max / i, b1 == b2, c1 == c2])

end
end

def speed_test(src, max, ext)
1.upto(10) do |i|
b = src.dup
b.extend(ext)
t = Time.now
b.shift_until(max / i)
t = Time.now - t
puts(“shift %d: %s” % [max / i, t.to_s])

b = src.dup
b.extend(ext)
t = Time.now
b.pop_until(max / i)
t = Time.now - t
puts("pop %d: %s" % [max / i, t.to_s])

end
end

max = 10000
a = Array.new
1.upto(max) do |i| a << i end

puts(“Equi test”)
equi_test(a, max, ArrayMisc, ArrayMisc2)

puts(“Speed test with ArrayMisc”)
speed_test(a, max, ArrayMisc)

puts(“Speed test with ArrayMisc2”)
speed_test(a, max, ArrayMisc2)

[gus@comp ruby]$ ruby array.rb
Equi test
shift 10000: true true
pop 10000: true true
shift 5000: true true
pop 5000: true true
shift 3333: true true
pop 3333: true true
shift 2500: true true
pop 2500: true true
shift 2000: true true
pop 2000: true true
shift 1666: true true
pop 1666: true true
shift 1428: true true
pop 1428: true true
shift 1250: true true
pop 1250: true true
shift 1111: true true
pop 1111: true true
shift 1000: true true
pop 1000: true true
Speed test with ArrayMisc
shift 10000: 0.582758
pop 10000: 3.7e-05
shift 5000: 0.46754
pop 5000: 0.116931
shift 3333: 0.343047
pop 3333: 0.243035
shift 2500: 0.268995
pop 2500: 0.313689
shift 2000: 0.2221
pop 2000: 0.365074
shift 1666: 0.189278
pop 1666: 0.398018
shift 1428: 0.162366
pop 1428: 0.423032
shift 1250: 0.14342
pop 1250: 0.440438
shift 1111: 0.127911
pop 1111: 0.454871
shift 1000: 0.116714
pop 1000: 0.46972
Speed test with ArrayMisc2
shift 10000: 0.044474
pop 10000: 0.000476
shift 5000: 0.022319
pop 5000: 0.022654
shift 3333: 0.015035
pop 3333: 0.030153
shift 2500: 0.011277
pop 2500: 0.034355
shift 2000: 0.009064
pop 2000: 0.036037
shift 1666: 0.007574
pop 1666: 0.037444
shift 1428: 0.00655
pop 1428: 0.038519
shift 1250: 0.005707
pop 1250: 0.039406
shift 1111: 0.005158
pop 1111: 0.040032
shift 1000: 0.004618
pop 1000: 0.040485

···

On Thursday 29 May 2003 07:20 am, you wrote:

On Thu, 29 May 2003 20:56:34 +0900, Brian Candler wrote:

Another idea, “length != 0” could be replaced with “not empty?”

Using ‘until’ instead of ‘while’ does reduce it even further :slight_smile:

module ArrayMisc
def shift_until(klass)
res =
until empty? or klass === first
res << self.shift
end
res
end
def pop_until(klass)
res =
until empty? or klass === last
res.unshift(self.pop)
end
res
end
end

module ArrayMisc2
def shift_until(klass)
p = -1
detect { |x| p += 1; klass === x }
slice!(0, p)
end
def pop_until(klass)
p = -1
reverse.detect { |x| p += 1; klass === x }
slice!(-p, p)
end
end

Sorry your code does not pass all the tests… it has somekind of
different behavier. I have never tried using ‘Enumable#detect’ before
so I cannot figure out exactly what seems to be the problem.
Thanks for trying :slight_smile:

ruby test_misc.rb

TestMisc#test_pop_until .
TestMisc#test_pop_until_non_existing_class F.
TestMisc#test_pop_until_regex .
TestMisc#test_shift_until .
TestMisc#test_shift_until_non_existing_class F.
TestMisc#test_shift_until_regex .
Time: 0.015673
FAILURES!!!
Test Results:
Run: 6/6(10 asserts) Failures: 2 Errors: 0
Failures: 2
test_misc.rb:37:in test_pop_until_non_existing_class'(TestMisc): expected:<[]> but was:<[1]> (RUNIT::AssertionFailedError) from test_misc.rb:49 test_misc.rb:19:in test_shift_until_non_existing_class’(TestMisc): expected:<> but was:<[6]> (RUNIT::AssertionFailedError)
from test_misc.rb:49

The test cases looks like this:

def test_shift_until_non_existing_class
    ary = [1, 2, "a", 3, 4, "b", 5, 6]
    res = ary.shift_until(IO)
    assert_equal([], ary)    # BOOM
    assert_equal([1, 2, "a", 3, 4, "b", 5, 6], res)
end
def test_pop_until_non_existing_class
    ary = [1, 2, "a", 3, 4, "b", 5, 6]
    res = ary.pop_until(IO)
    assert_equal([], ary)    # BOOM
    assert_equal([1, 2, "a", 3, 4, "b", 5, 6], res)
end
···

On Fri, 30 May 2003 01:15:32 +0900, Guillaume Marcais wrote:


Simon Strandgaard

Hi –

module ArrayMisc2
def shift_until(klass)
p = -1
detect { |x| p += 1; klass === x }
slice!(0, p)
end
def pop_until(klass)
p = -1
reverse.detect { |x| p += 1; klass === x }
slice!(-p, p)
end
end

Sorry your code does not pass all the tests… it has somekind of
different behavier. I have never tried using ‘Enumable#detect’ before
so I cannot figure out exactly what seems to be the problem.
Thanks for trying :slight_smile:

#detect returns the first element for which the block is true, passing
each element to the block in turn:

[1,2,3,4].detect {|e| e > 2} # 3

Here’s a modification of Guillaume’s code that passes your tests:

module ArrayMisc2A
def shift_until(klass)
p = 0
detect { |x| p += 1 and klass === x }
slice!(0, p)
end

def pop_until(klass)
  p = 0
  reverse.detect { |x| p += 1 and klass === x }
  slice!(-p, p)
end

end

David

···

On Fri, 30 May 2003, Simon Strandgaard wrote:

On Fri, 30 May 2003 01:15:32 +0900, Guillaume Marcais wrote:


David Alan Black
home: dblack@superlink.net
work: blackdav@shu.edu
Web: http://pirate.shu.edu/~blackdav

Here’s a modification of Guillaume’s code that passes your tests:

I ran the test-suite on your code and it failed.
I have adapted+adjusted the code so it now works properly :slight_smile:

module ArrayMisc
def shift_until(klass)
p = 0
detect do |x|
if klass === x
true
else
p += 1
false
end
end
slice!(0, p)
end
def pop_until(klass)
p = 0
reverse.detect do |x|
if klass === x
true
else
p += 1
false
end
end
slice!(-p, p)
end
end

ruby test_misc.rb

TestMisc#test_pop_until .
TestMisc#test_pop_until_non_existing_class .
TestMisc#test_pop_until_regex .
TestMisc#test_shift_until .
TestMisc#test_shift_until_non_existing_class .
TestMisc#test_shift_until_regex .
Time: 0.011196
OK (6/6 tests 12 asserts)

My earlier code says ‘Time: 0.007166’… so this
is a slower solution.

···

On Fri, 30 May 2003 11:41:21 +0900, dblac wrote:


Simon Strandgaard

My earlier code says ‘Time: 0.007166’… so this
is a slower solution.

Sorry not quite true :slight_smile:

I ran Guillaume’s speed test on the code

ArrayMisc == the old code (not using detect)
ArrayMisc2 == new code (using detect)

All pop operations within ArrayMisc is slow.
But shift operations seems to be faster with ArrayMisc.

ruby spt.rb
Speed test with ArrayMisc
shift 10000: 0.068033
pop 10000: 7.099999999999999e-05
shift 5000: 0.029729
pop 5000: 0.218009
shift 3333: 0.01859
pop 3333: 0.412474
shift 2500: 0.013405
pop 2500: 0.540523
shift 2000: 0.011306
pop 2000: 0.629184
shift 1666: 0.00953
pop 1666: 0.730164
shift 1428: 0.007726999999999999
pop 1428: 0.729752
shift 1250: 0.006794
pop 1250: 0.78048
shift 1111: 0.006069
pop 1111: 0.778281
shift 1000: 0.005391
pop 1000: 0.816113
Speed test with ArrayMisc2
shift 10000: 0.077073
pop 10000: 0.001267
shift 5000: 0.043104
pop 5000: 0.047606
shift 3333: 0.022285
pop 3333: 0.057452
shift 2500: 0.016819
pop 2500: 0.074443
shift 2000: 0.013847
pop 2000: 0.06378299999999999
shift 1666: 0.014762
pop 1666: 0.065674
shift 1428: 0.010433
pop 1428: 0.069156
shift 1250: 0.009172999999999999
pop 1250: 0.070936
shift 1111: 0.008364999999999999
pop 1111: 0.07146
shift 1000: 0.007538
pop 1000: 0.08437799999999999

···

On Fri, 30 May 2003 11:16:14 +0200, Simon Strandgaard wrote:


Simon Strandgaard

Hi –

Here’s a modification of Guillaume’s code that passes your tests:

I ran the test-suite on your code and it failed.

Try again :slight_smile: It shouldn’t fail.

Here’s what I ran, cut-and-pasted from your message and my message,
plus the TestUnit wrapper:

module ArrayMisc2A
def shift_until(klass)
p = 0
detect { |x| p += 1 and klass === x }
slice!(0, p)
end

def pop_until(klass)
  p = 0
  reverse.detect { |x| p += 1 and klass === x }
  slice!(-p, p)
end

end

class Array; include ArrayMisc2A; end

require ‘test/unit’

class TestMe < Test::Unit::TestCase
def test_shift_until_non_existing_class
ary = [1, 2, “a”, 3, 4, “b”, 5, 6]
res = ary.shift_until(IO)
assert_equal(, ary) # BOOM
assert_equal([1, 2, “a”, 3, 4, “b”, 5, 6], res)
end
def test_pop_until_non_existing_class
ary = [1, 2, “a”, 3, 4, “b”, 5, 6]
res = ary.pop_until(IO)
assert_equal(, ary) # BOOM
assert_equal([1, 2, “a”, 3, 4, “b”, 5, 6], res)
end
end

Output:

$ ruby shifttwo.rb
Loaded suite shifttwo
Started…

Finished in 0.002075 seconds.
2 runs, 4 assertions, 0 failures, 0 errors

David

···

On Fri, 30 May 2003, Simon Strandgaard wrote:

On Fri, 30 May 2003 11:41:21 +0900, dblac wrote:


David Alan Black
home: dblack@superlink.net
work: blackdav@shu.edu
Web: http://pirate.shu.edu/~blackdav

I ran the test-suite on your code and it failed.

Try again :slight_smile: It shouldn’t fail.

Here’s what I ran, cut-and-pasted from your message and my message,
plus the TestUnit wrapper:

Yes it works with these 2 test cases… but there is a few more test-cases
it has to work with, see:

http://ruby-talk.org/72408

class TestMe < Test::Unit::TestCase
def test_shift_until_non_existing_class
[snip]
def test_pop_until_non_existing_class
[snip]
end

Sorry for causing confusion :slight_smile:

···

On Fri, 30 May 2003 19:48:55 +0900, dblac wrote:

On Fri, 30 May 2003, Simon Strandgaard wrote:


Simon Strandgaard

OK, my fault. The following code should pass your test and is faster
most of the time. Pop_until, because of the reverse, is slower when the
search ends early.

Guillaume.

[gus@gusmac test]$ cat misc2.rb
module ArrayMisc2
def shift_until(klass)
p = -1
detect { |x| p += 1; klass === x } or p += 1
slice!(0, p)
end
def pop_until(klass)
p = -1
reverse.detect { |x| p += 1; klass === x } or p += 1
slice!(-p, p)
end
end

[gus@gusmac test]$ cat speed_test.rb
require ‘misc’
require ‘misc2’

def speed_test(src, max, ext1, ext2)
1.upto(10) do |i|
b1 = src.dup
b2 = src.dup
b1.extend(ext1)
b2.extend(ext2)
t1 = Time.now
b1.shift_until(max / i)
t1 = Time.now - t1
t2 = Time.now
b2.shift_until(max / i)
t2 = Time.now - t2
puts(“shift %d: 1: %s 2: %s” % [max / i, t1.to_s, t2.to_s])

 b1 = src.dup
 b2 = src.dup
 b1.extend(ext1)
 b2.extend(ext2)
 t1 = Time.now
 b1.pop_until(max / i)
 t1 = Time.now - t1
 t2 = Time.now
 b2.pop_until(max / i)
 t2 = Time.now - t2
 puts("pop %d: 1: %s 2: %s" % [max / i, t1.to_s, t2.to_s])

end
end

max = 10000
a = Array.new
1.upto(max) do |i| a << i end

speed_test(a, max, ArrayMisc, ArrayMisc2)

[gus@gusmac test]$ ruby speed_test.rb
shift 10000: 1: 0.220421 2: 0.080113
pop 10000: 1: 4.5e-05 2: 0.000446
shift 5000: 1: 0.245843 2: 0.038756
pop 5000: 1: 0.05908 2: 0.076305
shift 3333: 1: 0.182882 2: 0.027826
pop 3333: 1: 0.087186 2: 0.086682
shift 2500: 1: 0.144765 2: 0.020657
pop 2500: 1: 0.199623 2: 0.060441
shift 2000: 1: 0.129832 2: 0.017254
pop 2000: 1: 0.177329 2: 0.064388
shift 1666: 1: 0.082475 2: 0.048194
pop 1666: 1: 0.172718 2: 0.076809
shift 1428: 1: 0.153042 2: 0.016205
pop 1428: 1: 0.179198 2: 0.06833
shift 1250: 1: 0.075859 2: 0.009778
pop 1250: 1: 0.192854 2: 0.070832
shift 1111: 1: 0.061179 2: 0.008301
pop 1111: 1: 0.240657 2: 0.073334
shift 1000: 1: 0.397667 2: 0.007408
pop 1000: 1: 0.20591 2: 0.077756

:

···

Le vendredi, 30 mai 2003, à 07:27 US/Eastern, Simon Strandgaard a écrit

On Fri, 30 May 2003 19:48:55 +0900, dblac wrote:

On Fri, 30 May 2003, Simon Strandgaard wrote:

I ran the test-suite on your code and it failed.

Try again :slight_smile: It shouldn’t fail.

Here’s what I ran, cut-and-pasted from your message and my message,
plus the TestUnit wrapper:

Yes it works with these 2 test cases… but there is a few more
test-cases
it has to work with, see:

http://ruby-talk.org/72408

class TestMe < Test::Unit::TestCase
def test_shift_until_non_existing_class
[snip]
def test_pop_until_non_existing_class
[snip]
end

Sorry for causing confusion :slight_smile:


Simon Strandgaard

OK, my fault. The following code should pass your test and is faster
most of the time. Pop_until, because of the reverse, is slower when the
search ends early.

Yes now it works… I am not yet 100% familiar with the ‘or’ thing. I would
definitly not have found out that myself. Thanks for new knowledge.

module ArrayMisc2
def shift_until(klass)
p = -1
detect { |x| p += 1; klass === x } or p += 1
slice!(0, p)
end
def pop_until(klass)
p = -1
reverse.detect { |x| p += 1; klass === x } or p += 1
slice!(-p, p)
end
end

This ‘detectj+slice’ concept is good… reduces the number of temporary
objects a lot. I have completely adapted it.

speed_test(a, max, ArrayMisc, ArrayMisc2)
[snip results data]

Nice speed comparison… you have improved it further,
so that the results is 2 adjacent columns :slight_smile:

BTW: Is there any speed-testing frameworks around ?

···

On Sat, 31 May 2003 14:26:53 +0900, Guillaume Marcais wrote:


Simon Strandgaard

Hi –

···

On Sat, 31 May 2003, Simon Strandgaard wrote:

BTW: Is there any speed-testing frameworks around ?

The Benchmark module (lib/benchmark.rb in the source distribution).

David


David Alan Black
home: dblack@superlink.net
work: blackdav@shu.edu
Web: http://pirate.shu.edu/~blackdav

OK… im looking at it. I like what I see here
(very good documentation :slight_smile: I think I will use it alot in the future.

I have gotton so much great feedback and all my questions has been
answered 600%. Thanks everyone.

···

On Sat, 31 May 2003 16:12:14 +0900, dblac wrote:

On Sat, 31 May 2003, Simon Strandgaard wrote:

BTW: Is there any speed-testing frameworks around ?

The Benchmark module (lib/benchmark.rb in the source distribution).


Simon Strandgaard