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.
I want to write a recursive method that returns the first num recursive numbers.
Here is my code so far:
def recursive_factorials(num)
return [1] if num == 1
arr = recursive_factorials(num-1)
arr << num * arr.last
end
Not sure what I'm doing wrong. The expected result for num = 6 is [1, 1, 2, 6, 24, 120], and I get [1, 2, 6, 24, 120, 720], so I may be close but really have no idea.
Any help would be appreciated. Also, if I am not using recursion properly please let me out.
Question is about recursion, but also you can use iteration, it's faster:
def factorials(num)
m = 1
(0...num).map {|e| e.zero? ? 1 : m *= e }
end
factorials(6)
=> [1, 1, 2, 6, 24, 120]
Or by using hash memoisation (I would say its a recursion too):
factorials = Hash.new { |h, k| h[k] = h[k-1] * k }.update(0 => 1)
factorials.values_at(*(0..5))
=> [1, 1, 2, 6, 24, 120]
Here is an example:
def recursive_factorials(num, acc = [])
acc << (num < 2 ? 1 : (num - 1) * recursive_factorials(num - 1, acc).last)
end
recursive_factorials 6
#⇒ [1, 1, 2, 6, 24, 120]
A variation of Ilya's answer:
def each_factorial
return enum_for(__method__) unless block_given?
m = 1
1.step do |i|
yield m
m *= i
end
end
each_factorial.take(6)
#=> [1, 1, 2, 6, 24, 120]
I'm following Ruby practice problem from a website and I'm completely stuck on figuring out a solution to this problem. Basically, given a function has_sum?(val, arr), return true if any combination of numbers in the array (second parameter) can be added together to equal the first parameter, otherwise return false. So:
has_sum?(5, [1, 2, 3, 4]) # true
has_sum?(5, [1, 2, 6]) # false
I'm completely stuck and not quite sure how to accomplish this... Here's what I have so far.
def has_sum?(val, arr)
arr.each_with_index do |idx, v|
# ??? no idea what to do here except add the current num to the next in the list
end
end
Any help would be greatly appreciated - thanks!
An array can produce a sum when there is a subset of any length that adds up to that sum:
def has_sum?(val, arr)
(arr.size + 1).times
.flat_map { |i| arr.combination(i).to_a }
.any? { |s| s.inject(:+) == val }
end
has_sum?(5, [5])
# => true
has_sum?(5, [1, 2, 3])
# => true
has_sum?(5, [1, 1, 1, 1, 1, 1])
# => true
has_sum?(5, [1, 2, 7])
# => false
This is not very efficient as it generates all the possibilities before testing. This should terminate sooner:
def has_sum?(val, arr)
(arr.size + 1).times.any? { |i|
arr.combination(i).any? { |s| s.inject(:+) == val }
}
end
Even more efficiently, a recursive implementation, with the idea that a sum of an empty array is zero (and has_sum(nonzero, []) should return false); for a larger array, we pop off its head, and see if the sum of the rest of the array is okay if we count, or don't count, the head element. Here, we don't do the useless summing of the whole array over and over again:
def has_sum?(val, arr)
if arr.empty?
val.zero?
else
first, *rest = arr
has_sum?(val, rest) || has_sum?(val - first, rest)
end
end
This solution employs dynamic programming. I assume that zeroes have been removed from the array. If all numbers in the array are positive, we can also remove elements that are larger than the target sum.
Code
def sum_to_target(arr, target)
h = arr.each_index.with_object({}) do |i,h|
v = arr[i]
h.keys.each do |n|
unless h.key?(n+v) # || (n+v > target)
h[n+v] = v
return reconstruct(h, target) if n+v == target
end
end
h[v] = v unless h.key?(v)
return reconstruct(h, target) if v == target
end
nil
end
def reconstruct(h, target)
a = []
loop do
i = h[target]
a.unshift i
target -= i
return a if target == 0
end
a
end
Additional efficiency improvements are possible if arr contains only postive values.1
Examples
#1
sum_to_target [2,4,7,2], 8
#=> [2, 4, 2]
#2
arr = [64, 18, 64, 6, 39, 51, 87, 62, 78, 62, 49, 86, 35, 57, 40, 15, 74, 10, 8, 7]
a = sum_to_target(arr, 461)
#=> [64, 18, 39, 51, 87, 62, 78, 62]
Let's check that.
a.reduce(:+)
#=> 461
#3
a = sum_to_target([-64, 18, 64, -6, 39, 51, -87, 62, -78, 62, 49, 86, 35, 57,
40, 15, -74, 10, -8, -7], 190)
#=> [18, 64, -6, 39, 51, -87, 62, 49]
a.reduce(:+)
#=> 190
#4
arr = 1_000.times.map { rand 1..5_000 }
#=> [3471, 1891, 4257, 2265, 832, 1060, 3961, 875, 614, 2308, 2240, 3286,
# ...
# 521, 1316, 1986, 4099, 1398, 3803, 4498, 4607, 2262, 3941, 4367]
arr is an array of 1,000 elements, each a random number between 1 and 5,000.
answer = arr.sample(500)
#=> [3469, 2957, 1542, 950, 4765, 3126, 3602, 755, 4132, 4281, 2374,
# ...
# 427, 4238, 4397, 2717, 912, 1690, 3626, 169, 3607, 4084, 3161]
answer is an array of 500 elements from arr, sampled without replacement.
target = answer.reduce(:+)
#=> 1_226_020
target is the sum of the elements of answer. We will now search arr for a collection of elements that sum to 1,226,020 (answer being one such collection).
require 'time'
t = Time.now
#=> 2016-12-12 23:00:51 -0800
a = sum_to_target(arr, target)
#=> [3471, 1891, 4257, 2265, 832, 1060, 3961, 875, 614, 2308, 2240, 3286,
# ...
# 3616, 4150, 3222, 3896, 631, 2806, 1932, 3244, 2430, 1443, 1452]
Notice that a != answer (which is not surprising).
a.reduce(:+)
#=> 1226020
(Time.now-t).to_i
#=> 60 seconds
For this last example, methods that use Array#combination would have to wade though as many as
(1..arr.size).reduce(0) { |t,i| t + arr.combination(i).size }.to_f
#~> 1.07+301
combinations.
Explanation
Let
arr = [2,4,7,2]
target = 8
Suppose we temporarily redefine reconstruct to return the hash passed to it.
def reconstruct(h, target)
h
end
We then obtain the following:
h = sum_to_target(arr, target)
#=> {2=>2, 6=>4, 4=>4, 9=>7, 13=>7, 11=>7, 7=>7, 8=>2}
h is defined as follows.
Given an array of non-zero integers arr and a number n, if n is a key of h there exists an array a containing elements from arr, in the same order, such that the elements of a sum to n and the last element of a equals h[n].
which, admittedly, is a mouthful.
We now use the reconstruct (as defined in the "Code" section) to construct an array answer that will contain elements from arr (without repeating elements) that sum to target.
reconstruct(h, target) #=> [2, 4, 2]
Initially, reconstruct initializes the array answer, which it will build and return:
answer = []
h will always contain a key equal to target (8). As h[8] #=> 2 we conclude that the last element of answer equals 2, so we execute
answer.unshift(2) #=> [2]
The problem is now to find an array of elements from arr that sum to 8 - 2 #=> 6. As h[6] #=> 4, we conclude that the element in answer that precedes the 2 we just added is 4:
answer.unshift(4) #=> [4, 2]
We now need 8-2-4 #=> 2 more to total target. As h[2] #=> 2 we execute
answer.unshift(2) #=> [2, 4, 2]
Since 8-2-4-2 #=> 0 we are finished and therefore return answer.
Notice that 4 precedes the last 2 in arr and the first 2 precedes the 4 in arr. The way h is constructed ensures the elements of answer will always be ordered in this way.
Now consider how h is constructed. First,
h = {}
As arr[0] #=> 2, we conclude that, using only the first element of arr, all we can conclude is:
h[2] = 2
h #=> {2=>2}
h has no key equal to target (8), so we continue. Now consider arr[1] #=> 4. Using only the first two elements of arr we can conclude the following:
h[2+4] = 4
h #=> {2=>2, 6=>4}
and since h has no key 4,
h[4] = 4
h #=> {2=>2, 6=>4, 4=>4}
h still has no key equal to target (8), so we press on and examine arr[2] #=> 7. Using only the first three elements of arr we conclude the following:
h[2+7] = 7
h[6+7] = 7
h[4+7] = 7
h #=> {2=>2, 6=>4, 4=>4, 9=>7, 13=>7, 11=>7}
and since h has no key 7:
h[7] = 7
h #=> {2=>2, 6=>4, 4=>4, 9=>7, 13=>7, 11=>7, 7=>7}
We added four elements to h, but since arr contains only positive numbers, those with keys 9, 13 and 11 are of no interest.
Since h still does not have a key equal to target (8), we examine the next element in arr: arr[3] #=> 2. Using only the first four elements of arr we conclude the following:
h[4+2] = 2
h[6+2] = 2
Here we stop, since 6+2 == target #=> true.
h #=> {2=>2, 6=>2, 4=>4, 9=>7, 13=>7, 11=>7, 7=>7, 8=>2}
Notice that we did not compute h[2+2] = 2 since h already has a key 4. Further, had arr contained additional elements we still would have terminated the construction of the hash at this point.
Had we modified the code to take advantage of the fact that arr contains only positive values, the final hash would have been:
h #=> {2=>2, 6=>2, 4=>4, 7=>7, 8=>2}
If this is still not clear, it might be helpful to run the code for this example with included puts statements (e.g., puts "i=#{i}, h=#{h}, v=#{v}" after the line v = arr[i] in sum_to_target, and so on).
1 The line unless h.key?(n+v) can be changed to unless h.key?(n+v) || (n+v > target) if it is known that the array contains no negative elements. (Doing so reduced the solution time for example #4 by 4 seconds.) One could also compute #all_positive = arr.all?(&:positive?) and then make that line conditional on #all_positive.
I would do nested loops.
for x = 0 to length of array
for y = x + 1 to length of array
if number at x + number at y = sum return true
return false
Basically it will check the sum of each number with each of the numbers after it.
EDIT: this will only sum 2 numbers at a time. If you want to be able to sum any amount of numbers this wont work.
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 }