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

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.

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.

Remove next elements in array with ruby

Given an array containing numbers the following rules apply:
a 0 removes all previous numbers and all subsequent adjacent even numbers.
a 1 removes all previous numbers and all subsequent adjacent odd numbers.
if the first element of the array is 1 it can be removed
I am trying to write an algorithm to reduce the array but I could come up only with a bad looking solution:
def compress(array)
zero_or_one_index = array.rindex { |element| [0,1].include? element }
array.slice!(0, zero_or_one_index) if zero_or_one_index
deleting = true
while deleting
deleting = false
array.each_with_index do |element, index|
next if index.zero?
previous_element = array[index - 1]
if (previous_element == 0 && element.even?) ||
(previous_element == 1 && element.odd?)
array.delete_at(index)
deleting = true
break
end
end
end
array.shift if array[0] == 1
end
The problem is that delete_if and similar, start messing up the result, if I delete elements while iterating on the array, therefore I am forced to use a while loop.
Examples:
compress([3, 2, 0]) #=> [0]
compress([2, 0, 4, 6, 7]) #=> [0,7]
compress([2, 0, 4, 1, 3, 6]) #=> [6]
compress([3, 2, 0, 4, 1, 3, 6, 8, 5]) #=> [6,8,5]
This problem arises in the context of some refactorings I am performing on cancancan to optimize the rules definition.
Here is how I would solve the problem:
def compress(arr)
return arr unless idx = arr.rindex {|e| e == 0 || e == 1}
value = arr[idx]
method_options = [:even?,:odd?]
arr[idx..-1].drop_while do |n|
n.public_send(method_options[value])
end.tap {|a| a.unshift(value) if value.zero? }
end
First we find index of the last occurrence of 0 or 1 using Array#rindex. If none then return the Array.
Then we get the value at that index.
Then we use Array#[] to slice off the tail end of the Array starting at the index.
Then drop all the consecutive adjacent :even? or :odd? numbers respective to the value (0 or 1) using Array#drop_while.
Finally if the value is 0 we place it back into the front of the Array before returning.
Examples
compress([3, 2, 0])
#=> [0]
compress([2, 0, 4, 6, 7])
#=> [0,7]
compress([2, 0, 4, 1, 3, 6])
#=> [6]
compress([3, 2, 0, 4, 1, 3, 6, 8, 5])
#=> [6,8,5]
compress([4, 5, 6])
#=> [4,5,6]
compress([0])
#=> [0]
compress([1])
#=> []
If your goal was to be mutative, as your question and gist seem to suggest, I honestly would not change what I have but rather go with:
def compress!(arr)
arr.replace(compress(arr))
end
For example
a = [3, 2, 0, 4, 1, 3, 6, 8, 5]
a == compress!(a)
#=> true
a
#=> [6,8,5]

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 Implementing merge_sort algorithm leaving off elements from input array

I am trying to implement a merge sort function into my app. It takes an array as an input, sorts it, and outputs the sorted array.
def sort(list)
swapped = true
sorted_list = []
slice_count = list.size.to_i
chunked_list = list.each_slice(slice_count).to_a.each{ |element| element.fill nil, slice_count, 0 }.transpose.map(&:compact)
while swapped do
swapped = false
(slice_count-1).times do |i|
if chunked_list[i][0] > chunked_list[i+1][0]
chunked_list[i], chunked_list[i+1] = chunked_list[i+1], chunked_list[i]
swapped = true
end
end
break if !swapped
end
(slice_count-1).times do |i|
sorted_list.push(chunked_list[i][0])
end
puts "Sorted list (merge): #{sorted_list}"
end
My issue arises from getting the input array.
Running merge.sort([0, 3, 8, 5, 4, 9, 22]) outputs a sorted array WITHOUT the 0 and 22: Sorted list (merge): [3, 4, 5, 8, 9]
Debugging and returning the 'list' variable in pry gives me [3, 4, 5, 8, 9, 22], which includes the 22 not present in the final output, but still excluding the 0 element from the input array. Why is it not taking the full array?
you need to remove - 1 on (slice_count).times do
def sort(list)
swapped = true
sorted_list = []
slice_count = list.size.to_i
chunked_list = list.each_slice(slice_count).to_a.each{ |element| element.fill nil, slice_count, 0 }.transpose.map(&:compact)
while swapped do
swapped = false
(slice_count-1).times do |i|
if chunked_list[i][0] > chunked_list[i+1][0]
chunked_list[i], chunked_list[i+1] = chunked_list[i+1], chunked_list[i]
swapped = true
end
end
break if !swapped
end
(slice_count).times do |i|
sorted_list.push(chunked_list[i][0])
end
puts "Sorted list (merge): #{sorted_list}"
end
Sorted list (merge): [0, 3, 4, 5, 8, 9, 22]

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