Enumerable#build

class Enumerable
  def build(seed)
    each {|i|
      seed.add(yield i)
    }
  end
end

and

Array#build == Array#<<
Hash#build(k,v) == Hash#[]
etc

So you could say [1,2,3].build({}) {|i| [i, 1]} for the question that
prompted the to_hash thread, but also, in general, you can collect into
non-array structures (binary trees, e.g.) as long as they implement a
suitable #add method.

(Of course, you can do ary.inject(seed) {|i| seed.add(f(i)); seed} right
now but a #build method makes the common case pretty.)

martin

Martin DeMello wrote:

class Enumerable
  def build(seed)
    each {|i|
      seed.add(yield i)
    }
  end
end

and

Array#build == Array#<<
Hash#build(k,v) == Hash#
etc

So you could say [1,2,3].build({}) {|i| [i, 1]} for the question that
prompted the to_hash thread, but also, in general, you can collect
into non-array structures (binary trees, e.g.) as long as they
implement a suitable #add method.

(Of course, you can do ary.inject(seed) {|i| seed.add(f(i)); seed}
right now but a #build method makes the common case pretty.)

Looks good. But,

- Enumerable is a module... :slight_smile:

- You should add seed as return value.

- Your first example won't work as Hash#add is nonexistent

An alternative approach would be to reverse the logic and have
Enumerable#populate:

module Enumerable
  def populate(*enum)
    enum = enum.first if enum.size == 1
    enum.each {|*a| add(yield(*a))}
    self
  end
end

class Hash
  def populate(*enum)
    enum = enum.first if enum.size == 1
    enum.each {|*a| send(:=, *yield(*a))}
    self
  end
end

h={}.populate(1,2,3) {|x| [x, "val #{x}"]}

=> {1=>"val 1", 2=>"val 2", 3=>"val 3"}

What do you think?

Kind regards

    robert

Not sure you tested this! :slight_smile: Maybe something like this:

module Enumerable
def build(seed)
   each {|i|
     seed.add(*(yield i))
   }
   seed
end
end

class Hash
  alias :add :store
end
class Array
  def add(*args)
    self << args
  end
end

p [1,2,3].build({}) {|i| [i, 1]}
p [1,2,3].build() {|i| [i, 1]}

---------- Output ----------
{1=>1, 2=>1, 3=>1}
[[1, 1], [2, 1], [3, 1]]

Regards,

Sean

···

On 10/26/05, Martin DeMello <martindemello@yahoo.com> wrote:

class Enumerable
def build(seed)
   each {|i|
     seed.add(yield i)
   }
end
end

and

Array#build == Array#<<
Hash#build(k,v) == Hash#
etc

Martin DeMello wrote:
> class Enumerable
> def build(seed)
> each {|i|
> seed.add(yield i)
> }
> end
> end
>
> and
>
> Array#build == Array#<<
> Hash#build(k,v) == Hash#
> etc
>
> So you could say [1,2,3].build({}) {|i| [i, 1]} for the question that
> prompted the to_hash thread, but also, in general, you can collect
> into non-array structures (binary trees, e.g.) as long as they
> implement a suitable #add method.
>
> (Of course, you can do ary.inject(seed) {|i| seed.add(f(i)); seed}
> right now but a #build method makes the common case pretty.)

Looks good. But,

- Enumerable is a module... :slight_smile:

- You should add seed as return value.

Oops - yes, I was enthused about the idea and got sloppy with the
actual code.

- Your first example won't work as Hash#add is nonexistent

No, that was my point - add a #add method (chosen not to conflict with
anything currently in the core) to any class that you wish to act as a
build "client".

An alternative approach would be to reverse the logic and have
Enumerable#populate:

module Enumerable
  def populate(*enum)
    enum = enum.first if enum.size == 1
    enum.each {|*a| add(yield(*a))}
    self
  end
end

class Hash
  def populate(*enum)
    enum = enum.first if enum.size == 1
    enum.each {|*a| send(:=, *yield(*a))}
    self
  end
end

>> h={}.populate(1,2,3) {|x| [x, "val #{x}"]}
=> {1=>"val 1", 2=>"val 2", 3=>"val 3"}

What do you think?

I'd make one change to your way - keep Addable separate from Enumerable.
Once we've done that, it's simple to add both #populate to Addable
(requires an Enumerable argument) and #build to Enumerable (regretting,
once again, that #collect is already a synonym of #map). One nice thing
is that classes implementing both Enumerable and Addable could then
provide a "structural map" - e.g.

class Hash
  def smap
    build({}) {|k,v| [k, yield(v)]}
  end
end

{:x=>2, :y=>4}.smap {|i| i*i} #=> {:x => 4, :y => 16}

Or even something more complex like

class Tree
  def each
    ...
    yield [element, path]
  end

  def add args
    element, path = args
    ...
  end

  def smap
    build(Tree.new) {|e, path| [(yield e), path }
    # or
    # Tree.new.populate(self) {|e, path| [(yield e), path]}
  end
end

martin

···

Robert Klemme <bob.news@gmx.net> wrote:

I think you have too much magic.

Enumerable#to_a, Array#fill, Array#flatten and Hash:: do this pretty handily and much more cleanly:

Hash[*ary.fill { |i| [i, "val #{i}"]}.flatten]

$ ruby -e 'p Hash[*(0..3).to_a.fill { |i| [i, "val #{i}"] }.flatten ]'
{0=>"val 0", 1=>"val 1", 2=>"val 2", 3=>"val 3"}

···

On Oct 26, 2005, at 4:47 AM, Robert Klemme wrote:

Martin DeMello wrote:

class Enumerable
  def build(seed)
    each {|i|
      seed.add(yield i)
    }
  end
end

and

Array#build == Array#<<
Hash#build(k,v) == Hash#
etc

So you could say [1,2,3].build({}) {|i| [i, 1]} for the question that
prompted the to_hash thread, but also, in general, you can collect
into non-array structures (binary trees, e.g.) as long as they
implement a suitable #add method.

(Of course, you can do ary.inject(seed) {|i| seed.add(f(i)); seed}
right now but a #build method makes the common case pretty.)

Looks good. But,

- Enumerable is a module... :slight_smile:

- You should add seed as return value.

- Your first example won't work as Hash#add is nonexistent

An alternative approach would be to reverse the logic and have
Enumerable#populate:

module Enumerable
  def populate(*enum)
    enum = enum.first if enum.size == 1
    enum.each {|*a| add(yield(*a))}
    self
  end
end

class Hash
  def populate(*enum)
    enum = enum.first if enum.size == 1
    enum.each {|*a| send(:=, *yield(*a))}
    self
  end
end

h={}.populate(1,2,3) {|x| [x, "val #{x}"]}

=> {1=>"val 1", 2=>"val 2", 3=>"val 3"}

What do you think?

--
Eric Hodel - drbrain@segment7.net - http://segment7.net
FEC2 57F1 D465 EB15 5D6E 7C11 332A 551C 796C 9F04

Martin DeMello wrote:

h={}.populate(1,2,3) {|x| [x, "val #{x}"]}

=> {1=>"val 1", 2=>"val 2", 3=>"val 3"}

What do you think?

I'd make one change to your way - keep Addable separate from
Enumerable.

What exactly do you mean by Addable? Did I overlook something? If you
just mean method #add then of course you have to keep them separate
because every container must implement it's own version od #add. Btw, the
name doesn't seem to be well chosen for Hash because not every element is
added, i.e. for duplicate keys the second one isn't exactly added.

Once we've done that, it's simple to add both #populate
to Addable (requires an Enumerable argument) and #build to Enumerable
(regretting, once again, that #collect is already a synonym of #map).

Problem I see here is that requirements of Enumerable are extended: now
it's "your class needs to implement each" then it's "your class needs to
implement each and if you want to use build/populate your class also needs
to implement #add (or whatever it's called)".

One nice thing is that classes implementing both Enumerable and
Addable could then provide a "structural map" - e.g.

class Hash
  def smap
    build({}) {|k,v| [k, yield(v)]}
  end
end

{:x=>2, :y=>4}.smap {|i| i*i} #=> {:x => 4, :y => 16}

{:x=>2, :y=>4}.inject({}) {|h,(k,v)| h[k]=v*v;h}

=> {:x=>4, :y=>16}

This doesn't look too bad - and it's not restricted to building hashes. I
think your #smap is not general enough. I also start doubting whether
build / populate are actually good ideas...

Or even something more complex like

class Tree
  def each
    ...
    yield [element, path]
  end

  def add args
    element, path = args
    ...
  end

  def smap
    build(Tree.new) {|e, path| [(yield e), path }
    # or
    # Tree.new.populate(self) {|e, path| [(yield e), path]}
  end
end

Btw, I'd prefer the idiom

def smap
  build(self.class.new) ...
....
end

Kind regards

    robert

Eric Hodel wrote:

Martin DeMello wrote:

class Enumerable
  def build(seed)
    each {|i|
      seed.add(yield i)
    }
  end
end

and

Array#build == Array#<<
Hash#build(k,v) == Hash#
etc

So you could say [1,2,3].build({}) {|i| [i, 1]} for the question
that prompted the to_hash thread, but also, in general, you can
collect into non-array structures (binary trees, e.g.) as long as
they implement a suitable #add method.

(Of course, you can do ary.inject(seed) {|i| seed.add(f(i)); seed}
right now but a #build method makes the common case pretty.)

Looks good. But,

- Enumerable is a module... :slight_smile:

- You should add seed as return value.

- Your first example won't work as Hash#add is nonexistent

An alternative approach would be to reverse the logic and have
Enumerable#populate:

module Enumerable
  def populate(*enum)
    enum = enum.first if enum.size == 1
    enum.each {|*a| add(yield(*a))}
    self
  end
end

class Hash
  def populate(*enum)
    enum = enum.first if enum.size == 1
    enum.each {|*a| send(:=, *yield(*a))}
    self
  end
end

h={}.populate(1,2,3) {|x| [x, "val #{x}"]}

=> {1=>"val 1", 2=>"val 2", 3=>"val 3"}

What do you think?

I think you have too much magic.

Is this good or bad? :-))

Enumerable#to_a, Array#fill, Array#flatten and Hash:: do this
pretty handily and much more cleanly:

Hash[*ary.fill { |i| [i, "val #{i}"]}.flatten]

$ ruby -e 'p Hash[*(0..3).to_a.fill { |i| [i, "val #{i}"] }.flatten ]'
{0=>"val 0", 1=>"val 1", 2=>"val 2", 3=>"val 3"}

I'm not sure about cleanness but this is certainly less memory efficient
as it creates relatively large temporary data structures.

Kind regards

    robert

···

On Oct 26, 2005, at 4:47 AM, Robert Klemme wrote:

Martin DeMello wrote:

>>>> h={}.populate(1,2,3) {|x| [x, "val #{x}"]}
>> => {1=>"val 1", 2=>"val 2", 3=>"val 3"}
>>
>> What do you think?
>
> I'd make one change to your way - keep Addable separate from
> Enumerable.

What exactly do you mean by Addable? Did I overlook something? If you
just mean method #add then of course you have to keep them separate
because every container must implement it's own version od #add. Btw, the
name doesn't seem to be well chosen for Hash because not every element is
added, i.e. for duplicate keys the second one isn't exactly added.

I mean a collection of methods (like #populate) that rely on the
existence of #add in the receiver.

> Once we've done that, it's simple to add both #populate
> to Addable (requires an Enumerable argument) and #build to Enumerable
> (regretting, once again, that #collect is already a synonym of #map).

Problem I see here is that requirements of Enumerable are extended: now
it's "your class needs to implement each" then it's "your class needs to
implement each and if you want to use build/populate your class also needs
to implement #add (or whatever it's called)".

Which is why I postulated a separate Addable mixin. #build would be
under Enumerable (receiver implements #each, argument implements #add),
and #populate under Addable (vice versa).

> One nice thing is that classes implementing both Enumerable and
> Addable could then provide a "structural map" - e.g.
>
> class Hash
> def smap
> build({}) {|k,v| [k, yield(v)]}
> end
> end
>
> {:x=>2, :y=>4}.smap {|i| i*i} #=> {:x => 4, :y => 16}

>> {:x=>2, :y=>4}.inject({}) {|h,(k,v)| h[k]=v*v;h}
=> {:x=>4, :y=>16}

This doesn't look too bad - and it's not restricted to building hashes. I
think your #smap is not general enough. I also start doubting whether
build / populate are actually good ideas...

Not general enough how? The intention is to replicate a 'structure'
while transforming its elements. Even if build/populate aren't good
ideas, the underlying problem (that #map flattens its receiver into a
linear list) remains, and ought to be solved.

Btw, I'd prefer the idiom

def smap
  build(self.class.new) ...
...
end

Yeah, that's nice. Can be implemented properly inside a mixin, then.
Perhaps with included hooks in both Enumerable and Addable to check if
the other one is already included, and to define smap if so.

Incidentally, there's an excellent FP paper titled "The Underappreciated
Unfold", which hurts my brain, but is well worth a read. There's a copy
available here:
http://www.itee.uq.edu.au/~ck/Papers/Program%20Transformation/folds%20etc/unfold.ps

martin

···

Robert Klemme <bob.news@gmx.net> wrote: