cheenbabes
cheenbabes

Reputation: 382

Checking arrays and implementing bool methods

You have an array. If any two numbers add to zero in the array, return true. It doesn't matter how many pairs there are—as long as there is one pair that adds to zero, return true. If there is a zero, it can only return true if there is more than one.

I wrote two functions, one to check for each, and a final one to combine both, and return false if either aren't met.

def checkZero(array)
  zerocount = 0
  for j in 0..array.count
    if array[j] == 0
      zerocount += 1
    end
  end
  if zerocount > 1 #this part seems to not be working, not sure why
    return true
  else
    return false
  end
end

def checkNegative(array)
  for j in 0..array.count
    neg = -array[j] #set a negative value of the current value
    if array.include?(neg) #check to see whether the negative exists in the array
      return true
    else
      return false
    end
  end
end

def checkArray(array)
  if checkZero(array) == true or checkNegative(array) == true
    return true
  else
    return false
  end
end

Then run something like

array = [1,2,3,4,0,1,-1]
checkArray(array)

So far, Ruby isn't returning anything. I just get a blank. I have a feeling my return isn't right.

Upvotes: 0

Views: 100

Answers (4)

Cary Swoveland
Cary Swoveland

Reputation: 110685

Here's are a few relatively efficient ways to check if any two values sum to zero:

Solution #1

def checkit(a)
  return true if a.count(&:zero?) > 1
  b = a.uniq.map(&:abs)
  b.uniq.size < b.size
end

Solution #2

def checkit(a)
  return true if a.sort_by(&:abs).each_cons(2).find { |x,y| x == -y }
  false
end

Solution #3

def checkit(a)
  return true if a.count(&:zero?) > 1
  pos, non_pos = a.group_by { |n| n > 0 }.values
  (pos & non_pos.map { |n| -n }).any?
end

Solution #4

require 'set'

def checkit(a)
  a.each_with_object(Set.new) do |n,s|
    return true if s.include?(-n)
    s << n
  end
  false
end        

Examples

checkit([1, 3, 4, 2, 2,-3,-5,-7, 0, 0]) #=> true
checkit([1, 3, 4, 2, 2,-3,-5,-7, 0])    #=> true 
checkit([1, 3, 4, 2,-3, 2,-3,-5,-7, 0]) #=> true
checkit([1, 3, 4, 2, 2,-5,-7, 0])       #=> false

Explanations

The following all refer to the array:

a = [1,3,4,2,2,-3,-5,-7,0]

#1

Zeroes present a bit of a problem, so lets first see if there are more than one, in which case we are finished. Since a.count(&:zero?) #=> 1, a.count(&:zero?) > 1 #=> false, so

return true if a.count(&:zero?) > 1

does not cause us to return. Next, we remove any duplicates:

a.uniq                #=> [1, 3, 4, 2, -3, -5, -7, 0]

Then convert all the numbers to their absolute values:

b = a.uniq,map(&:abs) #=> [1, 3, 4, 2,  3,  5,  7, 0]

Lastly see if c contains any dups, meaning the original array contained at least two non-zero numbers with opposite signs:

c.uniq.size < c.size  #=> true

#2

b = a.sort_by(&:abs)
  #=> [0, 1, 2, 2, 3, -3, 4, -5, -7]

c = b.each_cons(2)
  #=> #<Enumerator: [0, 1, 2, 2, 3, -3, 4, -5, -7]:each_cons(2)>

To see the contents of the enumerator:

c.to_a
  #=> [[0, 1], [1, 2], [2, 2], [2, 3], [3, -3], [-3, 4], [4, -5], [-5, -7]]

c.find { |x,y| x == -y }
  #=> [3, -3]

so true is returned.

#3

return true if a.count(&:zero?) > 1
  #=> return true if 1 > 1

h = a.group_by { |n| n > 0 }
  #=> {true=>[1, 3, 4, 2, 2], false=>[-3, -5, -7, 0]} 
b = h.values 
  #=> [[1, 3, 4, 2, 2], [-3, -5, -7, 0]] 
pos, non_pos = b
pos
  #=> [1, 3, 4, 2, 2]
non_pos
  #=> [-3, -5, -7, 0]
c = non_pos.map { |n| -n }
  #=> [3, 5, 7, 0] 
d = pos & c
  #=> [3] 
d.any?
  #=> true 

#4

require 'set'

enum = a.each_with_object(Set.new)
  #=> #<Enumerator: [1, 3, 4, 2, 2, -3, -5, -7, 0]:each_with_object(#<Set: {}>)>
enum.to_a
  #=> [[1, #<Set: {}>],
  #    [3, #<Set: {}>],
  #    ...
  #    [0, #<Set: {}>]]

Values are passed into the block, assigned to the block variables and the block is executed, as follows:

n, s = enum.next
  #=> [1, #<Set: {}>] 
s.include?(-n)
  #=> #<Set: {}>.include?(-1)
  #=> false
s << n
  #=> #<Set: {1}>

n, s = enum.next
  #=> [3, #<Set: {1}>] 
s.include?(-3)
  #=> false 
s << n
  #=> #<Set: {1, 3}> 

...

n, s = enum.next
  #=> [2, #<Set: {1, 3, 4, 2}>] 
s.include?(-n)
  #=> false 
s << n
  #=> #<Set: {1, 3, 4, 2}> # no change

n, s = enum.next
  #=> [-3, #<Set: {1, 3, 4, 2}>] 
s.include?(-n)
  #=> true 

causing true to be returned.

Upvotes: 2

Arie Xiao
Arie Xiao

Reputation: 14082

The problem may be that you didn't output the result.

array = [1,2,3,4,0,1,-1]
puts checkArray(array)

The checkArray method can be written like the following, if performance (O(n^2)) is not a great concern:

def check_array(array)
  array.combination(2).any?{|p| p.reduce(:+) == 0}
end

The more efficient (O(n log n)) solution is:

def check_array(array)
  array.sort! # `array = array.sort` if you need the original array unchanged
  i, j = 0, array.size - 1
  while i < j
    sum = array[i] + array[j]
    if sum > 0
      j -= 1
    elsif sum < 0
      i += 1
    else
      return true
    end
  end
  return false
end

Upvotes: 2

Mark Thomas
Mark Thomas

Reputation: 37517

This is a bit of a code review. Let's start with the first method:

def checkZero(array)

Ruby naming convention is snake_case rather than camelCase. This should be def check_zero(array)

Now the loop:

  zerocount = 0
  for j in 0..array.count
    if array[j] == 0
      zerocount += 1
    end
  end

As @AndrewMarshall said, for is not idiomatic. each is preferable. However, in ruby initializing a variable before a loop is almost never needed thanks to all the methods available to you on Array and Enumerable (which is included in Array). I highly recommend committing these methods to memory. The above can be written

array.any? {|number| number.zero?}

or equivalently

array.any?(&:zero?)

Now, this part:

  if zerocount > 1 #this part seems to not be working, not sure why
    return true
  else
    return false
  end
end

Whenever you have the pattern

if (expr that returns true or false)
  return true
else
  return false
end

it can be simplified to simply return (expr that returns true or false). And you can even omit the return if it is the last statement of a method.

Putting it all together:

def check_zero(array)
  array.any?(&:zero?)
end

def check_zero_sum(array)
  array.combination(2).any?{|a,b| a + b == 0}
end

def check_array(array)
  check_zero(array) || check_zero_sum(array)
end

(Note I borrowed AndrewMarshall's code for check_zero_sum which I think is easy to follow, but @CarySwoveland's answer will be faster)

Edit

I missed the fact that check_zero isn't even necessary because you want at least a pair, in which case check_zero_sum is all you need.

def check_array(array)
  array.combination(2).any?{|a,b| a + b == 0}
end

Upvotes: 1

Andrew Marshall
Andrew Marshall

Reputation: 96954

I can’t reproduce any problem with your code, but you can express the solution very succinctly using combination to get all possible pairs, then summing each pair with reduce, and finally checking if any are zero?:

[1,2,3,4,0,1,-1].combination(2).map { |pair| pair.reduce(:+) }.any?(&:zero?)

Upvotes: 1

Related Questions