Function with arrays as a parameter - ruby

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

Related

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.

Min, Max, Average, and Median of All Possible Sums (Ruby)

I've been using a piece of Ruby code that I found here.
Here's the code:
a = [1, 4, 7, 13]
def add(ary, idx, sum)
(idx...ary.length).each do |i|
add(ary, i+1, sum + ary[i])
end
puts sum
end
add(a, 0, 0)
Thing is, I don't need it to spit out the results of adding all the sums. I need the min, max, median, and average of the sums.
How do I modify this code in order to get them? I'm a total beginner at Ruby. I've been using this code, and then transferring the results to Excel to get the values I want. But it feels like my methods could be more efficient.
Thank you for your help.
EDIT: Expected results - Currently the code spits this out on my screen:
25
12
18
5
21
8
14
1
24
11
17
4
20
7
13
0
I want it to spit out the min, average, median, and max instead:
0
12.5
12.5
25
a = [1, 4, 7, 13]
def all_sums(array)
combination_lengths = (0..array.length)
all_combinations = combination_lengths.flat_map do |c|
array.combination(c).to_a
end
all_combinations.map(&:sum)
end
def print_min_max_avg_med(array)
puts array.min
puts array.max
puts array.sum.to_f / array.length
sorted_arr = array.sort
puts sorted_arr[(array.length - 1) / 2] + sorted_arr[array.length / 2] / 2.0
end
print_min_max_avg_med(all_sums(a))
Ok, instead of outputting the values we can store them in an arrary and use that array for the values you need.
(edited after chewing out by Stefan Pochmann)
a = [1, 4, 7, 13]
def add(ary, idx, sum, results = nil)
unless results
results = []
first_run = true
end
(idx...ary.length).each do |i|
add(ary, i+1, sum + ary[i], results)
end
results << sum
if first_run
puts results.min
puts results.inject(&:+).to_f / results.size
puts (results.sort[((results.size - 1) / 2)] + results.sort[(results.size / 2)]) / 2.0
puts results.max
end
end
add(a, 0, 0)
Alright, after seeing the examples from Pochmann and Bronca, I put this together after googling for a better way to get the median.
a = [1, 4, 7, 13]
def all_sums(array)
combination_lengths = (0..array.length)
all_combinations = combination_lengths.flat_map do |c|
array.combination(c).to_a
end
all_combinations.map(&:sum)
end
def median(array)
sorted = array.sort
len = sorted.length
(sorted[(len - 1) / 2] + sorted[len / 2]) / 2.0
end
def print_min_max_avg_med(array)
puts array.min
puts array.empty? ? 0 : array.sum.to_f / array.length
puts median(array)
puts array.max
end
print_min_max_avg_med(all_sums(a))
I've run a few tests, and it seems to work for both odd and even arrays. Hope this is useful to the future somebody else stuck in my present position.
Thank you everyone who helped.
Min and Max
The min and max are easy.
def min_and_max_of_sums a
return [nil, nil] if a.empty?
negs, nonnegs = a.partition { |n| n < 0 }
[negs.any? ? negs.sum : nonnegs.min, nonnegs.any? ? nonnegs.sum : negs.max]
end
min_and_max_of_sums [1, 4, -5, 7, -8, 13]
#=> [-13, 25]
min_and_max_of_sums [1, 2, 3]
#=> [1, 6]
min_and_max_of_sums [-1, -2, -3]
#=> [-6, -1]
min_and_max_of_sums []
#=> [nil, nil]
Mean
Now consider the calculation of the mean.
If n is the size of the array a, there are 2n combinations of elements of a that contain between 0 and n elements.1 Moreover, there is a 1-1 mapping between each of those combinations and an n-vector of zeros and ones, where the ith element of the n-vector equals 1 if and only if the element ai is included in the combination. Note that there are 2n such n-vectors, one-half containing a 1 in the ith position. This means that one-half of the combinations contain the element ai. As i is arbitrary, it follows that each element of a appears in one-half of the combinations.
The mean of the sums of all elements of all combinations equals T/2n, where T is the sum of the sums of the elements of each combination. Each element ai appears in 2n/2 combinations, so its contribution to T equals (in Ruby terms)
a[i] * 2**(n)/2
As this hold for every element of a, the mean equals
a.sum * (2**(n)/2)/2**(n)
=> a.sum/2
Here's an example. For the array
a = [1, 4, 8]
the mean of the sums would be
a.sum/2
#=> 13/2 => 6.5
If we were to calculate the mean by its definition we would perform the following calculation (and of course get the same return value).
(0 + (1) + (4) + (8) + (1+4) + (1+8) + (4+8) + (1=4+8))/2**3
#=> (4*1 + 4*4 + 4*8)/8
#=> (1 + 4 + 8)/2
#=> 6.5
I will leave the calculating of the median to others.
1 Search for "Sums of the binomial coefficients" here.

Ruby's dynamic Inject method not working

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

How to 'reverse sum' in Ruby?

I have no clue how to call this in correct math-terms. Consider a method which takes two digits:
def num_of_sum(total, group_count)
end
where total is an integer and group_count is an integer.
How would I get a 'nicely' grouped Array of integers of group_count-length which sum up till total.
My spec would look like:
describe "number to sum of" do
it "grabs all numbers" do
expect(num_of_sum(10, 2)).to eq([5,5])
expect(num_of_sum(10, 3)).to eq([3,3,4])
expect(num_of_sum(20, 3)).to eq([6,7,7])
expect(num_of_sum(100, 3)).to eq([33,33,34])
expect(num_of_sum(100, 2)).to eq([50,50])
end
end
I tried this, which works:
def num_of_sum(total, in_groups_of)
result = []
section_count ||= (total.to_f / in_groups_of.to_f).round
while(total > 0)
total -= section_count
if (total - section_count) < 0 && (total + section_count).even?
section_count += total
total -= total
end
result << section_count
end
result
end
But, for instance, this spec doesn't work:
expect(num_of_sum(67,5)).to eq([13,13,13,14,14])
I need the array to contain numbers that are as close to each other as possible. But the array is limited to the length of the group_count.
Does someone know what the mathemetical name for this is, so I can search a bit more accurately?
The mathematical term for this is an integer partition
A more direct approach to this is to observe that if you do integer division (round down) of the total by the number of groups, then your sum would be short by total mod number_of_groups, so you just need to distribute that amount across the array:
def even_partition(total, number_of_groups)
quotient, remainder = total.divmod(number_of_groups)
(number_of_groups-remainder).times.collect {quotient} +
remainder.times.collect { quotient + 1}
end
def n_parts(num, groupcount)
div, mod = num.divmod(groupcount)
Array.new(groupcount-mod, div) + Array.new(mod, div+1)
end
n_parts(100,3) => [33, 33, 34]
Docs to Array.new and Fixnum.divmod
A naive implementation is like this:
Let's take example of (20, 3). You want three numbers as a result.
20 / 3 # => 6
This is your "base" value. Create an array of three sixes, [6, 6, 6]. That'll get you 18. Now you have to distribute remaining 2 as equally as possible. For example, enumerate array elements and increment each one by 1, until you have no value to distribute. Result is [7, 7, 6]. Good enough, I think.
Possible (working) implementation:
def breakdown(total, group_count)
avg_value, extra = total.divmod(group_count)
result = Array.new(group_count, avg_value)
extra.times do |i|
result[i] += 1
end
result
end
breakdown(10, 2) == [5, 5] # => true
breakdown(10, 3) == [4, 3, 3] # => true
breakdown(20, 3) # => [7, 7, 6]
I have no clue how it’s called, but here is a solution:
def num_of_sum sum, count
result = [i = sum / count] * count # prepare an array e.g. [3,3,3] for 10,3
result[sum - i * count..-1] + # these should be left intact
result[0...sum - i * count].map { |i| i + 1 } # these are ++’ed
end
Hope it helps.
Another way:
def floors_then_ceils(n, groups)
floor, ceils = n.divmod(groups)
groups.times.map { |i| (i < groups-ceils) ? floor : floor + 1 }
end
floors_then_ceils(10, 3)
#=> [3, 3, 4]
floors_then_ceils(9, 3)
#=> [3, 3, 3]
Alternatively, groups.times.map... could be replaced with:
Array.new(groups-ceils, floor).concat(Array.new(ceils, floor+1))

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