tags:

views:

76

answers:

5

How do I create an exclusion for a array map in Ruby. Here's what I want to achieve,

a = [1,2,3,4]
b = [5,6,7,8]
a.map.each do |x|
  b.map.each do |y|
    if !(x == 1 && y == 7)
      puts "#{x} and #{y}"
    elsif !(x == 4 && y == 8)
      puts "#{x} and #{y}"
    end
  end
end

1 and 5
1 and 6
1 and 7 # still here
1 and 8
2 and 5
2 and 6
2 and 7
2 and 8
3 and 5
3 and 6
3 and 7
3 and 8
4 and 5
4 and 6
4 and 7
4 and 8 # still here

However, it doesn't work, how do I add an exception to these values being processed by map? Also if it's possible to use inject/reject/filter function with the same goal.

+1  A: 

I think this does what you want:

a = [1,2,3,4]
b = [5,6,7,8]
a.map.each do |x|
  b.map.each do |y|
    if !(x == 1 && y == 7) && !(x == 4 && y == 8)
      puts "#{x} and #{y}"
    end
  end
end

(tested on codepad)

Your old code only tested that !(x == 1 && y == 7) was true OR that !(x == 4 && y == 8) was true - it did not test them both. So when x was 1 and y was 7, the first puts did not execute, but the second one did. Execute this code to trace it better:

a = [1,2,3,4]
b = [5,6,7,8]
a.map.each do |x|
  b.map.each do |y|
    if !(x == 1 && y == 7)
      puts "First #{x} and #{y}"
    elsif !(x == 4 && y == 8)
      puts "SEcond #{x} and #{y}"
    end
  end
end
FrustratedWithFormsDesigner
this logic is also wrong. it's the larger disjunction that is the issue, not the smaller
James Baker
@James Baker: yeah, fixed that.
FrustratedWithFormsDesigner
+2  A: 

This is just a problem of not understanding disjunctive semantics in an if statement.

If you want a value NOT to be printed at all, it must match ALL of the negative conditions. Since your predicate is the same (using puts), all you need to do is combine the if statements with an "and" keyword.

That is, something like:

if !(x == 1 && y == 7) and !(x == 4 && y == 8)
Platinum Azure
+2  A: 

To explain why 1 and 7 is still printing, step through the logic:

  1. if !(x == 1 && y == 7)
    • x == 1 is true and y == 7 is true, therefore !(true && true) is false, this is skipped.
  2. elsif !(x == 4 && y == 8)
    • the if was skipped, so the elsif is evaluated. x == 4 is false (since x is still 1) and y == 8 is false (since y is still 7). Therefore, !(false && false) is true, and the puts is reached.

Because x can never be both 1 and 4 at the same time and y can never be 7 and 8 at the same time, either your if statement or your elsif statement will always succeed, and since both branches print, the values will be always printed, no matter what x and y are.

As other answers said, you need to combine your clauses.

Daniel Vandersluis
A: 

Another variation using a patched Array class and the #reject method.

class Array
  def * arr
    outer = []
    self.each do |a|
      arr.each do |b|
        outer << [a,b]
      end
    end
    outer
  end
end


a = [1,2,3,4]
b = [5,6,7,8]

c = (a * b).reject {|pair| pair == [1,7] || pair == [4,8]}

c.each {|pair| puts "#{pair[0]} and #{pair[1]}"}

Not sure about using the '*' operator, it would need a better implementation if you wanted to keep the ability to multiply arrays by a Fixnum, but i like the syntax for applying methods to a combination of the two arrays which is a fairly common requirement.

Joc
Err, what happened with `Array#zip`?
Mladen Jablanović
@Mladen Jablanović: This is the Cartesian Product, not `zip`. That's why `*` is kinda sorta appropriate, although the Cartesian Product is actually `×`. In particular, the Cartesian Product is *not* commutative, but you would normally expect `*` to be.
Jörg W Mittag
Ah, right, I apologize. BTW, there's `Array#product` in 1.9: http://www.ruby-doc.org/ruby-1.9/classes/Array.html#M000760
Mladen Jablanović
Thanks Mladen, 'product' is probably a better name. Array#product will come in handy when 1.9 becomes the standard ruby version.
Joc
A: 

I think it would be a good idea to seperate out the seperate steps:

  1. compute the Cartesian Product of the two arrays: a.product(b)
  2. filter out the unwanted pairs: reject {|x, y| [[1, 7], [4, 8]].any? {|pair| [x, y] == pair } }
  3. convert to string: map {|pair| pair.join(' and ') }
  4. print it: puts

That's what we end up with:

puts a.product(b).reject {|x, y|
  [[1, 7], [4, 8]].any? {|pair| [x, y] == pair }
}.map {|pair| pair.join(' and ') }
Jörg W Mittag