Ruby quickies and useful idioms

S

Sam Stephenson

There's a few trivial but useful "extensions" to Ruby's standard
library that I find myself using in most of my projects. I'll share a
couple, and I'd really love to see what you're using, too.

Array folding is a functional idiom in which each item is evaluated in
some way and its result accumulated, returning the final value of the
accumulator. It's simple in Ruby:

| class Array
| def foldl(accum, &block)
| each {|value| accum = yield(accum, value)}
| return accum
| end
| def foldr(accum, &block)
| reverse.foldl(accum, &block)
| end
| alias fold :foldl
| end

The canonical example is summing values:

| r = (1..100).to_a
| p r.foldl(0) {|a,v| a += v}
=> 5050
| p r.foldr(5050) {|a,v| a -= v}
=> 0

A more interesting and useful application involves boolean logic. For
example, the following snippet ensures all arguments passed to
FooSet#new are of type Foo:

| class Foo; end
| class FooSet
| def initialize(*values)
| raise TypeError unless
| values.fold(true) {|a,v| a and v.is_a? Foo}
| # ...
| end
| end

Another quickie I'm fond of assists in what Josh Bloch calls
``defensive copying'' in his book ``Effective Java.'' The idea is
that you should always return copies of mutable instance variables if
you aren't prepared to have users of your class modify those
variables.

For instance, if I have a class which contains an array, and I want to
make that array accessible from outside but not modifiable,
attr_reader won't be sufficient -- I need to call Array#dup and return
that. Once again, it's trivial in Ruby to define a helper to do just
that:

| class Class
| def attr_defender(*symbols)
| symbols.each do |symbol|
| class_eval "def #{symbol}; @#{symbol}.dup; end"
| end
| end
| end

Here's how I might use it:

| class Container
| def initialize(*values)
| @values = values
| @length = values.length
| end
| attr_defender :values
| attr_reader :length
| end

Container#values returns a new object each time:

| c = Container.new('foo', 'bar', 'baz')
| p c.values.__id__
=> 402800548
| p c.values.__id__
=> 402800512

So now I can be lazy and do whatever I want with the copy.

Please share your quickies!

Sam
 
G

Gavin Sinclair

There's a few trivial but useful "extensions" to Ruby's standard
library that I find myself using in most of my projects. I'll share a
couple, and I'd really love to see what you're using, too. [.....]

Hi Sam,

'foldl' is, I believe, the same as Enumeration#inject. Try checking
out the docs for that, but:

(1..100).inject(0) { |acc,x| acc + x }

That "defensive attribute reader" was interesting; thanks.

For more of these, check out

www.rubygarden.org/ruby?StandardClassExtentions

Some of those, and others besides, are coded up in the 'extensions'
project on RubyForge, for which I'll gratefully receive suggestions.

Cheers,
Gavin
 
M

Mikael Brockman

Sam Stephenson said:
There's a few trivial but useful "extensions" to Ruby's standard
library that I find myself using in most of my projects. I'll share a
couple, and I'd really love to see what you're using, too.

[...]

Please share your quickies!

These threads make the util.rb files from my long-abandoned projects
feel useful again.

Here's my take on Lisp's PROG1:

| def before_returning (value, &action)
| action.call
| return value
| end

And an example:

| class IdSupply
| def initialize
| @id = 0
| end
|
| def next
| before_returning @id do
| @id += 1
| end
| end
| end

Here's a convenient abstraction of some uses of Array#each:

| def each_call (id, things)
| things.each do |thing|
| self.send id, thing
| end
| end

And an example:

| things = [1, 2, 3, 4, 5, 6, 7, 8]
| unwanted = [3, 6, 2, 1, 5]
| things.each_call :delete, unwanted

Here's my take on prototype-based programming:

| def add_method (id, &closure)
| (class << self; self; end).send:)define_method, id, &closure)
| end

And an example:

| foo = [1, 2, 3]
| foo.add_method :frob do |x|
| self[x] * self.length
| end
|
| # foo.frob 1 => 6

Here's some shuffling stuff:

| class Array
| def shuffle!
| each_index do |j|
| i = rand(size - j)
| self[j], self[j+i] = self[j+i], self[j]
| end
| self
| end
|
| def shuffle
| self.clone.shuffle!
| end
| end

And that's it.

mikael
 
G

gabriele renzi

il Mon, 12 Jul 2004 07:55:58 +0900, Mikael Brockman
Here's some shuffling stuff:

| class Array
| def shuffle!
| each_index do |j|
| i = rand(size - j)
| self[j], self[j+i] = self[j+i], self[j]
| end
| self
| end
|
| def shuffle
| self.clone.shuffle!
| end
| end

And that's it.

the best shuffle I can think of is the Grossian shuffle (quoting
Flroain Gross (actually written with a different character than ss):

class Array
def shuffle
sort_by {rand}
end
end

You may even found extensions.rubyforge.org interesting
 
M

Mikael Brockman

Mikael Brockman said:
Here's my take on prototype-based programming:

| def add_method (id, &closure)
| (class << self; self; end).send:)define_method, id, &closure)
| end

And an example:

| foo = [1, 2, 3]
| foo.add_method :frob do |x|
| self[x] * self.length
| end
|
| # foo.frob 1 => 6

I just took that verbatim from a utility file of mine, forgetting what
its actual purpose is.

The difference between Object#add_method and class << foo is that the
former takes a closure, so it can use variables from its lexical scope.

mikael
 
S

Sam Stephenson

'foldl' is, I believe, the same as Enumeration#inject. Try checking
out the docs for that, but:

(1..100).inject(0) { |acc,x| acc + x }

Wow, I didn't know about Enumerable#inject. Semantically, it's not
quite the same as foldl: for instance, it doesn't make sense to foldl
(or foldr) a Hash, since you're not guaranteed any order. Practically
speaking, however, it's much nicer, since I can use it with Range.

As an aside: It would be nice to have a subclass of Enumerable that
guarantees item order; Array, Range, et al could mix it in.
For more of these, check out

www.rubygarden.org/ruby?StandardClassExtentions

Some of those, and others besides, are coded up in the 'extensions'
project on RubyForge, for which I'll gratefully receive suggestions.

Cool, thanks!

Sam
 
M

Mikael Brockman

gabriele renzi said:
il Mon, 12 Jul 2004 07:55:58 +0900, Mikael Brockman
Here's some shuffling stuff:

| class Array
| def shuffle!
| each_index do |j|
| i = rand(size - j)
| self[j], self[j+i] = self[j+i], self[j]
| end
| self
| end
|
| def shuffle
| self.clone.shuffle!
| end
| end

And that's it.

the best shuffle I can think of is the Grossian shuffle (quoting
Flroain Gross (actually written with a different character than ss):

class Array
def shuffle
sort_by {rand}
end
end

You may even found extensions.rubyforge.org interesting

It's very nifty indeed. I heard it was biased in some obscure way,
though.

mikael
 
G

Gavin Sinclair

It's very nifty indeed. I heard it was biased in some obscure way,
though.

It is: biased in favour of suggestions I receive :) The only major
suggestion I knocked back was a C implementation.

Gavin
 
M

Mikael Brockman

Gavin Sinclair said:
It is: biased in favour of suggestions I receive :) The only major
suggestion I knocked back was a C implementation.

Haha, I was referring to sort_by { rand }.

mikael
 
F

Florian Gross

Mikael said:
gabriele renzi said:
[some shuffling stuff]
the best shuffle I can think of is the Grossian shuffle (quoting
Flroain Gross (actually written with a different character than ss):

It's actually "Florian Groß", but I've seen the last character come out
as an y with two dots above it which is why I usually use "Gross" where
I can't be sure about it coming out right. ;)

Oh, and thanks for naming this after me. :)
It's very nifty indeed. I heard it was biased in some obscure way,
though.

It's biased when rand returns the same float for two or more elements of
the Array.

This means that it will be ~0.012% biased on an Array that has one
million entries and ~0.0001% biased on an Array with 100000 entries when
you're on a 32-bit machine.

On 64-bit machines you will have a 50% chance of one element being
biased for an Array with 5,000,000,000 elements.

If that's still to likely for you you can use sort_by { Array.new(100) {
rand }} instead.

The problem with all this is that people frequently confuse sort_by {
rand } with sort { rand } and variants of it.

Oh, and it might even make sense to add it to Enumerable instead of
Arrays. After all #sort_by is defined for all Enumerables.

Regards,
Florian Gross
 
G

gabriele renzi

il Mon, 12 Jul 2004 08:15:13 +0900, Sam Stephenson
As an aside: It would be nice to have a subclass of Enumerable that
guarantees item order; Array, Range, et al could mix it in.

post an rcr about it or write your own module including Enumerable :)
 
M

Martin DeMello

Sam Stephenson said:
Wow, I didn't know about Enumerable#inject. Semantically, it's not
quite the same as foldl: for instance, it doesn't make sense to foldl
(or foldr) a Hash, since you're not guaranteed any order. Practically
speaking, however, it's much nicer, since I can use it with Range.

My favourite 'practical' feature is that it tests for a seed value, and
defaults to foldl1 if none is given.

martin
 
M

Martin DeMello

Sam Stephenson said:
There's a few trivial but useful "extensions" to Ruby's standard
library that I find myself using in most of my projects. I'll share a
couple, and I'd really love to see what you're using, too.

I've found this useful on several occasions

class Array
include EnumerableOperator
def all_pairs
a = size-1
for i,j in product(0..a, 0..a, proc {|i,j| i < j})
yield at(i), at(j)
end
end
end

It depends on http://zem.novylen.net/ruby/fproduct.rb

Called all_pairs to distinguish it from the [1,2] [2,3] [3,4]... meaning
of each_pair

martin
 
G

gabriele renzi

(quoting

It's actually "Florian Groß", but I've seen the last character come out
as an y with two dots above it which is why I usually use "Gross" where
I can't be sure about it coming out right. ;)

well, I see it fine (well, I think that is the german letter similar
to a beta that gets read kind of 'ss' )
Oh, and thanks for naming this after me. :)

eh, I always wanted to name things :)

It's biased when rand returns the same float for two or more elements of
the Array.

I don't understand: what happens if it returns the same float for two
different numbers? they just get near in the sorting, what's the
problem with that?
 
M

Mauricio Fernández

I don't understand: what happens if it returns the same float for two
different numbers? they just get near in the sorting, what's the
problem with that?
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

When rand returns the same float twice for 2 numbers, they will keep
their relative ordering in the end result. The Mersenne Twister Ruby
uses has got a period of 2^19937-1, but Kernel.rand only returns 64
bits, so collisions are way more likely than it would seem at first
sight. For instance, for an array with 10 million elements, the
probability of being slightly biased is a bit over 2.71*10^-6. If you
use 128 bits (by doing sort_by{[rand,rand]}), the probability decreases
to 1.47*10^-25 (at that point it is more likely that your computer
will explode or just crash).

For most intents and purposes, sort_by{rand} is unbiased enough.

--
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

'Ooohh.. "FreeBSD is faster over loopback, when compared to Linux
over the wire". Film at 11.'
-- Linus Torvalds
 
M

Michael Neumann

There's a few trivial but useful "extensions" to Ruby's standard
library that I find myself using in most of my projects. I'll share a
couple, and I'd really love to see what you're using, too.

Array folding is a functional idiom in which each item is evaluated in
some way and its result accumulated, returning the final value of the
accumulator. It's simple in Ruby:

| class Array
| def foldl(accum, &block)
| each {|value| accum = yield(accum, value)}
| return accum
| end
| def foldr(accum, &block)
| reverse.foldl(accum, &block)
| end
| alias fold :foldl
| end

The canonical example is summing values:

| r = (1..100).to_a
| p r.foldl(0) {|a,v| a += v}
=> 5050
| p r.foldr(5050) {|a,v| a -= v}
=> 0

A more interesting and useful application involves boolean logic. For
example, the following snippet ensures all arguments passed to
FooSet#new are of type Foo:

| class Foo; end
| class FooSet
| def initialize(*values)
| raise TypeError unless
| values.fold(true) {|a,v| a and v.is_a? Foo}
| # ...
| end
| end

There's an easier way to do it:

values.all? {|v| v.is_a? Foo}


Regards,

Michael
 
R

Robert Klemme

Sam Stephenson said:
Wow, I didn't know about Enumerable#inject. Semantically, it's not
quite the same as foldl: for instance, it doesn't make sense to foldl
(or foldr) a Hash, since you're not guaranteed any order.

That's not true. Since + is commutative it doesn't matter in which order
you add elements up. And there's a whole lot other algorithms that don't
depend on the order in which they are processed.

Even if you need an order you can do (stupid example):

sorted_keys = hash.sort.inject([]) {|keys, (key, val)| print key, " -> ",
val, "\n"; keys << key }
Practically
speaking, however, it's much nicer, since I can use it with Range.

As an aside: It would be nice to have a subclass of Enumerable that
guarantees item order; Array, Range, et al could mix it in.

Which order? Insertion order, natural order of the elements or an
arbitrary order?

Regards

robert
 
I

Ian Macdonald

There's a few trivial but useful "extensions" to Ruby's standard
library that I find myself using in most of my projects. I'll share a
couple, and I'd really love to see what you're using, too.

I often find myself needing to instantiate a populated Hash, based on
the contents of two arrays, where the first contains what will become
the keys of the Hash and the second the values.

I use the following for this:

irb(main):001:0* foo = %w[ a b c ]
=> ["a", "b", "c"]
irb(main):002:0> bar = %w[ d e f ]
=> ["d", "e", "f"]
irb(main):003:0> baz = Hash[ *( foo.zip( bar ).flatten ) ]
=> {"a"=>"d", "b"=>"e", "c"=>"f"}

Nice, but I'd like it even more if ruby had a more direct and legible
way of achieving the same goal.

Ian
--
Ian Macdonald | Obstacles are what you see when you take
System Administrator | your eyes off your goal.
(e-mail address removed) |
http://www.caliban.org |
|
 
G

Gavin Sinclair

Ian said:
I often find myself needing to instantiate a populated Hash, based on
the contents of two arrays, where the first contains what will become
the keys of the Hash and the second the values.

Just curious: what sort of scenario necessitates that use?

If there were a nice easy way to do that in Ruby, what would you like it
to be called? i.e.

a = [:x, :y, :z]
b = [1, 2, 3]
hash = # Fill me in!
assert_equal({:x => 1, :y => 2, :z => 3}, hash)

Cheers,
Gavin
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top