Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > How to make combinations of an array to produce all possible expressions?

Reply
Thread Tools

How to make combinations of an array to produce all possible expressions?

 
 
Erik Terpstra
Guest
Posts: n/a
 
      05-13-2004
I have an array 'conds', which contains some sub-expressions for an
xpath query:

conds = ["@title='Foo'", "@edition='Bar'", "@date='20040513'"]

Is there an existing library that lets me construct all possible
combinations like this?

puts conds.<some Array extension method>.collect{|n| n.join ' and '}

which produces:

@title='Foo' and @edition='Bar' and @date='20040513'
@title='Foo' and @edition='Bar'
@title='Foo' and @date='20040513'
@edition='Bar' and @date='20040513'
@title='Foo'
@edition='Bar'
@date='20040513'
 
Reply With Quote
 
 
 
 
Robert Klemme
Guest
Posts: n/a
 
      05-13-2004

"Erik Terpstra" <> schrieb im Newsbeitrag
news:40a333fc$0$65124$...
> I have an array 'conds', which contains some sub-expressions for an
> xpath query:
>
> conds = ["@title='Foo'", "@edition='Bar'", "@date='20040513'"]
>
> Is there an existing library that lets me construct all possible
> combinations like this?
>
> puts conds.<some Array extension method>.collect{|n| n.join ' and '}
>
> which produces:
>
> @title='Foo' and @edition='Bar' and @date='20040513'
> @title='Foo' and @edition='Bar'
> @title='Foo' and @date='20040513'
> @edition='Bar' and @date='20040513'
> @title='Foo'
> @edition='Bar'
> @date='20040513'


Not yet, but:

module Enumerable
def combine
masks = inject([[], 1]){|(ar, m), e| [ar<<m, m<<1]}[0]
all = masks.inject(0){|al, m| al|m}

result = []

for i in 1..all do
tmp = []

each_with_index do |e, idx|
tmp << e unless (masks[idx] & i) == 0
end

result << tmp
end

result
end
end


irb(main):098:0> puts conds.combine.collect{|n| n.join ' and '}
@title='Foo'
@edition='Bar'
@title='Foo' and @edition='Bar'
@date='20040513'
@title='Foo' and @date='20040513'
@edition='Bar' and @date='20040513'
@title='Foo' and @edition='Bar' and @date='20040513'
=> nil

robert

 
Reply With Quote
 
 
 
 
Harry Ohlsen
Guest
Posts: n/a
 
      05-13-2004
Erik Terpstra wrote:

> I have an array 'conds', which contains some sub-expressions for an
> xpath query:
>
> conds = ["@title='Foo'", "@edition='Bar'", "@date='20040513'"]

<snip>

There's no method I know of, but this seems to work (note that I've explicitly avoided generating an empty set, because you didn't have one in your example, but it should probably be included if we wanted to call this method "subsets" as I have) ...

module Enumerable
def subsets
values = []

(2 << length - 1).times do |n|
items = []

length.times do |i|
if n[i] == 1
items << self[i]
end
end

if items.length > 0 # I'd omit this test for a real "subsets"
values << items
end
end

return values
end
end

conds = ["@title='Foo'", "@edition='Bar'", "@date='20040513'"]

conds.subsets.collect { |subset| p subset.join(" and ") }



 
Reply With Quote
 
Robert Klemme
Guest
Posts: n/a
 
      05-13-2004

"Harry Ohlsen" <> schrieb im Newsbeitrag
news:...
> Erik Terpstra wrote:
>
> > I have an array 'conds', which contains some sub-expressions for an
> > xpath query:
> >
> > conds = ["@title='Foo'", "@edition='Bar'", "@date='20040513'"]

> <snip>
>
> There's no method I know of, but this seems to work (note that I've

explicitly avoided generating an empty set, because you didn't have one in
your example, but it should probably be included if we wanted to call this
method "subsets" as I have) ...
>
> module Enumerable
> def subsets
> values = []
>
> (2 << length - 1).times do |n|
> items = []
>
> length.times do |i|
> if n[i] == 1
> items << self[i]
> end
> end
>
> if items.length > 0 # I'd omit this test for a real "subsets"
> values << items
> end
> end
>
> return values
> end
> end


Ah, similar idea but nicer coding. I like especially your calculation of
the counting range and int[idx] as bit test. I didn't know that one.

Btw, you don't need the test for length 0 if you do

for n in 1 ... (2<<length) do
....

Regards

robert

 
Reply With Quote
 
Kristof Bastiaensen
Guest
Posts: n/a
 
      05-13-2004
On Thu, 13 May 2004 10:38:19 +0200, Erik Terpstra wrote:

> I have an array 'conds', which contains some sub-expressions for an
> xpath query:
>
> conds = ["@title='Foo'", "@edition='Bar'", "@date='20040513'"]


Hi,
I have found at least to ways.
Using recursion (maybe not so efficient):

class Array
def combine
comb = Proc.new do |a, *t|
tail = (t.empty? ? [] : comb[t])
[[a]] + tail.collect{ |e| [a] + e} + tail
end
comb[self]
end
end

The following should perform better:

class Array
def combine2
(1..(2**(size) - 1)).collect do |i|
i <<= 1
self.select { (i >>= 1) & 1 == 1}
end
end
end

irb(main):018:0> puts conds.combine.collect{|n| n.join ' and '}
@title='Foo'
@title='Foo' and @edition='Bar'
@title='Foo' and @edition='Bar' and @date='20040513'
@title='Foo' and @date='20040513'
@edition='Bar'
@edition='Bar' and @date='20040513'
@date='20040513'
=> nil
irb(main):019:0> puts conds.combine2.collect{|n| n.join ' and '}
@title='Foo'
@edition='Bar'
@title='Foo' and @edition='Bar'
@date='20040513'
@title='Foo' and @date='20040513'
@edition='Bar' and @date='20040513'
@title='Foo' and @edition='Bar' and @date='20040513'
=> nil


Kristof
 
Reply With Quote
 
Harry Ohlsen
Guest
Posts: n/a
 
      05-14-2004
Robert Klemme wrote:

> Ah, similar idea but nicer coding.


Coming from you, that's a much appreciated compliment!

> I like especially your calculation of
> the counting range and int[idx] as bit test. I didn't know that one.


I discovered during a re-browse of pickaxe a couple of months ago, but this is the first time I've actually had a chance to use it.

> Btw, you don't need the test for length 0 if you do
>
> for n in 1 ... (2<<length) do


Good point.

Something I was thinking about was that the power set (set of subsets) becomes large very quickly (obvious from the "2 << length", I guess). It might be nice to have an iterator, too. The method that returns the collection of subsets could then just use it.

Something like this (I've removed the empty set test, since I think this is cleaner in a mathematical sense) ...

module Enumerable
def each_subset(&block)
(2 << length - 1).times do |n|
subset = []

length.times do |i|
if n[i] == 1
subset << self[i]
end
end

yield subset
end
end

def subsets
subsets = []

each_subset { |s| subsets << s }

return subsets
end
end

if __FILE__ == $0

conds = ["@title='Foo'", "@edition='Bar'", "@date='20040513'"]

puts "Using iterator ..."
puts

conds.each_subset { |subset| p subset.join(" and ") }

puts
puts "Using aggregate ..."
puts

conds.subsets.collect { |subset| p subset.join(" and ") }

end



 
Reply With Quote
 
Robert Klemme
Guest
Posts: n/a
 
      05-14-2004

"Harry Ohlsen" <> schrieb im Newsbeitrag
news:...
> Robert Klemme wrote:
>
> > Ah, similar idea but nicer coding.

>
> Coming from you, that's a much appreciated compliment!


You're welcome!

> > I like especially your calculation of
> > the counting range and int[idx] as bit test. I didn't know that one.

>
> I discovered during a re-browse of pickaxe a couple of months ago, but

this is the first time I've actually had a chance to use it.
>
> > Btw, you don't need the test for length 0 if you do
> >
> > for n in 1 ... (2<<length) do

>
> Good point.
>
> Something I was thinking about was that the power set (set of subsets)

becomes large very quickly (obvious from the "2 << length", I guess). It
might be nice to have an iterator, too. The method that returns the
collection of subsets could then just use it.

Another good point!

> Something like this (I've removed the empty set test, since I think this

is cleaner in a mathematical sense) ...

That might be handled by an additional flag with a default value (which
one, the mathematical or the practical?). Practically you often don't
need / want the empty set.

Funny to see how this evolves. My current vesion looks like this:

module Enumerable
def each_subset(skip_empty = false)
for n in (skip_empty ? 1 : 0) ... (1 << size) do
subset = []

each_with_index do |elem, i|
subset << elem if n[i]
end

yield subset
end

self
end

def powerset(skip_empty = false)
subsets = []

each_subset(skip_empty) { |s| subsets << s }

return subsets
end
end


Changes / Improvements:

- self[index] is not used since it is not available with all Enumerables
- flag 'skip_empty' added
- self returned from iterator
- renamed #subsets to #powerset (this is the mathematical correct term,
isn't it)
- changed (2 << length - 1) to (1 << length)

Here's another experimental version that works even if #size is not
supported. This really needs only #each. Alternatively one could do an
initial iteration to calculate the size - that saves the intermediate
array.

module Enumerable
def each_subset(skip_empty = false)
enum = respond_to?( :size ) ? self : to_a

for n in (skip_empty ? 1 : 0) ... (1 << enum.size) do
subset = []

enum.each_with_index do |elem, i|
subset << elem if n[i] == 1
end

yield subset
end

self
end

def powerset(skip_empty = false)
subsets = []

each_subset(skip_empty) { |s| subsets << s }

return subsets
end
end

# test class
class Test
include Enumerable

def initialize(n); @n=n; end
def each; @n.times {|c| yield c}; self; end
end

p Test.new(3).powerset


Kind regards

robert

 
Reply With Quote
 
Linus Sellberg
Guest
Posts: n/a
 
      05-14-2004
> module Enumerable
> def each_subset(skip_empty = false)
> enum = respond_to?( :size ) ? self : to_a
>
> for n in (skip_empty ? 1 : 0) ... (1 << enum.size) do
> subset = []
>
> enum.each_with_index do |elem, i|
> subset << elem if n[i] == 1
> end
>
> yield subset
> end
>
> self
> end
>
> def powerset(skip_empty = false)
> subsets = []
>
> each_subset(skip_empty) { |s| subsets << s }
>
> return subsets
> end


irb(main):013:0> def powerset( x )
irb(main):014:1> x.inject([[]]) {|m, n| m.map {|b| [n] + b} + m }
irb(main):015:1> end
irb(main):017:0> a = [1,2,3]
=> [1, 2, 3]
irb(main):018:0> powerset a
=> [[3, 2, 1], [3, 2], [3, 1], [3], [2, 1], [2], [1], []]
irb(main):019:0>

Only works for arrays, but it might be possible to generalize it?
 
Reply With Quote
 
Harry Ohlsen
Guest
Posts: n/a
 
      05-17-2004
Robert Klemme wrote:
> Funny to see how this evolves. My current vesion looks like this:
>
> module Enumerable
> def each_subset(skip_empty = false)
> for n in (skip_empty ? 1 : 0) ... (1 << size) do
> subset = []
>
> each_with_index do |elem, i|
> subset << elem if n[i]
> end
>
> yield subset
> end
>
> self
> end
>
> def powerset(skip_empty = false)
> subsets = []
>
> each_subset(skip_empty) { |s| subsets << s }
>
> return subsets
> end
> end


The first version in your latest e-mail didn't work for me. It just gave me the complete array every time.

I think it's because when n[i] is zero, that's not the same as false, hence the "if" always succeeds.

You just need to change that line to

subset << elem if (n[i] == 1)

The second version worked fine.

I like all of your clean-ups. I wonder whether re-coding in C would increase performance?

Anyway, it might be worth creating an RCR to have this added to Enumerable.

Cheers,

Harry O.



 
Reply With Quote
 
Robert Klemme
Guest
Posts: n/a
 
      05-17-2004

"Harry Ohlsen" <> schrieb im Newsbeitrag
news:...

> The first version in your latest e-mail didn't work for me. It just

gave me the complete array every time.
>
> I think it's because when n[i] is zero, that's not the same as false,

hence the "if" always succeeds.
>
> You just need to change that line to
>
> subset << elem if (n[i] == 1)
>
> The second version worked fine.


Yeah, I noticed the error but apparently didn't recheck the first version.
You're right of course.

> I like all of your clean-ups.


Thank you!

> I wonder whether re-coding in C would increase performance?


Dunno. I didn't write a ruby extension yet but I heard it's pretty easy.


> Anyway, it might be worth creating an RCR to have this added to

Enumerable.

Maybe. The crucial point is, is this general enough to place it there?
Well, the vote might show it.

Regards

robert

 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Re: How include a large array? Edward A. Falk C Programming 1 04-04-2013 08:07 PM
All possible letter combinations? Teme Rosi Ruby 3 12-16-2008 12:02 AM
can java produce .exe? if it can produce jar,how do you do? aungkopyay@gmail.com Java 5 10-27-2006 02:07 AM
iterator? way of generating all possible combinations? akameswaran@gmail.com Python 16 05-31-2006 04:45 PM
all possible combinations rbt Python 36 07-28-2005 02:01 PM



Advertisments
 



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57