Ruby array five_sort algorithm - ruby

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.

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

Algorithm for array with `while` or `until` loop

I have:
array = [1, 4, -1, 3, 2]
I want a new array that follows the following logic:
First element is located at index 0, so it is 1.
Second element is located at index 1 (because value for index 0 was 1).
Third element is located at index 4, so it is 2.
And so on until the loop meets value -1, which is the last value, and it should brake.
The new array should be:
[1, 4, 2, -1]
I have:
def task(a)
array = []
a.each_with_index do |v, i|
result = a[i]
until a[i] == -1
array << a[result]
end
end
puts result
end
As others say, you need to change the index in your loop. Also, if you want -1 in the result, you should exit at bottom. And with_index will give you indices in order, which is not what you want here. This will do what you want:
def task(a)
i = 0
array = []
begin
i = a[i]
array << i
end until i == -1
array
end
p task([1, 4, -1, 3, 2])
# => [1, 4, 2, -1]
until a[i] == -1
array << a[result]
end
This code is looping eternally - there is nothing to change i .
As discussed in the comments, you are looping through the array which is not what you require.
You could use a recursive method to handle jumping from one element to another based on previous value. Consider the following:
arr = [1, 4, -1, 3, 2]
def task(arr, n=0, result=[])
if arr[n] == -1
return result + [-1]
end
r = arr[n]
task(arr, r, result + [r])
end
puts task(arr)
input_array = [1, 4, -1, 3, 2]
last_valid_index = input_array.find_index { |entry| entry < 0 }
first_element = input_array.first
last_element = input_array[last_valid_index]
middle_elements = (1..last_valid_index).map { |i| input_array[input_array[i-1]]}
output_array = [first_element] + middle_elements + [last_element]
p output_array
# => [1, 4, 2, -1]
you could to most of it on one line like so, but I think the more verbose version is more self documenting.
input_array = [1, 4, -1, 3, 2]
last_valid_index = input_array.find_index { |entry| entry < 0 }
output_array = [input_array.first] + (1..last_valid_index).map { |i| input_array[input_array[i-1]]} + [input_array[last_valid_index]]
p output_array
# => [1, 4, 2, -1]
I'd suggest this option, just to avoid infinite loops or index out range:
i, ary = 0, [array[0]]
array.size.times do
break if array[i] == -1 or array[i] > array.size - 1
i = array[i]
ary << array[i]
end
ary #=> [1, 4, 2, -1]
An infinite loop happens for example when array = [1, 4, -1, 0, 3].
Index out of range can happen when array = [1, 4, 6, 3, 2]

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.

Given an array, replace each prime number with the next prime number

So
#[11,13,17,23] => [13,17,19,29]
if the number isn't prime, then the function is just returning the number
#[11,8,2,24] => [13,8,3,24]
I'm having so much trouble with the very last number (29), for some reason, it's going into an infinite loop. I feel like there is such a simple solution, but it's just escaping me..
def next_prime(arr)
new_arr = []
arr.each do |num|
#puts num
if prime?(num)
p = false
i = num + 2
while !p
p = prime?(i)
i += 2
end
new_arr << i
else
new_arr << num
end
end
new_arr
end
EDIT: here is the prime function
def prime?(num)
if num ==1
return false
elsif num < 3
return true
elsif num % 2 == 0 || num % 3 == 0
return false
end
i = 5
while i*i <= num
if num % i == 0 || num % (i + 2) == 0
return false
end
i += 6
end
return true
end
The first array works decently for me, even the 29, except for the fact that everything is 2 higher than it should be, because you add 2 after you check if you have a prime which can be fixed by a slight alteration to the code:
if prime?(num)
p = false
i = num
while !p
i += 2
p = prime?(i)
end
new_arr << i
The only infinite loop I encounter is when you hit 2 in the second array, because to check for primes, you just keep incrementing by 2, and so you end up checking every multiple of 2 to see if it's prime. Naturally you never find a prime multiple of 2. To fix this, instead of incrementing by 2 before checking for the next prime, if you increment by 1 would work, you just need to check twice as many numbers:
if prime?(num)
p = false
i = num
while !p
i += 1
p = prime?(i)
end
new_arr << i
your last problem is that your prime? function returns false for 3, which can be fixed by changing:
elsif num <= 3
return true
and now your 2 samples yield:
[11, 13, 17, 23] => [13, 17, 19, 29]
[11, 8, 2, 24] => [13, 8, 3, 24]
Similar to #Cary Swoveland answer, but more idiomatic Ruby IMHO.
require 'prime'
def next_primes(ary)
ary.map { |candidate| candidate.prime? ? next_prime(candidate) : candidate }
end
def next_prime(previous_prime)
all_primes = Prime.each
all_primes.find { |prime| prime > previous_prime }
end
next_primes([11,8,2,24]) # => [13, 8, 3, 24]
next_primes([11,13,17,23]) # => [13, 17, 19, 29]
I've assumed the array arr is sorted. If it isn't, save the original indices of the sorted values, which are then used to reorder the elements of the array of values containing prime and non-prime numbers. For example, if
arr = [57, 13, 28, 6]
then
indices = arr.each_with_index.sort.map(&:last)
#=> [3, 1, 2 0]
The steps of the the main method are as follows.
Step 1: create an empty array a that will be returned by the method.
Step 2: create an enumerator, enum, that generates an infinite sequence of prime numbers (2, 3, 5, 7,..). Generate the first prime m = enum.next #=> 2.
Step 3: create an enumerator, earr, that generate the elements of the given array arr.
Step 4: consider the next element of the array, x = earr.next, which is initially the first element of the array. When there are no more elements of the array, break from the loop and return the array a.
Step 5: if x is not prime save it to an array a (a << x) and repeat step 4; else go to Step 6.
Step 6: (x is prime) if x < m, save m to the array a and go to
Step 4; else (i.e., m <= x), obtain the next prime (m = enum.next) and repeat this step.
require 'prime"
def next_primes(arr)
enum = Prime.each
m = enum.next
earr = arr.to_enum
x = earr.next
a = []
loop do
if x.prime?
if x < m
a << m
x = earr.next
else
m = enum.next
end
else
a << x
x = earr.next
end
end
a
end
next_primes([2, 6, 7, 23, 100])
#=> [3, 6, 11, 29, 100]
next_primes([2, 6, 7, 7, 100])
#=> [3, 6, 11, 11, 100]
Solution to find the next prime number if it is a prime number if not return the same number:
def is_prime(num)
if num<2
return false
end
(2...num).each do |i|
if num%i == 0
return false
end
end
return true
end
def next_prime(arr)
new_arr = []
arr.each do |num|
if is_prime(num)
p = false
i = num
while !p
i += 1
p = is_prime(i)
end
new_arr<<i
else
new_arr<<num
end
end
return new_arr
end
print next_prime([2,3,4,5])
puts
print next_prime([2, 6, 7, 23, 100]) #[3, 6, 11, 29, 100]

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