Ruby Forum Ruby > ruby and list comprehension

Posted by Brad Tilley (rtilley)
on 26.11.2006 00:48
(Received via mailing list)
In Python, I can do this to arrays:

added = [x for x in new_data if x not in old_data]
removed = [x for x in old_data if x not in new_data]
same = [x for x in new_data if x in old_data]

I believe this is known as list comprehension in Python. How is this 
done in
Ruby?

Thanks,
Brad
Posted by Timothy Hunter (Guest)
on 26.11.2006 00:54
(Received via mailing list)
Brad Tilley wrote:
> Brad
>
>   
This question comes up from time to time. You can search the archives of
comp.lang.ruby on Google Groups for "list comprehension" to find the
previous threads on this topic.

http://groups.google.com/group/comp.lang.ruby/search?group=comp.lang.ruby&q=list+comprehension&qt_g=1&searchnow=Search+this+group
Posted by Lou Scoras (ljscoras)
on 26.11.2006 01:08
(Received via mailing list)
On 11/25/06, Brad Tilley <rtilley@vt.edu> wrote:

> In Python, I can do this to arrays:
>
> added = [x for x in new_data if x not in old_data]
> removed = [x for x in old_data if x not in new_data]
> same = [x for x in new_data if x in old_data]

Short answer:

    added   = new_data.reject {|i| old_data.include? i }
    removed = old_data.reject {|i| new_data.include? i }
    same    = new_data.select {|i| old_data.include? i }

Provided ordering isn't important here, you can do the same thing with
set operations.

    require 'set'

    old_data = old_data.to_set
    new_data = new_data.to_set

    added   = new_data - old_data
    removed = old_data - new_data
    same    = new_data.intersection(old_data)

Note those returns sets, not arrays.
Posted by Mike Austin (Guest)
on 26.11.2006 01:22
(Received via mailing list)
Brad Tilley wrote:
> Brad
Because Ruby's select() returns a value, not just a boolean like in 
Smalltalk,
you can do the following:

[1,2,3].select { |x| ![2,3,4].include? x }
[2,3,4].select { |x| ![1,2,3].include? x }
[1,2,3].select { |x| [2,3,4].include? x }

You can also use Array operators:

[1,2,3] - [2,3,4]
[2,3,4] - [1,2,3]
[1,2,3] & [2,3,4]


Mike
Posted by Brad Tilley (rtilley)
on 26.11.2006 01:30
(Received via mailing list)
Quoting Mike Austin <noone@nowhere.com>:

> [1,2,3] - [2,3,4]
> [2,3,4] - [1,2,3]
> [1,2,3] & [2,3,4]

Thanks for all the examples guys! That's great stuff.
Posted by James Cunningham (Guest)
on 26.11.2006 23:46
(Received via mailing list)
On 2006-11-25 18:47:26 -0500, Brad Tilley <rtilley@vt.edu> said:

> Brad
I found this while web searching for the same thing recently; I can't
recall where I found it. It's a cute little hack.

class Array
  def comprehend
    return self unless block_given?
    result = []
    self.each { |i| result.push yield(i) }
    result.compact
  end
end

Then:

added = new_data.comprehend { |x| x if not old_data.include? x }
removed = old_data.comprehend { |x| x if not new_data.include? x }
same = new_data.comprehend { |x| x if old_data.include? x }

Best,
James
Posted by unknown (Guest)
on 26.11.2006 23:54
(Received via mailing list)
Hi --

On Mon, 27 Nov 2006, James Cunningham wrote:

>> Ruby?
>   result = []
>   self.each { |i| result.push yield(i) }
>   result.compact
> end
> end
>
> Then:
>
> added = new_data.comprehend { |x| x if not old_data.include? x }
> removed = old_data.comprehend { |x| x if not new_data.include? x }
> same = new_data.comprehend { |x| x if old_data.include? x }

I'm not getting how that's better than:

   added = new_data.select {|x| not old_data.include?(x) }

(or the reject equivalent) and so on.


David
Posted by James Cunningham (Guest)
on 27.11.2006 01:06
(Received via mailing list)
On 2006-11-26 17:52:37 -0500, dblack@wobblini.net said:

>>> same = [x for x in new_data if x in old_data]
>> class Array
>> added = new_data.comprehend { |x| x if not old_data.include? x }
> David
I should have clarified. In your example there's no difference, but the
above gives a general replacement for list comprehensions.

irb(main):018:0> (1..25).to_a.comprehend { |x| x**2 if not x % 2 == 0 }
=> [1, 9, 25, 49, 81, 121, 169, 225, 289, 361, 441, 529, 625]

Best,
James
Posted by James Cunningham (Guest)
on 27.11.2006 01:06
(Received via mailing list)
On 2006-11-26 19:01:53 -0500, James Cunningham
<jameshcunningham@gmail.com> said:

>>>> added = [x for x in new_data if x not in old_data]
>>> recall where I found it. It's a cute little hack.
>>> Then:
>> 
> Best,
> James

Er, "your" meaning "Brad Tilley's".

Best,
James
Posted by Martin DeMello (Guest)
on 27.11.2006 09:10
(Received via mailing list)
On 11/27/06, James Cunningham <jameshcunningham@gmail.com> wrote:
>
> added = new_data.comprehend { |x| x if not old_data.include? x }
> removed = old_data.comprehend { |x| x if not new_data.include? x }
> same = new_data.comprehend { |x| x if old_data.include? x }

I wrote http://zem.novylen.net/ruby/fproduct.rb a while ago to emulate
list comprehensions in ruby - for example:

for x,y,z in product 1..40,
                       1..40, proc {|x,y| x <= y},
                       1..40, proc {|x,y,z| x**2 + y**2 == z**2}
    p [x,y,z]
end

The benefit is that the filters do get applied in order (sorted on
arity), so that it doesn't generate all the combinations first and
then filter.

martin
Posted by Robert Klemme (Guest)
on 27.11.2006 09:36
(Received via mailing list)
On 27.11.2006 01:01, James Cunningham wrote:
> On 2006-11-26 17:52:37 -0500, dblack@wobblini.net said:
>> I'm not getting how that's better than:
>>
>>    added = new_data.select {|x| not old_data.include?(x) }
>>
>> (or the reject equivalent) and so on.

> I should have clarified. In your example there's no difference, but the 
> above gives a general replacement for list comprehensions.
> 
> irb(main):018:0> (1..25).to_a.comprehend { |x| x**2 if not x % 2 == 0 }
> => [1, 9, 25, 49, 81, 121, 169, 225, 289, 361, 441, 529, 625]

Frankly, I am not sure I find this better than using the built in 
methods:

irb(main):001:0> (1..25).inject([]) {|a,x| a << x**2 unless x % 2 == 0; 
a}
=> [1, 9, 25, 49, 81, 121, 169, 225, 289, 361, 441, 529, 625]

irb(main):002:0> (1..25).inject([]) {|a,x| a << x**2 if x % 2 == 1; a}
=> [1, 9, 25, 49, 81, 121, 169, 225, 289, 361, 441, 529, 625]

irb(main):003:0> (1..25).select {|x| x % 2 == 1}.map! {|x| x**2}
=> [1, 9, 25, 49, 81, 121, 169, 225, 289, 361, 441, 529, 625]

Kind regards

	robert
Posted by Sky Yin (cookoo)
on 27.11.2006 10:51
(Received via mailing list)
I've always thought list comprehension is just a bunch of
map/filter/... transformation until I saw the following version of
permutation:

in Haskell:
permutation [] = [[]]
permutation xs = [x:ys | x <- xs, ys <- permutation (delete x xs)]

in Erlang:
permutation([]) -> [[]];
permutation(L)  -> [[H|T] || H <- L, T <- permutation(L--[H])].

really neat, isn't it?
Posted by Robert Dober (Guest)
on 27.11.2006 11:01
(Received via mailing list)
On 11/26/06, Mike Austin <noone@nowhere.com> wrote:
>
> Brad Tilley wrote:
> <snip>
> Because Ruby's select() returns a value, not just a boolean like in
> Smalltalk,
>
>
OT of course ;)
... this does not seem true for all Smalltalks, I am using Squeak and
select: returns the filtered array, I am quite surprised that it worked
differently before.
<snip>
Robert



--
The reasonable man adapts himself to the world; the unreasonable one
persists in trying to adapt the world to himself. Therefore all progress
depends on the unreasonable man.

- George Bernard Shaw
Posted by James Cunningham (Guest)
on 27.11.2006 14:01
(Received via mailing list)
On 2006-11-27 03:30:58 -0500, Robert Klemme <shortcutter@googlemail.com> 
said:

>> 
> 
> irb(main):003:0> (1..25).select {|x| x % 2 == 1}.map! {|x| x**2}
> => [1, 9, 25, 49, 81, 121, 169, 225, 289, 361, 441, 529, 625]
> 
> Kind regards
> 
> 	robert

That's fair enough, but I think list comprehension is clearer than
method chaining and one-liner array accumulation. I'm afraid

a << x**2 if x % 2 == 1; a

is perhaps just a little less elegant than

x**2 if x % 2 == 1

I think the nicest thing about Ruby, though, is that it's even possible.

Best,
James
Posted by Devin Mullins (twifkak)
on 27.11.2006 15:23
(Received via mailing list)
piggybox wrote:
> in Haskell:
> permutation [] = [[]]
> permutation xs = [x:ys | x <- xs, ys <- permutation (delete x xs)]
> 
> in Erlang:
> permutation([]) -> [[]];
> permutation(L)  -> [[H|T] || H <- L, T <- permutation(L--[H])].
> 
> really neat, isn't it?
Yes. :)
Posted by Phrogz (Guest)
on 28.11.2006 05:45
(Received via mailing list)
James Cunningham wrote:
> end
Maybe I don't comprehend comprehending, but why the result/each instead
of map?

class Array
  def comprehend
    if block_given?
      map{ |i| yield( i ) }.compact
    else
      self
    end
  end
end

or perhaps better

class Array
  def comprehend( &block )
    block ? map( &block ).compact : self
  end
end
Posted by James Cunningham (Guest)
on 28.11.2006 06:15
(Received via mailing list)
On 2006-11-27 23:39:57 -0500, "Phrogz" <gavin@refinery.com> said:

> class Array
>   def comprehend( &block )
>     block ? map( &block ).compact : self
>   end
> end

The same.

You're just cleverer than I am. ;)

Best,
James
Posted by Robert Klemme (Guest)
on 28.11.2006 11:36
(Received via mailing list)
On 28.11.2006 05:39, Phrogz wrote:
> class Array
>   def comprehend( &block )
>     block ? map( &block ).compact : self
>   end
> end


This could go into Enumerable instead.  There is no special Array
functionality involved.

Kind regards

	robert