Merge sort algorithm using recursion - ruby

I'm doing The Odin Project. The practice problem is: create a merge sort algorithm using recursion. The following is modified from someone's solution:
def merge_sort(arry)
# kick out the odds or kick out of the recursive splitting?
# I wasn't able to get the recombination to work within the same method.
return arry if arry.length == 1
arry1 = merge_sort(arry[0...arry.length/2])
arry2 = merge_sort(arry[arry.length/2..-1])
f_arry = []
index1 = 0 # placekeeper for iterating through arry1
index2 = 0 # placekeeper for iterating through arry2
# stops when f_arry is as long as combined subarrays
while f_arry.length < (arry1.length + arry2.length)
if index1 == arry1.length
# pushes remainder of arry2 to f_arry
# not sure why it needs to be flatten(ed)!
(f_arry << arry2[index2..-1]).flatten!
elsif index2 == arry2.length
(f_arry << arry1[index1..-1]).flatten!
elsif arry1[index1] <= arry2[index2]
f_arry << arry1[index1]
index1 += 1
else
f_arry << arry2 [index2]
index2 += 1
end
end
return f_arry
end
Is the first line return arry if arry.length == 1 kicking it out of the recursive splitting of the array(s) and then bypassing the recursive splitting part of the method to go back to the recombination section? It seems like it should then just keep resplitting it once it gets back to that section as it recurses through.
Why must it be flatten-ed?

The easiest way to understand the first line is to understand that the only contract that merge_sort is bound to is to "return a sorted array" - if the array has only one element (arry.length == 1) it is already sorted - so nothing needs to be done! Simply return the array itself.
In recursion, this is known as a "Stop condition". If you don't provide a stop condition - the recursion will never end (since it will always call itself - and never return)!
The result you need to flatten your result, is because you are pushing an array as an element in you resulting array:
arr = [1]
arr << [2, 3]
# => [1, [2, 3]]
If you try to flatten the resulting array only at the end of the iteration, and not as you are adding the elements, you'll have a problem, since its length will be skewed:
arr = [1, [2, 3]]
arr.length
# => 2
Although arr contains three numbers it has only two elements - and that will break your solution.
You want all the elements in your array to be numbers, not arrays. flatten! makes sure that all elements in your array are atoms, and if they are not, it adds the child array's elements to itself instead of the child array:
arr.flatten!
# => [1, 2, 3]
Another you option you might want to consider (and will be more efficient) is to use concat instead:
arr = [1]
arr.concat([2, 3])
# => [1, 2, 3]
This method add all the elements in the array passed as parameter to the array it is called on.

Related

Find the odd int - Ruby Nested Loop Error

I was doing this question on codewars: "Given an array, find the int that appears an odd number of times. There will always be only one integer that appears an odd number of times."
Code:
def find_it(seq)
int = []
for a in seq do
count = 0
for b in seq do
if a == b
count += 1
end
end
if count % 2.0 != 0
int << b
end
end
puts int.uniq[0].to_i
end
It was tested against a couple inputs, but the answers were wrong for these two arrays:
find_it([1,1,2,-2,5,2,4,4,-1,-2,5]) - returns 5 instead of -1
find_it([1,1,1,1,1,1,10,1,1,1,1]) - returns 1 instead of 10
What went wrong with my code?
if count % 2.0 != 0
int << b
end
The problem you have here is that your pushing b instead of a into the integer array, so what's happening is that instead of the value that you counted being pushed in, your pushing in the last value of b which is the last value element in the array regardless as long as the condition that the counter is an odd number, although b and counter have nothing to do with each other. so to fix it you replace b with a so that it pushes in the value you are testing comparing with the other elements in the second loop
fix:
if count % 2.0 != 0
int << a
end
a similar yet simpler code that does a similar job except in a shorter and more understandable way is:
def find_it(seq)
numberWithOddCount = 0
seq.each do |currentElement|
counter = 0
seq.each { |elementToCompare| counter += 1 if currentElement == elementToCompare}
numberWithOddCount = currentElement if counter % 2 != 0
end
numberWithOddCount
end
Just added a few tid-bits that you could also utilize to shorten and simplify code.
Happy Coding!
Note:
You could utilize built in ruby methods in creative ways to make the code do what you want in very few lines (or even one line) such as what #iGian did in the questions comments, but if your still new to ruby then its best to utilize those methods one by one when learning them otherwise you'll be confused. But if your willing to take the time now to learn them, I suggest you take his code and separate each method execution into its own line and output what each method had done to know what's doing what. and practice using each separately.
#aimen_alt is right about your mistake
but let's decompose your problem.
First, you need to calculate the appearances of each number.
Second, you need to find the one with the odd count of the appearances.
Accordingly to the problem, there is only one such number, so you can return it right away.
You can go your way and do it in O(N^2) complexity by scanning your sequence for each item in the sequence (so N items in the sequence multiply by the size of the sequence N = N*N). You can do it linearly* by constructing a Hash and than you'll be able to get the key with odd value:
def find_it(seq)
numbers = {}
seq.each do |item|
numbers[item] = numbers[item].to_i + 1
end
numbers.select{ |k,v| v.odd? }.first.first
end
to be more idiomatic you can use group_by to group the numbers themselves:
seq = [1, 2, 6, 1, 2]
seq.group_by{ |item| item }
#=> {1=>[1, 1], 2=>[2, 2], 6=>[6]}
You can see that each value is an Array, and you just need to get one with the odd amount of items:
seq = [1, 2, 6, 1, 2]
seq.group_by{ |item| item }.select{ |k, v| v.size.odd? }
#=> {6=>[6]}
And the last thing you would like to do is to get the value of the key:
seq.group_by{ |item| item }.select{ |k, v| v.size.odd? }.keys.first
So, the final solution would be
def find_it(seq)
seq.group_by{ |item| item }
.select{ |k, v| v.size.odd? }
.keys
.first
end
as #pascalbetz mentioned:
def find_it(seq)
seq.group_by{ |item| item }
.find{ |k, v| v.size.odd? }
.first
end
def find_it(seq)
seq.group_by{|x| x}.select{|k, v| (v.count % 2.0 !=0)}.first[0]
end
The above code will take a sequence in an array. Here we are grouping by elements:
For example:
[1,1,2,-2,5,2,4,4,-1,-2,5].group_by{|x| x}
# => {1=>[1, 1], 2=>[2, 2], -2=>[-2, -2], 5=>[5, 5], 4=>[4, 4], -1=>[-1]}
after getting the above results, we are finding the whose elements count not odd with the select condition.
ex:
[1,1,2,-2,5,2,4,4,-1,-2,5].group_by{|x| x}.select{|k, v| (v.count % 2.0 !=0)}
we will get the results as {-1=>[-1]}
we are taking the key as result element.
What about this one
def find_it(seq)
seq.reduce(:^)
end
^ -> this symbol is bitwise XOR.
reduce function is taking each value and doing whatever work assigned inside. In this case, it's taking each element and doing an XOR operation. the first element is doing XOR with zero and the next element doing XOR with the previous result and so on.
In this way, we found the odd element.
How XOR operation work
0 ^ 2 = 2
4 ^ 4 = 0
If you want to know more about XOR. kindly refer to this.
Thank you for all the detailed answers, I'm going over everyone's answers now. I'm new to Ruby, and I'm still in the process of learning the methods/rules of using them/Big O notation, so I much appreciated everyone's input. Codewar listed some top ranked solutions. This seems to be the fastest so far:
def find_it(seq)
seq.detect { |n| seq.count(n).odd? }
end

Using .min and max and pushing to an array

I'm working through Chris Pine's "learn to program" book and I am at the exercise in Chapter 10 where he asks you to alphabetize a list of words without using .sort. I used min/max (which he probably doesn't intend you to use either, but it's a start). This works, except when I use .min and push that value to the sorted array, the result comes out z to a, rather than a to z as I expected. When I use max (which I use in my code below just to make it work), it comes out a to z. Any idea why?
puts "Enter a list of words, separated by commas. Hit enter when done."
puts "This program will sort your words alphabetically."
word_list = gets.chomp.downcase
word_array = word_list.split(", ")
def sort_words (words)
sorted_array = [] if sorted_array.nil?
words = [] if words.nil?
until words.length == 0
first_word = words.max #method should be .min (?)
words.delete(first_word)
sorted_array.push(first_word)
sort_words(words)
end
puts sorted_array
end
sort_words(word_array)
Think of it like this.
unsorted = [1, 3, 2, 5, 4]
sorted = []
unsorted.max is 5. Delete that and push it onto sorted.
unsorted = [1, 3, 2, 4]
sorted = [5]
unsorted.max is 4. Delete that and push it onto sorted.
unsorted = [1, 3, 2]
sorted = [5, 4]
I think you can see where the mistake lies. push adds to the end of an array, so you want to build sorted from the smallest to the largest. Thus uses unsorted.max.
The problem with your code is you call sort_words(words) inside the loop after removing the max. This is a form of recursion. While you can write this sort routine using recursion, mixing the loop with recursion is causing your problem.
What is happening is the loop is removing the max element, then calling sort_words again with the same list less the max element. Then it does that again, and again, and again. You wind up with a stack of calls like...
call_stack sorted_array (local to each call)
sort_words([1,3,2,5,4]) [5]
sort_words([1,3,2,4]) [4]
sort_words([1,3,2]) [3]
sort_words([1,2]) [2]
sort_words([1]) [1]
sort_words([]) []
Since words is a reference it isn't copied in each call, every call to sort_words is working on the same word list. Each call shrinks words by one. When words is empty all the loops exit and print their results, but the stack returns from the bottom first! You get what looks like
1
2
3
4
5
But if you change puts sorted_array to puts "sorted array: #{sorted_array}" you'll see what's really happening.
sorted array: []
sorted array: ["1"]
sorted array: ["2"]
sorted array: ["3"]
sorted array: ["4"]
sorted array: ["5"]
Got it, thanks. Overdid it with the recursion in the loop. Deleted the call to the method within the loop. Also converted the sorted_array to a string for output.
puts "Enter a list of words, separated by commas. Hit enter when done."
puts "This program will sort your words alphabetically."
word_list = gets.chomp.downcase
word_array = word_list.split(", ")
def sort_words (words)
sorted_array = [] if sorted_array.nil?
words = [] if words.nil?
until words.length == 0
first_word = words.min
words.delete(first_word)
sorted_array.push(first_word)
end
sorted_words = sorted_array.join(", ")
puts sorted_words
end
sort_words(word_array)

Efficient way of removing similar arrays in an array of arrays

I am trying to analyze some documents and find similarities in them. After analysis, I have an array, the elements of which are arrays of data from documents considered similar. But sometimes I have two almost similar elements, and naturally I want to leave the biggest of them. For simplification:
data = [[1,2,3,4,5,6], [7,8,9,10], [1,2,3,5,6]...]
How do I efficiently process the data that I get:
data = [[1,2,3,4,5,6], [7,8,9,10]...]
I suppose I could intersect every array, and if the intersected array matches one of the original arrays - I ignore it. Here is a quick code I wrote:
data = [[1,2,3,4,5,6], [7,8,9,10], [1,2,3,5,6], [7,9,10]]
cleaned = []
data.each_index do |i|
similar = false
data.each_index do |j|
if i == j
next
elsif data[i]&data[j] == data[i]
similar = true
break
end
end
unless similar
cleaned << data[i]
end
end
puts cleaned.inspect
Is this an efficient way to go? Also, the current behaviour only allows to leave out arrays that are a few elements short, and I might want to merge similar arrays if they occur:
[[1,2,3,4,5], [1,3,4,5,6]] => [[1,2,3,4,5,6]]
You can delete any element in the list if it is fully contained in another element:
data.delete_if do |arr|
data.any? { |a2| !a2.equal?(arr) && arr - a2 == [] }
end
# => [[1, 2, 3, 4, 5, 6], [7, 8, 9, 10]]
This is a bit more efficient than your suggestion since once you decide that an element should be removed, you don't check against it in the next iterations.

Maximum and minimum value in an Array

I wrote a Ruby code to get max and min values from an array. The code prints the max value (8) correct but it's not printing the minimum value (2). Please let me know what went wrong in my code.
class MaxMinArray
def MaxMinMethod()
array = [4,2,8,3,5]
maxNo = array[0]
minNo = array[0]
arrayLength = array.length
for i in 1..arrayLength
if array[i].to_i > maxNo
maxNo = array[i]
end
if array[i].to_i < minNo
minNo = array[i]
end
end
puts "Maximum no. in the given array: " + maxNo.to_s
puts "Minimum no. in the given array: " + minNo.to_s
end
end
MaxiMinArrayObj = MaxMinArray.new
MaxiMinArrayObj.MaxMinMethod()
It is the combination of two things.
First, you iterated over for i in 1..arrayLength, which iterates past the last element in array. After the last element, array[i] is nil.
Second, you have the condition if array[i].to_i < minNo, which can be satisfied even if array[i] is not a number.
Because of that, the nil returned by array[i] after the last element satisfies the condition due to nil.to_i being 0, and that nil is assigned to minNo.
I realize you're trying to learn how to code, but, as you do so, it's also important to learn to take advantage of pre-existing solutions. Reinventing wheels will waste your time debugging code.
I'd write the code like:
def max_min(ary)
[ary.max, ary.min]
end
max_min([1,2,4]) # => [4, 1]
But, then again, Ruby already has a good minmax method:
[1,2,4].minmax # => [1, 4]
so use it and focus your energy on more interesting things.
If you have to see the values in the opposite order, use:
[1,2,4].minmax.reverse # => [4, 1]
A more verbose/old-school way of doing it is:
FIXNUM_MAX = (2 ** (0.size * 8 - 2) - 1)
FIXNUM_MIN = -(2 ** (0.size * 8 - 2))
def max_min(ary)
return [nil, nil] if ary.empty?
minval = FIXNUM_MAX
maxval = FIXNUM_MIN
ary.each do |i|
minval = i if i < minval
maxval = i if i > maxval
end
[maxval, minval]
end
max_min([1,2,4]) # => [4, 1]
[1,2,4].minmax.reverse # => [4, 1]
That simply loops over the array, checks each value to see if it's either smaller or larger than the last minimum or maximum value, and, if so, remembers it. Once the array is exhausted the values are returned. It's a lot more concise because using each removes a lot of the hassle of trying to walk the array using index values. We almost never use for in Ruby, especially to walk through an array.
(Technically Ruby can hold values well beyond 4611686018427387903 and -4611686018427387904, which are what FIXNUM_MAX and FIXNUM_MIN are, but those suffice for most things we want to do.)
It's not a good practice to print inside methods as long as you might want to use the results for something else.
Also Ruby comes with all sorts of magic methods to get the maximum and minimum of an array:
results = [5, 23, 43, 2, 3, 0].minmax
puts "Maximum no. in the given array: " + results[1]
puts "Minimum no. in the given array: " + results[0]
You should iterate from 1 to arrayLength - 1 (it's an index of the last element). You can use three dots for this:
for i in 1...arrayLength
If I were not allowed to used Ruby's minmax method, than I would do it probably like this:
array = [4,2,8,3,5]
min, max = nil, nil
array.each do |element|
min = element if min.nil? || element < min
max = element if max.nil? || max < element
end
puts [min, max]
# => [2, 8]
I used this expression for the min and max within ruby, it's a stretch but it works
class RubyMinMax
def self.min_function(array=[])
puts "my array is the following #{array}"
puts "the length of the array is #{array.length}"
it = 0
while array.length > 1
array.fetch(it).to_i > array.fetch(it-1).to_i ? array.delete_at(it) : array.delete_at(it-1)
it = array.length-1
end
print array[0]
end
def self.max_function(array=[])
puts "my array is the following #{array}"
puts "the length of the array is #{array.length}"
it = 0
while array.length > 1
array.fetch(it).to_i < array.fetch(it-1).to_i ? array.delete_at(it) : array.delete_at(it-1)
it = array.length-1
end
print array[0]
end
end
RubyMinMax.min_function([18, 19, 17])
RubyMinMax.max_function([18, 19, 17])
In the simplest way you can use max and min method of array.
:001 > [1,4,1,3,4,5].max
=> 5
:002 > [1,4,1,3,4,5].min
=> 1
And if your array may contain nil the first compact it the use min max
For example
:003 > [1,4,1,3,4,5,nil].compact
=> [1, 4, 1, 3, 4, 5]
:004 > [1,4,1,3,4,5].max
=> 5
:005 > [1,4,1,3,4,5].min
=> 1

The most idiomatic way to iterate through a Ruby array, exiting when an arbitrary condition met?

I want to iterate through an array, each element of which is an array of two integers (e.g. `[3,5]'); for each of these elements, I want to calculate the sum of the two integers, exiting the loop when any of these sums exceeds a certain arbitrary value. The source array is quite large, and I will likely find the desired value near the beginning, so looping through all of the unneeded elements is not a good option.
I have written three loops to do this, all of which produce the desired result. My question is: which is more idiomatic Ruby? Or--better yet--is there a better way? I try not to use non-local loop variables in, but break statements look kind of hackish to my (admittedly novice) eye.
# Loop A
pairs.each do |pair|
pair_sum = pair.inject(:+)
arr1 << pair_sum
break if pair_sum > arr2.max
end
#Loop B - (just A condensed)
pairs.each { |pair| arr1.last <= arr2.max ? arr1 << pair.inject(:+) : break }
#Loop C
i = 0
pair_sum = 0
begin
pair_sum = pairs[i].inject(:+)
arr1 << pair_sum
i += 1
end until pair_sum > arr2.max
A similar question was asked at escaping the .each { } iteration early in Ruby, but the responses were essentially that, while using .each or .each_with_index and exiting with break when the target index was reached would work, .take(num_elements).each is more idiomatic. In my situation, however, I don't know in advance how many elements I'll have to iterate through, presenting me with what appears to be a boundary case.
This is from a project Euler-type problem I've already solved, btw. Just wondering about the community-preferred syntax. Thanks in advance for your valuable time.
take and drop have a variant take_while and drop_while where instead of providing a fixed number of elements you provide a block. Ruby will accumulate values from the receiver (in the case of take_while) as long as the block returns true. Your code could be rewritten as
array.take_while {|pair| pair.sum < foo}.map(&:sum)
This does mean that you calculate the sum of some of these pairs twice.
In Ruby 2.0 there's Enumerable#lazy which returns a lazy enumerator:
sums = pairs.lazy.map { |a, b| a + b }.take_while { |pair_sum| pair_sum < some_max_value }.force
This avoids calculating the sums twice.
[[1, 2], [3, 4], [5, 6]].find{|x, y| x + y > 6}
# => [3, 4]
[[1, 2], [3, 4], [5, 6]].find{|x, y| x + y > 6}.inject(:+)
#=> 7

Resources