How are these conditions able to find a peak number? - ruby

Need insight on this code. I have the solution but do not understand it.
The goal here was to create a method that:
accepts an array of numbers as an arg
should return an array containing all peaks of array
an element is considered a peak if it is greater than its left & right neighbor
first/ last element is considered peak if it is greater than its one neighbor
def peak_finder(arr)
peaks = []
arr.each_index do |i|
left = arr[i - 1] # left most num
mid = arr[i] # mid num
right = arr[i + 1]# right most num
if i == 0 && mid > right
peaks << mid
elsif i == arr.length - 1 && mid > left
peaks << mid
elsif !left.nil? && !right.nil? && mid > right && mid > left #do not understand this condition
peaks << mid
end
end
peaks
end
p peak_finder([1, 3, 5, 4])
# => [5]

!left.nil? is always true so it may be removed. !right.nil?, because of where in the if statement it is executed, is also always true, so it may be removed. One can only speculate as to what was in the mind of the coder when those conditions were stipulated1.
Defining left = arr[i-1] when i = 0 and right = arr[i+1] when i = arr.size-1 do no harm (as they are not referenced in the following if statement), but that construct is misleading to the reader (and is ugly), so it should be avoided.
One problem with the code is that it fails if the array contains zero or one element.
The code could be corrected and improved as follows.
def peak_finder(arr)
return arr if arr.size < 2
arr.each_index.filter_map do |i|
x = arr[i]
x if
case i
when 0
x > arr[i+1]
when arr.size-1
x > arr[i-1]
else
x > arr[i-1] && x > arr[i+1]
end
end
end
peaks([1, 3, 2, 5, 4, 6])
#=> [3, 5, 6]
peaks([1])
#=> [1]
peaks([])
#=> []
See Enumerable#filter_map, which made its debut in Ruby v2.7.
Another way is to make use of the method Enumerable#each_cons.
def peaks(arr)
[-Float::INFINITY, *arr, -Float::INFINITY].
each_cons(3).filter_map { |n1,n2,n3| n2 if (n2 > n1 && n2 > n3) }
end
peaks([1, 3, 2, 5, 4, 6])
#=> [3, 5, 6]
peaks([1])
#=> [1]
peaks([])
#=> []
1. Could the coder have forgotten that i = 0 and i = arr.size -1 have been dealt with earlier in the if statement and was thinking that arr[i+1] equals nil when i = arr.size-1 and that arr[i-1] equals nil when i = 0 (which is of course incorrect, as arr[-1] equals the last last element of the array)?

Related

Trying to run each_index on an array but getting `each': can't iterate from NilClass (TypeError)

I am currently attempting the codewars 6th kyu kata "Equal Sides Of An Array" where it asks:
'Input:
An integer array of length 0 < arr < 1000. The numbers in the array can be any integer positive or negative.
Output:
The lowest index N where the side to the left of N is equal to the side to the right of N. If you do not find an index that fits these rules, then you will return -1.
Note:
If you are given an array with multiple answers, return the lowest correct index.'
I know my code doesn't work completely but trying to run the code to see where it is going wrong to try and get a further understanding of how to use error codes to guide my thought process.
My code is as follows
def find_even_index(arr)
even_index = -1
arr.with_each_index do |n, index|
even_index = index if (arr[0]..arr[index - 1]).sum == (arr[index + 1]..arr[-1]).sum
end
even_index
end
When I input this into the kata to test, I get the following error:
'./lib/solution.rb:4:in `each': can't iterate from NilClass (TypeError)'
This error leads me to believe that there is an issue with the use of each_with_index on arr but the parameter is an array. What is going wrong here?
There is not with_each_index in Ruby, only each_with_index
Your problem is in arr[index + 1]..arr[-1] on last index
arr[index + 1] in that case is nil so you tries nil..arr[-1]
That's why you have can't iterate from NilClass (TypeError) error
You don't need to use ranges in your solution, just use subarrays, array also have sum method
So you can use each_index method here like this:
def find_even_index(ary)
ary.each_index do |idx|
return idx if ary[...idx].sum == ary[idx + 1..].sum
end
-1
end
find_even_index([1, 2, 3, 4, 3, 2, 1])
# => 3
find_even_index([1, 2, 2, 4, 3, 2, 1])
# => -1
This solution with beginless (available in Ruby >= 2.7) and endless (available in Ruby >= 2.6) ranges
In earlier versions you can use ary[0...idx] and ary[idx + 1..-1]
Here's a solution that does not repeatedly sum each side of a partitioned array.
Note that the Kata defines the sum of the elements before the first and after the last to equal zero.
def doit(arr)
left = 0
right = arr.sum - arr[0]
(0..arr.size-1).each do |i|
return i if left == right
return -1 if i == arr.size - 1
left += arr[i]
right -= arr[i+1]
end
end
doit [1, 2, 3, 4, 3, 2, 1]
#=> 3
doit [1, 2, 3, -5]
#=> 0
doit [1, -3, 2, 18]
#=> 3
doit [99]
#=> 0
doit [0,0,0,0,0]
#=> 0
doit [1, 2, 2, 4, 3, 2, 1]
#=> -1
I can best explain the calculations by executing the method after having salted it with puts statements.
def doit(arr)
puts "arr.size == #{arr.size}, arr.sum = #{arr.sum}"
left = 0
right = arr.sum - arr[0]
puts "initially left = 0, right = #{right}"
(0..arr.size-1).each do |i|
puts "i = #{i}"
puts left == right ?
"returning #{i} because left == right" : "left != right"
return i if left == right
puts i == arr.size - 1 ? " i == arr.size - 1 so returning #{-1}" :
" i < arr.size - 1, so continuing"
return -1 if i == arr.size - 1
left += arr[i]
puts " left = left + arr[#{i}] = #{left} for i = #{i+1}"
right -= arr[i+1]
puts " right = right - arr[#{i+1}] = #{right} for i = #{i+1}"
end
end
doit [1, 2, 3, 4, 3, 2, 1]
#=> 3
arr.size == 7, arr.sum = 16
initially left = 0, right = 15
i = 0
left != right
i < arr.size - 1, so continuing
left = left + arr[0] = 1 for i = 1
right = right - arr[1] = 13 for i = 1
i = 1
left != right
i < arr.size - 1, so continuing
left = left + arr[1] = 3 for i = 2
right = right - arr[2] = 10 for i = 2
i = 2
left != right
i < arr.size - 1, so continuing
left = left + arr[2] = 6 for i = 3
right = right - arr[3] = 6 for i = 3
i = 3
returning 3 because left == right

Ruby array five_sort algorithm

I'm trying to solve a problem called five_sort that accepts an array of integers as the argument and places all the fives at the end of the array and leaves all of the other numbers unsorted. For example, [1,2,5,3,2,5,5,7] would be sorted as [1,2,3,2,7,5,5,5].The rules for the problem state that only a while loops can be used and no other methods can be called on the array except [] and []=. Here is my current code:
def five_sort(array)
sorted = false
while sorted == false
idx = 0
while idx < array.length
if array[idx] == 5
array[idx], array[idx + 1] = array[idx + 1], array[idx]
end
idx += 1
end
sorted = true
end
array
end
When running it, it is just in a continuous loop but I can't find out how to fix it. I know that if I just run the second while loop without the while sorted loop, the array would only run once and the fives would only switch places once and the loop would be over. But I don't know how to run the second while loop and stop it once all the fives are at the end.
Can anyone help me figure this one out?
Just a simple O(n) time and O(1) space solution, using a write-index and a read-index.
w = r = 0
while array[w]
r += 1 while array[r] == 5
array[w] = array[r] || 5
w += 1
r += 1
end
While a couple of people have posted alternative approaches, which are all good, I wanted to post something based on your own code to reassure you that you had got pretty close to a solution.
I've added comments to explain the changes I've made:
def five_sort(array)
sorted = false
while sorted == false
idx = 0
# use did_swap to keep track of if we've needed to swap any numbers
did_swap = false
# check if next element is nil as alternative to using Array#length
while array[idx + 1] != nil
# it's only really a swap if the other entry is not also a 5
if array[idx] == 5 and array[idx + 1] != 5
array[idx], array[idx + 1] = array[idx + 1], array[idx]
did_swap = true
end
idx += 1
end
# if we've been through the array without needing to make any swaps
# then the list is sorted
if !did_swap
sorted = true
end
end
array
end
Your array is becoming longer at each loop:
array = [1,2]
array[1], array[2] = array[2], array[1]
puts array.length
Outputs 3.
What you need is to not swap if idx = array.length - 1
if (array[idx] == 5)
array[idx], array[idx+1] = array[idx+1], array[idx] if idx != array.length - 1
end
def five_sort(arr)
i = 0
cnt = 0
while arr[i]
if arr[i] == 5 && arr[i+1]
arr[i..i] = []
cnt += 1
else
i += 1
end
end
cnt.times { arr[-1,2] = [arr[-1],5] }
arr
end
arr = [1,5,3,5,6]
five_sort arr
#=> [1, 3, 6, 5, 5]
arr
#=> [1, 3, 6, 5, 5] # confirms arr is mutated
five_sort [5,5,5,3,6]
#=> [3, 6, 5, 5, 5]
five_sort [5,5,5,5,5]
#=> [5, 5, 5, 5, 5]
five_sort [1,2,3,4,6]
#=> [1, 2, 3, 4, 6]
five_sort []
#=> []
Notes:
As required by the spec, the only methods invoked on arr are [] and []= and no other arrays are created.
if i indexes the last element of the array, arr[i+1] equals nil.
arr[i..i] = [] removes the elementarr[i] from arr.
arr[-1,2] = [arr[-1],5] appends a 5 to arr.

Can someone explain why this Ruby next if works in an until loop?

def sorting(arr)
sorted = false
until sorted
sorted = true
arr.each_index do |i|
next if i == arr.length - 1
if arr[i] > arr[i + 1]
arr[i], arr[i+1] = arr[i+1], arr[i]
sorted = false
end
end
end
arr
end
p sorting([7, 4, 5, 2, 1, 3]) => [1, 2, 3, 4, 5, 7]
My question is: Why would the above Ruby code work?
First: When we called sorted = true right inside the until loop, that fulfills the condition of the sorted and it should end the loop.
Second: Can someone explain why the process of next if inside the iteration method won't continue to iterate the whole array,
when it should have ended after the first iteration, and then call on sorted = true.
I was asked to explain, and I was wondering if anyone can provide a better explanation.
Thank you Ruby Masters!
First, until is a top-testing loop. The entire block must loop before the until condition is tested again; it is not tested after every statement within the loop. You have to reach end or next before the until will evaluate again.
Second, <statement> if <condition> is a ruby shorthand for if <condition> <statement> end. It allows you to write a simple conditional on one line without sacrificing readability. The next will only execute at the last iteration of the arr.each_index loop, going up the stack to the until condition, by which time sorted will have been set to false.
To see how this works, try running the following modification:
#!/usr/bin/ruby
def sorting(arr)
puts "starting with #{arr}"
sorted = false
until sorted
sorted = true
arr.each_index do |i|
if i == arr.length - 1
puts "'next' when i == #{i}, arr = #{arr}"
next
end
if arr[i] > arr[i + 1]
puts "swapping at #{i}: #{arr[i]} <=> #{arr[i+1]}"
arr[i], arr[i+1] = arr[i+1], arr[i]
sorted = false
end
end
end
arr
end
p sorting([7,4,5,1,2,3])
The output of this program is:
starting with [7, 4, 5, 1, 2, 3]
swapping at 0: 7 <=> 4
swapping at 1: 7 <=> 5
swapping at 2: 7 <=> 1
swapping at 3: 7 <=> 2
swapping at 4: 7 <=> 3
'next' when i == 5, arr = [4, 5, 1, 2, 3, 7]
swapping at 1: 5 <=> 1
swapping at 2: 5 <=> 2
swapping at 3: 5 <=> 3
'next' when i == 5, arr = [4, 1, 2, 3, 5, 7]
swapping at 0: 4 <=> 1
swapping at 1: 4 <=> 2
swapping at 2: 4 <=> 3
'next' when i == 5, arr = [1, 2, 3, 4, 5, 7]
'next' when i == 5, arr = [1, 2, 3, 4, 5, 7]
[1, 2, 3, 4, 5, 7]
This is of course academic: the proper way to sort the array would be with Array#sort.
Note: the next is only necessary at all because on the last iteration of the each_index loop, i + 1 will be out of range for the array, which would cause the next line which accesses arr[i + 1] to fail (it will evaluate to nil, and you can't compare an Integer to nil). Alternate ways of doing this would be to modify the conditional around the swap testing the index, or change the outside of the loop enumerating the array to be a smaller range.
Modifying the conditional, eliminating the next, which works because logical-and conditionals are evaluated left to right and the interpreter stops as soon as the first one is false:
def sorting(arr)
sorted = false
until sorted
sorted = true
arr.each_index do |i|
if (i < arr.size - 1) && (arr[i] > arr[i + 1])
arr[i], arr[i+1] = arr[i+1], arr[i]
sorted = false
end
end
end
arr
end
p sorting([7,4,5,1,2,3])
Changing the range of the loop, which is better in that the loop executes fewer times:
def sorting(arr)
sorted = false
until sorted
sorted = true
(0..arr.size - 2).each do |i|
if (arr[i] > arr[i + 1])
arr[i], arr[i+1] = arr[i+1], arr[i]
sorted = false
end
end
end
arr
end
p sorting([7,4,5,1,2,3])
next is akin to goto in other languages and should be avoided if possible.

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 }

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}

Resources