Ruby 2.0.0 Array#bsearch behavior - arrays

I noticed that as of Ruby 2.0.0 the array class has a bsearch method that I was testing and I'm not getting the behavior I'd expect. Why does it return a value for 2 and 5 but nil for -1, 1, and 4?
arr_in = [-1, 1, 2, 4, 5]
arr_in.bsearch { |x| x == 3 } #=> nil
arr_in.bsearch { |x| x == -1 } #=> nil
arr_in.bsearch { |x| x == 1 } #=> nil
arr_in.bsearch { |x| x == 2 } #=> 2
arr_in.bsearch { |x| x == 4 } #=> nil
arr_in.bsearch { |x| x == 5 } #=> 5

arr_in = [-1, 1,2,4,5]
arr_in.bsearch{ |x| 2 - x }
#=> 2
arr_in.bsearch{ |x| -1 - x }
#=> -1
arr_in.bsearch{ |x| 3 - x }
#=> nil
Binary search uses block's result as a hint which part of array (left or right side) should be chosen for searching on next iteration. If block returns 0 it will stop searching. If it returns less then 0 it will go left otherwise it goes right :)
More information here
http://www.ruby-doc.org/core-2.1.1/Array.html#method-i-bsearch
UPD
Ok, let's take your example
arr_in = [-1, 1, 2, 4, 5]
arr_in.bsearch { |x| x == 3 }
First we will take middle element (2) and yield it to the block. 2 == 3 will return false, so we move to the right side of the array.
We take middle element of [4, 5] which is 5 and 5 == 3 is false
There is no any elements on the right, so we will return nil
arr_in = [-1, 1, 2, 4, 5]
arr_in.bsearch { |x| x == 2 }
First 2 == 2 is true. We go to the left.
Middle element of [-1, 1] is 1. 1 == 2 is false. We go to the right.
There is no any elements in [-1, 1] right to the 1, so we return last last element which returned true statement which is 2
PS: don't forget, that the array should be sorted ;)

I find it more intuitive to use the spaceship operator
array.bsearch {|x| 3 <=> x }
Just make sure to put the x to the right of the spaceship.
The reason why is that the thing being compared against during each iteration is the left-hand operand. So if you want to find a 3, you need to continually compare against a 3, in order to get the correct left, right, or equal result. If you put the variable on the left (as one might intuitively to do), you've reversed the comparison output, foiling the bsearch algorithm!
This also works for strings and any object that is comparable with <=>.

Related

All_else method (Ruby)

I'm trying to create a method that iterates through an array and add up all of its elements and returns the element that is half of its sum, else it will return nil.
Examples:
p all_else_equal([2, 4, 3, 10, 1]) #=> 10, because the sum of all elements is 20
p all_else_equal([6, 3, 5, -9, 1]) #=> 3, because the sum of all elements is 6
p all_else_equal([1, 2, 3, 4]) #=> nil, because the sum of all elements is 10 and there is no 5 in the array.
My solution was to iterate through the array and add each element together using a 'sum' variable. Then write a conditional statement stating if half of the sum is included in the arr, then return the element, else return nil. But for what ever reseason I keep getting 'nil'. Can anyone out there tell me why this is wrong? Here's my code:
def all_else_equal(arr)
sum = 0
sum_half = sum / 2
arr.each_with_index do |ele, i|
sum += ele
if sum_half == ele
return ele
else
return nil
end
end
end
console:
nil
so your code will return nil right after the first value. this is because
the return condition is in the loop. to solve this, move it out as shown below.
also, create the sum_half variable after the sum has already been
evaluated:
def all_else_equal(arr)
sum = 0
arr.each_with_index do |ele, i|
sum += ele
end
sum_half = sum / 2
if arr.include?(sum_half) #check if sum_half in array
return sum_half
else
return nil
end
end
p all_else_equal([2, 4, 3, 10, 1]) #=> 10, because the sum of all elements is 20
p all_else_equal([6, 3, 5, -9, 1]) #=> 3, because the sum of all elements is 6
p all_else_equal([1, 2, 3, 4]) #=> nil, because the sum of all elements is 10 and there is no 5 in the array.
a simpler alternative:
def all_else_equal(arr)
sum_half = arr.sum / 2
arr.include?(sum_half) ? sum_half : nil
end
p all_else_equal([2, 4, 3, 10, 1]) #=> 10, because the sum of all elements is 20
p all_else_equal([6, 3, 5, -9, 1]) #=> 3, because the sum of all elements is 6
p all_else_equal([1, 2, 3, 4]) #=> nil, because the sum of all elements is 10 and there is no 5 in the array.
I see a few things here. First, in Ruby and all imperative languages, variable assignments are evaluated only at their time of execution -- so your sum_half variable will always be equal to 0 / 2 or 0. It will not dynamically re-evaluate to always be equal to sum / 2. You would need to recompute it after every iteration of the loop for it to be accurate.
Second, from a logical perspective, your sum variable is only really the sum so far. Checking if half of it is equal to the current element is not what you want to do, because even if that's true, it doesn't mean the current element is half of the final sum. Instead, you might want to find the full sum, divide it in two, and then look for an element that matches that value.
Also, stylistically, your each_with_index is currently unnecessary because you're not using the index at all -- change it to just an each until you find a use for that index value.
#RobertNubel and #PhiAgent have great answers - I would suggest you especially work through PhiAgent's Answer.
I will only add a worked example for the first iteration of the loop so you can see exactly what is happeneing
A worked Example with Comments:
def all_else_equal(arr) ## let's say array = [1,2] is passed in as a parameter
sum = 0
sum_half = sum / 2 ## => sum_half = 0
arr.each_with_index do |ele, i| # => elem = 1 (given the first element in the array)
sum += ele # => sum is now 1
if sum_half == ele # => 0 == 1 ## this will be false
return ele
else
return nil # => nil be returned
end
end
end
#PhiAgent has a great solution to get the code working.

Compare an element from one array with an element from another array

I have two arrays of numbers that have the same size. How can I tell if there is any element in the second array that is greater than the first array at a given index? With this example:
a = [2, 8, 10]
b = [3, 7, 5]
3 is greater than 2 at position 0. But in the following:
a = [1, 10]
b = [0, 8]
there is no such element. At index 0, 0 is not greater than 1, and at index 1, 8 is not greater than 10.
Try this one
a.each_with_index.any? { |item, index| b[index] > item }
No need for indices. Just pair them and check each pair.
b.zip(a).any? { |x, y| x > y }
=> true or false
And a tricky one: Check whether at every position, a is the maximum:
a.zip(b).map(&:max) != a
=> true or false
And a very efficient one (both time and space):
b.zip(a) { |x, y| break true if x > y }
=> true or nil
(If you need true/false (often you don't, for example in if-conditions), you could prepend !! or append || false)
If there's a number in b greater than the number in a at the given index, this will return the number in b. If no numbers in b are greater, nil will be returned.
b.detect.with_index { |n, index| n > a[index] }
For example, if you have the following arrays.
a = [3, 4, 5]
b = [6, 7, 8]
You'll get a return value of 6.

ruby - what is wrong with the following array and target sum code?

Two-Sum
Define a method, two_sum, that accepts an array and a target sum (integer) as arguments.
The method should return true if any two integers in the array sum to the target.
Otherwise, it should return false. Assume the array will only contain integers.
def two_sum(array, target)
i = 0
sum = []
while i < array.max
i = i + 1
b = i + i
sum.push(b)
end
sum.include?(target)
end
puts "------Two Sum------"
puts two_sum([1,2,3,4,5,6], 8) == true #(im getting true)
puts two_sum([1,2,3,4,5,6], 18) == false #(im getting true)
puts two_sum([1,3,6], 6) == false #(im getting false)
puts two_sum([1,8,2,1], 0) == false #(im getting true)
This is an attempt to speed the calculations when performance is important, particularly when the array is large and contains many duplicate values.
Code
require 'set'
def two_sum(arr, target)
return true if target.even? && arr.count(target/2) > 1
st = Set.new
arr.uniq.each do |n|
return true if st.include?(target-n)
st << n
end
false
end
Examples
two_sum [1, 4, -4, 4, 5], 6 #=> true
two_sum [1, 3, -4, 3, 4], 6 #=> true
two_sum [1, 3, -4, 3, 5], 5 #=> false
Explanation
The code for even values of target serves two purposes:
it short-circuits the calculations when the array contains a value that equals one-half of target, and that value appears at least twice in the array; and
should the aforementioned code not return true, it permits the removal of duplicate values in arr before the remaining calculations are performed.
For the first example the steps are as follows.
arr = [1, 4, -4, 4, 5]
target = 6
target.even?
#=> 6.even? => true
arr.count(target/2) > 1
#=> arr.count(3) > 1
#=> 1 > 1
#=> false
so true is not returned.
st = Set.new
=> #<Set: {}>
b = arr.uniq
#=> [1, 4, -4, 5]
The first element of b is now passed to the block.
n = 1
st.include?(target-n)
#=> st.include?(6-1) => false as the set is empty
st << n
#=> #<Set: {1}>
The next steps are as follows.
n = 4
st.include?(target-n)
#=> st.include?(6-4) => false
st << n
#=> #<Set: {1, 4}>
n = -4
st.include?(target-n)
#=> st.include?(6-(-4)) => false
st << n
#=> #<Set: {1, 4, -4}>
n = 5
st.include?(target-n)
#=> st.include?(6-5) => true
so true is returned.
The ruby solution looks like:
def two_sum(array, target)
array.combination(2).any? { |v| v.reduce(:+) == target }
end
Array#combination returns all the combinations of two elements and Enumerable#any? returns true if the block evaluates to true and false otherwise.
You loop through each element of the array, which is a good start. Inside this loop, you need to try adding together each pair of elements. At the moment, all you're pushing to your sum array is i+i: the index doubled (i.e. every even number from 0 to double the largest element of your array).
You could attempt to sum each pair of elements by having two loops, one inside the other. They'd both iterate through the elements of the array. In your inner loop, you'd attempt to add the current element from the outer loop to the current element from the inner loop. If you get any matches, you can return true and quit immediately; otherwise return false.

Ruby sorting even and odd numbers issue

I'm learning Ruby and just started with the sorting. Trying to sort the array like this: [1,3,5,2,4,6] and I'm don't really understand what is wrong with the code. Any help would be appreciated!
[1,2,3,4,5,6].sort do |x,y|
if x.odd? and y.odd?
0
elsif x.odd?
-1
else
1
end
if (x.odd? && y.odd?) or (x.even? && y.even?)
x <=> y
end
end
First off, let's fix your indentation (and convert to standard Ruby community coding style), so that we can better see what's going on:
[1, 2, 3, 4, 5, 6].sort do |x, y|
if x.odd? && y.odd?
0
elsif x.odd?
-1
else
1
end
if (x.odd? && y.odd?) || (x.even? && y.even?)
x <=> y
end
end
Now, the problem becomes obvious: your first conditional expression evaluates to 0, -1, or 1, but nothing is being done with this value. The value is not stored in a variable, not passed as an argument, not returned. It is simply ignored. The entire expression is a NO-OP.
Which means that the only thing that matters is this:
if (x.odd? && y.odd?) || (x.even? && y.even?)
x <=> y
end
This will return 0 for two elements that are equal, -1 or 1 for two elements that are unequal but both odd or both even, and nil (which to sort means "these two elements are un-comparable, they don't have a defined relative ordering") for elements where one element is odd and one is even. Since sort requires all elements to be comparable, it will then abort.
The easiest way to approach this problem would probably be to partition the array into odds and evens, sort them separately, and then concatenate them:
[1, 2, 3, 4, 5, 6].partition(&:odd?).map(&:sort).inject(:concat)
#=> [1, 3, 5, 2, 4, 6]
Or do it the other way round, just sort them all, and then partition (Thanks #Eric Duminil):
[1, 2, 3, 4, 5, 6].sort.partition(&:odd?).inject(:concat)
#=> [1, 3, 5, 2, 4, 6]
It's probably the first time I ever used a negative modulo :
i % -2 is -1 if i is odd
i % -2 is 0 if i is even
So sorting by i % -2 first and then by i should achieve the desired result.
If you want even numbers before odd numbers, you can sort by i % 2.
[3, 2, 1, 5, 6, 4].sort_by{ |i| [ i % -2, i] }
#=> [1, 3, 5, 2, 4, 6]
Thanks to #Stefan for his original idea!

How do I count the number of elements in my array that are unique and are bigger than the element before them?

I'm using Ruby 2.4. I have an array of strings taht are all numbers. I want to count the number of elements in the array that are unique and that are also greater than the element before them (I consider the first array element already greater than its non-existent predecessor). So I tried
data_col = ["3", "6", "10"]
#=> ["3", "6", "10"]
data_col.map { |string| string.to_i.to_s == string ? string.to_i : -2 }.each_cons(2).select { |a, b| a > b && data_col.count(a) == 1 }.count
#=> 0
but the results is zero, despite the fact that all the elements in my array satisfy my criteria. How can I improve the way I count this?
require 'set'
def nbr_uniq_and_bigger(arr)
processed = Set.new
arr.each_cons(2).with_object(Set.new) do |(n,m),keepers|
if processed.add?(m)
keepers << m if m > n
else
keepers.delete(m)
end
end.size + (processed.include?(arr.first) ? 0 : 1)
end
nbr_uniq_and_bigger [1, 2, 6, 3, 2]
#=> 2
nbr_uniq_and_bigger [1, 2, 1, 2, 1, 2]
#=> 0
See Set.add?.
Note the line keepers.delete(m) could be written
keepers.delete(m) if keepers.key(m)
but attempting to delete an element not in the set does not harm.
There are a few things wrong here:
a > b seems like the opposite of what you want to test. That should probably be b > a.
If I followed properly, I think data_col.count(a) is always going to be zero, since a is an integer and data_col contains only strings. Also, I'm not sure you want to be looking for a... b is probably the right element to look for.
I'm not sure you're ever counting the first element here. (You said you consider the first element to be greater than its non-existent predecessor, but where in your code does that happen?)
Here's some code that works:
def foo(x)
([nil] + x).each_cons(2).select { |a, b| (a == nil or b > a) and x.count(b) == 1 }.count()
end
p foo([3, 6, 10]) # 3
p foo([3, 6, 10, 1, 6]) # 2
(If you have an array of strings, feel free to do .map { |s| s.to_i } first.)
One more solution:
def uniq_and_bigger(data)
counts = data.each_with_object(Hash.new(0)) { |e, h| h[e] += 1 } #precalculate
data.each_cons(2).with_object([]) do |(n,m), a|
a << m if m > n && counts[m] == 1
end.size + (counts[data[0]] == 1 ? 1 : 0)
end
uniq_and_bigger([3, 6, 10, 1, 6])
=> 2
uniq_and_bigger([1, 2, 1, 2, 1, 2])
=> 0
Yet another solution. It's O(n) and it returns the desired result for [3, 6, 10].
It uses slice_when :
def unique_and_increasing(array)
duplicates = array.group_by(&:itself).select{ |_, v| v.size > 1 }.keys
(array.slice_when{ |a, b| a < b }.map(&:first) - duplicates).size
end
p unique_and_increasing [3, 6, 10]
# 3
p unique_and_increasing [3, 6, 10, 1, 6]
# 2
p unique_and_increasing [1, 2, 1, 2, 1, 2]
# 0

Resources