What is the proper syntax for multiple comparisons? - ruby

Is there a proper syntax in Ruby for comparing multiple values against the same variable? For example:
#!/usr/bin/ruby -w
y = 15
p 'success' if y == 1 || y == 5 || y == -2 || y == 15132 || y == 3.14159265 || y == 15
Can that be written as something along the lines of:
y = 15
p 'success' if y == 1,5,-2,15132,3.14159265,15
And, if so, would that also apply to while loops?
y = 15
while y != 1,5,-2,15132,3.14159265,15
y = rand(50)
p y
end
Based on my search I'm tending to believe this is either not possible, or it's too obscure for my searches.
I hope it's the second case.
I have already considered an array itteration solution, but it's not as pretty or simple as I'd like.

You're looking for include?
p 'success' if [1,5,-2,15132,3.14159265,15].include? y

p 'success' if [1, 5, -2, 15132, 3.14159265, 15].include? y

case y
when 1, 5, -2, 15132, 3.14159265, 15 then p "success"
end

For a more general case you can use the any? method with a comparison block; this has the advantage of being usable with operators apart from ==:
p 'success' if [1, 5, -2, 15132, 3.14159265, 15].any? { |i| i == y }

From the Array#index :
Returns the index of the first object in ary such that the object is == to obj.Returns nil if no match is found.
p 'success' if [1,5,-2,15132,3.14159265,15].index(y)

Related

Ruby, making a number negative

This is probably super basic, but I've tried enough things that have failed to reach out..
I want to change a number to it's negative version.
answer = []
array = [3, 5, 2, 19, 2, 1]
array.each.with_index do |x, i|
if x > array[i+1]
answer << array[i] * -1
else x =< array[i+1]
answer << array[i]
end
end
=> the answer I want is [-5] for when 'true' but I'm getting [5]
I also tried making a new 'negarray' with all the equivalent negative numbers as 'array'
answer = []
array = [3, 5, 2, 19, 2, 1]
negarray = [-3, -5, -2, -19, -2, -1]
=> again, getting [5], and not the [-5] I want.
Cheers!
In the actual version the questions is unclear.
If you mean with
I want to change a number to it's negative version.
that you want always a negative number, then you could try:
answer = []
array = [3, 5, 6, 19, 2, 1]
array.each do |x|
if x > 0
answer << x * -1
else
answer << x
end
end
p answer
or
array.each do |x|
answer << if x > 0
x * -1
else
x
end
end
or with a ternary operator:
array.each do |x|
answer << (x > 0 ? -x : x)
end
Or shorter and more ruby-esk (using a ternary operator):
array = [3, 5, 6, 19, 2, -1]
answer = array.map { |n| n > 0 ? -n : n }
If you prefer the longer if:
answer = array.map do |n|
if n > 0
-n
else
n
end
end
If you don't want to use any if-structure, then you could use a negative abs-method:
answer = array.map { |n| -n.abs }
WIth the following line
if x > array[i+1]
You are basically saying if the element at position i is greater than the position at i+1, you want to make it negative. The problem is that 5 is smaller than the next element 6 and for that reason it isn't being negated.
Let's fix up your code, and use the map method to simplify it:
out = array.map.with_index do |x, i|
(array[i+1].nil? || x > array[i+1]) ? x : x*-1
end
# [-3, -5, -6, 19, 2, 1]
If you want to get the negative value of the second array element at index 1, do the following
answer << array[1] * -1
In order to change ALL values of an array to negative numbers, use the following
answer = array.map { |n| -n }

Compare sums of elements in an array: Ruby

I need to check whether the sum of any 2 elements of an array equals to the given number. This is what I came up with, but it doesn't seem to do the comparison
def sum_comparison(int_array, x)
n = int_array.length
(0..n).each do |i|
(1..n).each do |j|
if ((int_array[i].to_i + int_array[j].to_i) == x)
return true
else
return false
end
end
end
end
Your solution seems overly complicated and strongly influenced by the programming style of low-level procedural languages like C. One apparent problem is that you write
n = int_array.length
(0..n).each do |i|
# use int_array[i].to_i inside the loop
end
Now inside the each loop, you will get the numbers i = 0, 1, 2, ..., n, for example for int_array = [3,4,5] you get i = 0, 1, 2, 3. Notice that there are four elements, because you started counting at zero (this is called an off by one error). This will eventually lead to an array access at n, which is one beyond the end of the array. This will again result in a nil coming back, which is probably why you use to_i to convert that back to an integer, because otherwise you would get a TypeError: nil can't be coerced into Fixnum whend doing the addition. What you probably wanted instead was simply:
int_array.each do |i|
# use i inside the loop
end
For the example array [3,4,5] this would actually result in i = 3, 4, 5. To get the combinations of an array in a more Ruby way, you can for example use Array#combination. Likewise, you can use Array#any? to detect if any of the combinations satisfy the specified condition:
def sum_comparison(array, x)
array.combination(2).any? do |a, b|
a + b == x
end
end
When your function compare first element, it's immediately returns false. You need to return only true when iterating and return false at the end if nothing were found, to avoid this issue:
def sum_comparison(int_array, x)
n = int_array.size
(0...n).each do |i|
(1...n).each do |j|
if (int_array[i].to_i + int_array[j].to_i) == x
return true
end
end
end
false
end
To simplify this you can use permutation or combination and any? methods as #p11y suggests. To get founded elements you could use find or detect.
def sum_comparison(a, x)
a.combination(2).any? { |i, j| i + j == x }
end
a.combination(2).detect { |i, j| i + j == x }
# sum_comparison([1,2,3, 4], 6) => [2, 4]
Using an enumerator:
#!/usr/bin/env ruby
def sum_comparison(int_array, x)
enum = int_array.to_enum
loop do
n = enum.next
enum.peek_values.each do |m|
return true if (n + m) == x
end
end
false
end
puts sum_comparison([1, 2, 3, 4], 5)
Output:
true
Problem
Your method is equivalent to:
def sum_comparison(int_array, x)
return int_array[0].to_i + int_array[1].to_i == x
end
Therefore,
int_array = [1,2,4,16,32,7,5,7,8,22,28]
sum_comparison(int_array, 3) #=> true, just lucky!
sum_comparison(int_array, 6) #=> false, wrong!
Alternative
Here is a relatively efficient implemention, certainly far more efficient than using Enumerable#combination.
Code
def sum_comparison(int_array, x)
sorted = int_array.sort
smallest = sorted.first
sorted_stub = sorted.take_while { |e| e+smallest <= x }
p "sorted_stub = #{sorted_stub}"
return false if sorted_stub.size < 2
loop do
return false if sorted_stub.size < 2
v = sorted_stub.shift
found = sorted_stub.find { |e| v+e >= x }
return true if found && v+found == x
end
false
end
Examples
sum_comparison([7,16,4,12,-2,5,8], 3)
# "sorted_stub = [-2, 4, 5]"
#=> true
sum_comparison([7,16,4,12,-2,5,8], 7)
# "sorted_stub = [-2, 4, 5, 7, 8]"
#=> false
sum_comparison([7,16,4,22,18,12,2,41,5,8,17,31], 9)
# "sorted_stub = [2, 4, 5, 7]"
#=> true
Notes
The line p "sorted_stub = #{sorted_stub}" is included merely to display the array sorted_stub in the examples.
If e+smallest > x for any elements f and g in sorted for which g >= e and f < g, f+g >= e+smallest > x. Ergo, sorted_stub.last is the largest value in sorted that need be considered.
For a given value v, the line found = sorted_stub.find { |e| v+e >= x } stops the search for a second value e for which v+e = x as soon as it finds e such that v+e >= x. The next line then determines if a match has been found.

Checking to see if 2 numbers in array sum to 0 in Ruby

I've been going at this problem for a few hours, and I can't see why I can't get it to run properly. The end game to this method is having 2 numbers in an array equaling zero when added together. Here is my code:
def two_sums(nums)
i = 0
j = -1
while i < nums.count
num_1 = nums[i]
while j < nums.count
num_2 = nums[j]
if num_1 + num_2 == 0
return "There are 2 numbers that sum to zero & they are #{num_1} and #{num_2}."
else
return "Nothing adds to zero."
end
end
i += 1
j -= 1
end
end
The problem I'm having is unless the first and last number in the array are the positive and negative of the same number, this will always return false.
For example, if I had an array that was [1, 4, 6, -1, 10], it should come back true. I'm sure my 2 while statement is the cause of this, but I can't think of a way to fix it. If someone could point me in the right direction, that would be helpful.
You can find the first pair that adds up to 0 like this:
nums.combination(2).find { |x, y| x + y == 0 }
#=> returns the first matching pair or nil
Or if you want to select all pairs that add up to 0:
nums.combination(2).select { |x, y| x + y == 0 }
#=> returns all matching pairs or an empty array
Therefore you can implement your method like this:
def two_sums(nums)
pair = nums.combination(2).find { |x, y| x + y == 0 }
if pair
"There are 2 numbers that sum to zero & they are #{pair.first} and #{pair.last}."
else
"Nothing adds to zero."
end
end
Or if you want to find all pairs:
def two_sums(nums)
pairs = nums.combination(2).select { |x, y| x + y == 0 }
if pairs.empty?
"Nothing adds to zero."
else
"The following pairs sum to zero: #{pairs}..."
end
end
Here's another way:
Code
def sum_to_zero(arr)
arr.group_by { |e| e.abs }
.values
.select { |a| (a.size > 1 && a.first == 0) || a.uniq.size > 1 }
end
Examples
sum_to_zero [1, 4, 6, -1, 10] #=> [[1, -1]]
sum_to_zero [1, 4, 1, -2, 10] #=> []
sum_to_zero [1, 0, 4, 1, 0, -1] #=> [[1, 1, -1], [0, 0]]
This method is relatively fast. Let's try it with an array of 200,000 elements, each a random number between -500,000 and 500,000.
require 'time'
t = Time.now
arr = Array.new(200_000) { rand(1_000_001) - 500_000 }
arr.size #=> 200000
sum_to_zero(arr).size #=> 16439
Time.now - t
#=> 0.23 (seconds)
sum_to_zero(arr).first(6)
#=> [[-98747, 98747],
# [157848, -157848],
# [-459650, 459650],
# [176655, 176655, -176655],
# [282101, -282101],
# [100886, 100886, -100886]]
If you wish to group the non-negative and negative values that sum to zero:
sum_to_zero(arr).map { |a| a.partition { |e| e >= 0 } }.first(6)
#=> [[[98747], [-98747]],
# [[157848], [-157848]],
# [[459650], [-459650]],
# [[176655, 176655], [-176655]],
# [[282101], [-282101]],
# [[100886, 100886], [-100886]]]
If you only want a single value for each group (a non-negative value, say):
sum_to_zero(arr).map { |a| a.first.abs }.first(6)
#=> [98747, 157848, 459650, 176655, 282101, 100886]
I think the most Ruby way would be:
nums.combination(2).any? { |x,y| (x+y).zero? }
Here's a way that should work well for large arrays. The methods above which go through every possible combination of two numbers are perfectly fine for small cases but will be very slow and memory hungry for arrays with lots of elements.
def two_sums nums
h = Hash.new
nums.each do |n|
return true if h[-n]
h[n] = true
end
false
end
Well, given it's tagged as #ruby, here's the most "ruby way" I could think of tackling this problem:
def two_sums(arr)
numbers = arr.combination(2).select { |a| a.reduce(:+) == 0 }.flatten
if numbers.empty?
"Nothing adds to zero."
else
"There are 2 numbers that sum to zero & they are #{numbers.first} and #{numbers.last}."
end
end
array.combination(2).select{|x|x[0] + x[1] == 0}

Modify particular items in array while traversing in reverse order in Ruby

Is there a way to modify particular array elements (based on some condition) while traversing it in reverse order in Ruby?
To be more clear lets say,
problem is replace even numbers in [1,2,3,4,5] with x
output should be [1,x,3,x,5] (same array) but replace should happen from right to left..traversing from 5 to 1.
Thanks in Advance!
This works: (arr.length -1).downto(0) { |x| do something with arr[x] }
p [1,2,3,4,5].reverse_each.map{|e| e.odd? ? e : e/2} #[5, 2, 3, 1, 1]
I understand you want to traverse in reverse order, not get the output also reversed. Maybe this:
xs = [1, 2, 3]
xs.reverse_each.with_index { |x, idx| xs[xs.size-1-idx] = x.to_s if x == 2 }
xs #=> [1, "2", 3]
I appreciate and love Ruby's humane syntax, but you may want to consider more verbose options such as:
ary = [1,2,3,4,5]
i = ary.count - 1
while i >= 0 do
ary[i] = "x" if ary[i] % 2 == 0
i -= 1
end
puts ary.join(",")

Exclusions in map in Ruby

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.
To explain why 1 and 7 is still printing, step through the logic:
if !(x == 1 && y == 7)
x == 1 is true and y == 7 is true, therefore !(true && true) is false, this is skipped.
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.
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)
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
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.
I think it would be a good idea to seperate out the seperate steps:
compute the Cartesian Product of the two arrays: a.product(b)
filter out the unwanted pairs: reject {|x, y| [[1, 7], [4, 8]].any? {|pair| [x, y] == pair }
}
convert to string: map {|pair| pair.join(' and ') }
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 ') }

Resources