How to improve algorithm efficiency for nested loop - ruby

Given a list of integers and a single sum value, return the first two values (from the left) that add up to form the sum.
For example, given:
sum_pairs([10, 5, 2, 3, 7, 5], 10)
[5, 5] (at indices [1, 5] of [10, 5, 2, 3, 7, 5]) add up to 10, and [3, 7] (at indices [3, 4]) add up to 10. Among them, the entire pair [3, 7] is earlier, and therefore is the correct answer.
Here is my code:
def sum_pairs(ints, s)
result = []
i = 0
while i < ints.length - 1
j = i+1
while j < ints.length
result << [ints[i],ints[j]] if ints[i] + ints[j] == s
j += 1
end
i += 1
end
puts result.to_s
result.min
end
It works, but is too inefficient, taking 12000 ms to run. The nested loop is the problem of inefficiency. How could I improve the algorithm?

Have a Set of numbers you have seen, starting empty
Look at each number in the input list
Calculate which number you would need to add to it to make up the sum
See if that number is in the set
If it is, return it, and the current element
If not, add the current element to the set, and continue the loop
When the loop ends, you are certain there is no such pair; the task does not specify, but returning nil is likely the best option
Should go superfast, as there is only a single loop. It also terminates as soon as it finds the first matching pair, so normally you wouldn't even go through every element before you get your answer.
As a style thing, using while in this way is very unRubyish. In implementing the above, I suggest you use ints.each do |int| ... end rather than while.
EDIT: As Cary Swoveland commented, for a weird reason I thought you needed indices, not the values.

require 'set'
def sum_pairs(arr, target)
s = Set.new
arr.each do |v|
return [target-v, v] if s.include?(target-v)
s << v
end
nil
end
sum_pairs [10, 5, 2, 3, 7, 5], 10
#=> [3, 7]
sum_pairs [10, 5, 2, 3, 7, 5], 99
#=> nil
I've used Set methods to speed include? lookups (and, less important, to save only unique values).

Try below, as it is much more readable.
def sum_pairs(ints, s)
ints.each_with_index.map do |ele, i|
if ele < s
rem_arr = ints.from(i + 1)
rem = s - ele
[ele, rem] if rem_arr.include?(rem)
end
end.compact.last
end

One liner (the fastest?)
ary = [10, 0, 8, 5, 2, 7, 3, 5, 5]
sum = 10
def sum_pairs(ary, sum)
ary.map.with_index { |e, i| [e, i] }.combination(2).to_a.keep_if { |a| a.first.first + a.last.first == sum }.map { |e| [e, e.max { |a, b| a.last <=> b.last }.last] }.min { |a, b| a.last <=> b.last }.first.map{ |e| e.first }
end
Yes, it's not really readable, but if you add methods step by step starting from ary.map.with_index { |e, i| [e, i] } it's easy to understand how it works.

Related

Ruby: reverse, mutating list

I'm trying to write a method, which reverses a list, but not using .reverse.
Here is my code:
def reverse(list)
a = list.length
while a >= 0
list << list[a]
a = a - 1
end
list
end
print reverse([1,2,3])
My expected result isn't [3,2,1] but [1, 2, 3, nil, 3, 2, 1]
Do you have any advice how to not repeat the original list once again, but only mutate it?
This mutates the original array as requested. nil is eliminated by being aware that the last element of the list is at list[list.length-1].
def reverse(list)
a = list.length-1
while a >= 0
list << list[a]
list.delete_at(a)
a = a - 1
end
list
end
p reverse([1, 2, 3]) #=> [3, 2, 1]
A more Ruby way could be as follows:
arr.sort_by!.with_index { |_,i| -i }
I understand the list is to be reversed in place (mutated). Below are two ways to do that.
If the list is not to be mutated, simply operate on a copy:
def non_mutating_reverse(list)
reverse(list.dup)
end
#1
Use parallel assignment (sometimes called multiple assignment).
def reverse(list)
(list.size/2).times { |i| list[i], list[-1-i] = list[-1-i], list[i] }
list
end
list = [1,2,3]
reverse list #=> [3, 2, 1]
list #=> [3, 2, 1]
Notice that when the size of the list is odd (as in this example), the middle element is not moved.
#2
def reverse(list)
list.replace(list.size.times.with_object([]) { |i,a| a.unshift(list[i]) })
end
list = [1,2,3]
reverse list #=> [3, 2, 1]
list #=> [3, 2, 1]

What is the right way to write ruby code?

I am solving the pyramid problem, in which an array is reduced to a single element over time by subtracting two consecutive numbers in each iteration.
input: [1, 5, 9, 2, 3, 5, 6]
iterations
[4, 4, -7, 1, 2, 1],
[0, -11, 8, 1, -1],
[-11, 19, -7, -2],
[30, -26, 5],
[-56, 31],
[87]
output: 87
What is the best way or ruby way to solve this problem? This can be done by inheriting array and making a new class, but I don't know how. Please help. I write this code to solve it:
a = [1,5,9,2,3,5,6]
class Array
def pyr
a = self.each_cons(2).to_a.map! { |e| e[1] - e[0] }
a
end
end
while a.length > 1
a = a.pyr
ans = a[0]
end
p ans
I see three ways to approach this.
Reopen the Array class
Sure, if in your particular ruby script/project this is an elementary functionality of an array, reopen the class. But if you are going to re-open a class, at least make sure the name is something meaningful. pyr? Why not write a full name, so no conflicts are possible, something like next_pyramid_iteration (I have never heard of this pyramid problem, so excuse me if I am way of base here).
Make a class inherit from Array
class Pyramid < Array
def next_iteration
self.each_const(2).map! { |e| e[1] - e[o] }
end
end
and then your calculation would become something like
pyramid = Pyramid.new([1,5,9,2,3,5,6])
while pyramid.length > 1
pyramid.next_iteration
end
pyramid[0]
Make a specific class to do the calculation
I am not quite sure what you are trying to achieve, but why not just make a specific class that knows how to calculate pyramids?
class PyramidCalculator
def initialize(arr)
#pyramid = arr
end
def calculate
while #pyramid.length > 1
do_next_iteration
end
#pyramid.first
end
def self.calculate(arr)
PyramidCalculator.new(arr).calculate
end
protected
def do_next_iteration
#pyramid = #pyramid.each_const(2).map! { |e| e[1] - e[o] }
end
end
because I added a convenience class-method, you can now calculate a result as follows:
PyramidCalculator.calculate([1,5,9,2,3,5,6])
My personal preference would be the last option :)
I would just do it as a two-liner.
a = a.each_cons(2).map{|e1, e2| e2 - e1} while a[1]
a.first # => 87
It's certainly easy enough to turn this into a simple function without hacking on the Array class:
def pyr(ary)
return ary[0] if ary.length < 2
pyr(ary.each_cons(2).map { |e| e[1] - e[0] })
end
p pyr [1,5,9,2,3,5,6] # => 87
Use return ary if you want the answer as a one-element array rather than a scalar.
If you prefer iteration to recursion or have a very large array:
def pyr(ary)
ary = ary.each_cons(2).map { |e| e[1] - e[0] } while ary.length > 1
ary
end
By encapsulating this as a function rather than doing it inline, you get the ability to do the operation on any number of arrays plus it's non-destructive on the original input array.
It's not necessary to compute the end value by successive computation of differences, which requires (n*(n-1)/2 subtractions and the same number of additions, where n is the size of the array a. Instead, we can compute that value by summing n terms of the form:
(-1)K+ibin_coeff(n-1,i)*a[i]
for i = 0..(n-1), where:
K equals 0 if the array has an even number of elements, else K equals 1; and
bin_coeff(n,i) is the binomial coefficient for choosing "n items i at a time" (n!/i!*(n-i)!).
I know what you're thinking: the calculation of each binomial coefficient will take some work. True, but that can be done in an efficient way (which I've not done below), by computing bin_coeff(n-1,i+1) from bin_coeff(n-1,i), etc. Of course, that's academic, as no one is likely to actually use the method I'm suggesting.
(I'm hoping nobody will demand a proof, but I'll try to oblige if a request is made.)
Code
class Fixnum
def factorial
(1..self).reduce(1) { |t,i| t*i }
end
def bin_coeff m
self.factorial/(m.factorial*(self-m).factorial)
end
end
def pyramid_sum(a)
n = a.size-1
sign = n.even? ? -1 : 1
(0..n).reduce(0) do |t,i|
sign = -sign
t + sign * n.bin_coeff(i) * a[i]
end
end
Examples
pyramid_sum [1, 5] #=> 4
pyramid_sum [1, 5, 9] # #=> 0
pyramid_sum [1, 5, 9, 2] #=> -11
pyramid_sum [1, 5, 9, 2, 3] #=> 30
pyramid_sum [1, 5, 9, 2, 3, 5] #=> -56
pyramid_sum [1, 5, 9, 2, 3, 5, 6] #=> 87

How can I remove duplicates in an array without using `uniq`?

The object of my coding exercise is to get rid of duplicates in an array without using the uniq method. Here is my code:
numbers = [1, 4, 2, 4, 3, 1, 5]
def my_uniq(array)
sorted = array.sort
count = 1
while count <= sorted.length
while true
sorted.delete_if {|i| i = i + count}
count += 1
end
end
return sorted
end
When I run this, I get an infinite loop. What is wrong?
Can I use delete the way that I am doing with count?
How will it execute? Will count continue until the end of the array before the method iterates to the next index?
I did this with each or map, and got the same results. What is the best way to do this using each, delete_if, map, or a while loop (with a second loop that compares against the first one)?
Here is a clearly written example.
numbers = [1, 4, 2, 4, 3, 1, 5]
def remove_duplicates(array)
response = Array.new
array.each do |number|
response << number unless response.include?(number)
end
return response
end
remove_duplicates(numbers)
As others pointed out, your inner loop is infinite. Here's a concise solution with no loops:
numbers.group_by{|n| n}.keys
You can sort it if you want, but this solution doesn't require it.
the problem is that the inner loop is an infinite loop:
while true
sorted.delete_if {|i| i = i + count}
count += 1
end #while
you can probably do what you are doing but it's not eliminating duplicates.
one way to do this would be:
numbers = [1, 4, 2, 4, 3, 1, 5]
target = []
numbers.each {|x| target << x unless target.include?(x) }
puts target.inspect
to add it to the array class:
class ::Array
def my_uniq
target = []
self.each {|x| target << x unless target.include?(x) }
target
end
end
now you can do:
numbers = [1, 4, 2, 4, 3, 1, 5]
numbers.my_uniq
You count use Set that acts like an array with does not allow duplicates:
require 'set'
numbers = [1, 4, 2, 4, 3, 1, 5]
Set.new(numbers).to_a
#=> [1, 4, 2, 3, 5]
Try using Array#& passing the array itself as parameter:
x = [1,2,3,3,3]
x & x #=> [1,2,3]
This is one of the answer. However, I do not know how much of performance issue it takes to return unique
def my_uniq(ints)
i = 0
uniq = []
while i < ints.length
ints.each do |integers|
if integers == i
uniq.push(integers)
end
i += 1
end
end
return uniq
end

Issue returning proper value

def sum_two(arry, sum)
p check_sums(sum, arry[0], arry[1..arry.length - 1])
end
def check_sums(target, first_num, remaining_nums)
result = []
return result if remaining_nums == []
remaining_nums.each do |n|
if first_num + n == target
result << [first_num, n]
end
end
check_sums(target, remaining_nums[0], remaining_nums[1..remaining_nums.length - 1])
end
my_arry = [2,4,6,1,3,5,7]
my_sum = 6
sum_two(my_arry, my_sum)
Above is my solution to a practice interview question. However, the output is always an empty array ([]). My question is seemingly rudimentary as I just need to return the final result array so I must be missing something obvious. Basically, I can't figure out why its printing an empty array because I feel quite confident the logic is sound.
UPDATE:
Below is an updated version of my solution in which I wrap the methods in a class and make result an instance variable so that I can maintain its state throughout the recursive call. Thanks to #BenE for mentioning that I was resetting the value every time the recursive call went through. That really cleared it up for me! Here's my new solution:
class SumTwo
#result = []
def self.sum_two(arry, sum)
p SumTwo.check_sums(sum, arry[0], arry[1..arry.length - 1])
end
def self.check_sums(target, first_num, remaining_nums)
return #result if remaining_nums == []
remaining_nums.each do |n|
if first_num + n == target
#result << [first_num, n]
end
end
check_sums(target, remaining_nums[0], remaining_nums[1..remaining_nums.length - 1])
#result
end
end
my_arry = [2,4,6,1,3,5,7]
my_sum = 6
SumTwo.sum_two(my_arry, my_sum)
The problem is that you don't return the result array that you loop on, you only return it when remaning_nums is empty, here is a working solution to you code:
def sum_two(arry, sum)
p check_sums(sum, arry[0], arry[1..arry.length - 1],[])
end
def check_sums(target, first_num, remaining_nums,result)
return result if remaining_nums == []
remaining_nums.each do |n|
if first_num + n == target
result << [first_num, n]
end
end
check_sums(target, remaining_nums[0], remaining_nums[1..remaining_nums.length - 1],result)
result
end
my_arry = [2,4,6,1,3,5,7]
my_sum = 6
sum_two(my_arry, my_sum)
If you want to return all pairs of numbers in an array whose sum is a given value, I think it's easiest to use the method Array#combination:
def sum_two(arry, sum)
arry.combination(2).select { |i,j| i+j == sum }
end
sum_two [2,4,6,1,3,5,7], 6
#=> [[2, 4], [1, 5]]
sum_two [*(1..24)], 12
#=> [[1, 11], [2, 10], [3, 9], [4, 8], [5, 7]]
sum_two [1,3, 6, 8, 2, 9, 3, 5, 7, 8, 16], 17
#=> [[1, 16], [8, 9], [9, 8]]
If you want to eliminate [8, 9] or [9, 8] in the last example, you could do this:
def sum_two(arry, sum)
arry.uniq.combination(2).select { |i,j| i+j == sum }
end
sum_two [1,3, 6, 8, 2, 9, 3, 5, 7, 8, 16], 17
#=> [[1, 16], [8, 9]]

Find all max of elements of an array [duplicate]

This question already has answers here:
Returning all maximum or minimum values that can be multiple
(3 answers)
Closed 8 years ago.
Suppose I have a array, namely arr: [1, 2, 3, 4, 8, 8], and I want to find all max elements in this array:
arr.allmax # => [8, 8]
Is there a built-in method combinations to solve this? I don't like to monkey patch as I am doing now:
class Array
def allmax
max = self.max
self.select { |e| e == max }
end
end
Monkey patch is not a good idea, I could just do:
some_array.select { |e| e == some_array.max }
and it will work as allmax. Thanks for all answers and comments for inspirations.
Here's a fun way to do it.
arr.sort!.slice arr.index(arr[-1]) || 0..-1
Sort the array, then find the leftmost index of the array which matches the rightmost index of the array, and take the subslice that matches that range (or the range 0..-1 if the array is empty).
This one is kind of fun in that it requires no intermediate arrays, though it does mutate the input to achieve the one-liner.
Here is one way :
2.1.0 :006 > arr = [1, 2, 3, 4, 8, 8]
=> [1, 2, 3, 4, 8, 8]
2.1.0 :007 > arr.group_by { |i| i }.max.last
=> [8, 8]
2.1.0 :008 >
Here is a method :-
def all_max(arr)
return [] if arr.empty?
arr.group_by { |i| i }.max.last
end
Another way:
def all_max(arr)
return [] if arr.empty?
mx = arr.max
[mx] * arr.count { |e| e == mx }
end
all_max([1, 2, 3, 4, 8, 8])
#=> [8, 8]
To construct the array in a single pass, you could do this:
arr.each_with_object([]) do |e,a|
if a.empty?
a << e
else
case e <=> a.first
when 0 then a << e
when 1 then a.replace([e])
end
end
end

Resources