Ruby's dynamic Inject method not working - ruby

In irb to checkout the working of inject method I wrote down simple code to print the count of numbers divisible by 3 but somehow that's malfunctioning:
[1,2,3,4].inject(0) do |count,value|
if value % 3 == 0
count = count + 1
end
end
It is something minor but I am not getting a hold on it.

With inject, you need to return the memo-element on each iteration:
[1, 2, 3, 4].inject(0) do |count, value|
if value % 3 == 0
count = count + 1
end
count
end
#=> 1
Or, if you prefer the one-line version:
[1, 2, 3, 4].inject(0) { |c, v| c += 1 if v % 3 == 0; c }
#=> 1
Worth noting. In some cases, you can substitute inject for each_with_object, and avoid having to return the memo object, but since the return value of the latter is the original object passed in, it only works with objects passed by reference (i.e. not Fixnums.)
[1, 2, 3, 4].each_with_object(0) { |v, c| c += 1 if v % 3 == 0 }
#=> 0

So it turns out it was infact something minor. I didn't return the count at end of each loop so count was nil after first iteration since I didn't return it. Blunder!
[1,2,3,4].inject(0) do |count,value|
if value % 3 == 0
count = count + 1
end
count
end

Related

Function with arrays as a parameter

How can I write a Ruby function that can calculate the average of an array? If the array doesn't have any elements, the result should be 0. I should use a loop for the implementation. I started like this, but I'm not quite sure how to use the loop.
a = [1, 2, 3, 4, 5, 6]
def average(a)
sum = 0.0
result = 0.0
if array.length > 0 then
array.each do |item|
sum += item
end
result = sum / array.length
end
return result.to_f
end
def average(arr, precision=0)
return 0 if arr.empty?
arr.sum.fdiv(arr.size).round(precision)
end
arr = [1,2,3,7]
average(arr) #=> 3
average(arr,2) #=> 3.25
Rather than using Integer#fdiv you could write
(arr.sum.to_f/arr.size).round(precision)
I suppose we can also write it simply as below
a = [1, 2, 3, 4, 5, 6]
def average(arr=[])
sum = 0.0
i=0
while(i < arr.length) do
sum += arr[i].to_f
i += 1
end
return ((i==0) ? 0 : (sum / i))
end
We can loop and calculate sum this way. Afterwards for average we took value of i which will be retained value after loop and make conditional operator for returning result.
Its simple solution, I have not tested it though so can have mistakes. You can try on your side. Hope This helps !!
You can write this:
def average(values)
total = 0.0
values.each do |i|
total += i
end
return total / values.length()
end
If you want to use a loop, you can do it this way:
def average(values=[])
total = 0.0
for i in values
total += i
end
return total / values.length()
end
If a non-empty array is passed, it will return the average of the values. If an empty array is passed, it will return 0.0.
You can test it like this:
puts average([1, 2, 3, 4, 5]) #=> "3"
puts average([]) #=> "0"
def average(a)
if a.empty?
0
else
sum = a.inject(0.0){|x, sum| sum += x}
sum / a.size.to_f
end
end

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.

Collatz Chain Algorithm RUBY

I am trying to populate an array according to the Collatz sequence. The constraints for the sequence are as follows:
positive integers:
n → n/2 (n is even)
n → 3n + 1 (n is odd)
Example Output
3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1
Ideally, I wanted to construct a recursive call that would populate the array according to the constraints of the sequence. However, I believe my logic for the recursive call is extremely flawed. The intended behavior is to iterate over the nested array, manipulating only the last element of each sub array until the element reaches 1. I am trying to build my understanding of recursion and would appreciate any suggestions on how to fix this problem.
def collatzSum(maxNumber)
sequenceHash = Hash.new(0)
i = maxNumber
until i == 0 do
if i.even?
sequenceHash[i] = [(i), (i / 2)]
elsif i.odd? && i != 1
sequenceHash[i] = [(i), (3 * i + 1)]
elsif i == 1
sequenceHash[i] = [i]
end
i -= 1
end
p sequenceHash
helper_method recursion. Method should take in hash values and iterate according to if statements.
=begin
desired output
hash = {5=>[5,16, 8, 4, 2,1],
4=>[4,2,1],
3=>[3,10,5,16,8,4,2,1],
2=>[2,1],
1=>[1]}
=end
Code:
collatzChain = lambda do |k|
j = 0
k = j[-1]
until k == 1 do
if k.even?
sequenceHash[k] << (k / 2)
elsif k.odd?
sequenceHash[k] << (3 * k + 1)
end
end
j += 1
end
collatzChain.call(sequenceHash.values)
sequenceHash
end
collatzSum(5)
So you mention that you wanted a recursive algorithm, your current approach looks iterative to me. To be recursive, you need to call the method you're in with values closer and closer to a base condition and then, once you hit the base condition, you return back out, up the call chain building up your return values. So, for the Collatz sequence a recursive approach would look like:
def build_collatz_chain(max_number)
return_value = [max_number]
# our base condition is when the number passed in is equal to 1, so
# when we get 1 as the max_number, we'll return an array looking like
# [1]
return return_value if max_number == 1
if max_number.even?
# here, with an even max_number, we'll recurse and call our method
# again, passing in the new max_number, which is the current
# max_number / 2.
return_value + build_collatz_chain(max_number / 2)
else
# same as above, but we're odd, so we'll recurse with 3 * max_number + 1
return_value + build_collatz_chain(3 * max_number + 1)
end
end
and now when we call this with a value of 5, what will end up happening is something like:
call build_collatz_chain(5)
call build_collatz_chain(16)
call build_collatz_chain(8)
call build_collatz_chain(4)
call build_collatz_chain(2)
call build_collatz_chain(1)
We have hit the base condition! return with [1]
return from 2 with [2, 1]
return from 4 with [4, 2, 1]
return from 8 with [8, 4, 2, 1]
return from 16 with [16, 8, 4, 2, 1]
return from 5 with [5, 16, 8, 4, 2, 1]
So, now if you want a hash of all numbers up to the passed in max_number with their Collatz chains as values you can use a helper to call this for each value, up to max (this helper is iterative, but could be made recursive...exercise for the viewer if you want it recursive):
def collatz_sum(max_number)
{ }.tap do |sequence_hash|
max_number.downto(1) do |i|
sequence_hash[i] = build_collatz_chain(i)
end
end
end
and then when you call collatz_sum(5) you get back:
{5=>[5, 16, 8, 4, 2, 1], 4=>[4, 2, 1], 3=>[3, 10, 5, 16, 8, 4, 2, 1], 2=>[2, 1], 1=>[1]}
The reason your approach is iterative is in the collatzChain lambda, you are setting a value (j) and then incrementing it and just looping through until k is equal to 1. It's also an infinite loop because you initially set k as:
j = 0
k = j[-1]
and so k == 0, and then you iterate until k == 1 and then you never update what the value of k is again.
It's not clear that a recursive operation is necessary here since this seems to be a straightforward mapping between a value x and f(x). By switching to a simple array output you can achieve what you want with:
def collatz_sum(max)
(2..max).map do |i|
[
i,
if (i.even?)
i / 2
else
3 * i + 1
end
]
end.reverse + [ [ 1 ] ]
end

Get max consecutive occurrences of value in array

Is there a more elegant way to achieve this below:
Input:
array = [1, 1, 1, 0, 0, 1, 1, 1, 1, 0]
Output:
4
My algo:
streak = 0
max_streak = 0
arr.each do |n|
if n == 1
streak += 1
else
max_streak = streak if streak > max_streak
streak = 0
end
end
puts max_streak
Similar to w0lf's answer, but skipping elements by returning nil from chunk:
array.chunk { |x| x == 1 || nil }.map { |_, x| x.size }.max
Edit: Another way to do this (that is less generic than Stefan's answer since you would have to flatten and split again if there was another number other than 0 and 1 in there, but easier to use in this case):
array.split(0).max.count
You can use:
array.chunk { |n| n }.select { |a| a.include?(1) }.map { |y, ys| ys.count}.max
ref: Count sequential occurrences of element in ruby array
You can use Enumerable#chunk:
p array.chunk{|x| x}.select{|x, xs| x == 1}.map{|x, xs| xs.size }.max
This is more concise, but if performance was important, I'd use your approach.
Edit: If you're in Ruby 2.2.2, you can also use the new Enumerable#slice_when method (assuming your input array consists of only 0s and 1s):
array.slice_when{|x,y| x < y }.map{|slice| slice.count 1 }.max
How about
array = [1, 1, 1, 0, 0, 1, 1, 1, 1, 0]
array.split(0).group_by(&:size).max.first #=> 4
The only bad thing - split(0)
Note: This only works with rails's ActiveSupport(extends Array with #split)
For ruby-only implementation
array.join.split("0").group_by(&:size).max.first #=> 4

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