I'm in the process of learning Ruby, taking a Berkeley's MOOC, and, in some of these MOOC's homework we have an exercise that says:
Define a method sum_to_n? which takes an array of integers and an
additional integer, n, as arguments and returns true if any two
elements in the array of integers sum to n. An empty array should sum
to zero by definition.
I already created two methods that can do the job, but I'm not comfortable with any of them because I think they are not written in the Ruby Way. I hope some of you can help me to learn which would be the right way!
The first method I made uses the each method for both iterations, but what I don't like about this method is that every number is summed with every other number, even with the same number, doing something like this:
arr[1, 2, 3, 4] => 1+1, 1+2, 1+3, 1+4, 2+1, 2+2, 2+3, 2+4, 3+1, 3+2... 4+3, 4+4
As you can see, there's a lot of repeated sums, and I don't want that.
This is the code:
def sum_to_n?(arr, n)
arr.each {|x| arr.each {|y| return true if x + y == n && x != y}}
return true if n == 0 && arr.length == 0
return false
end
With the other method I got what I wanted, just a few sums without repeating any of them or even summing the same numbers, but it looks HORRIBLE, and I'm pretty sure someone would love to kill me for doing it this way, but the method does a great job as you can see:
arr[1, 2, 3, 4] => 1+2, 1+3, 1+4, 2+3, 2+4, 3+4
This is the code:
def sum_to_n?(arr, n)
for i in 0..arr.length - 1
k = i + 1
for k in k..arr.length - 1
sum = arr[i] + arr[k]
if sum == n
return true
end
end
end
return true if n == 0 && arr.length == 0
return false
end
Well, I hope you guys have fun doing a better and prettier method as I did trying.
Thank you for your help.
I'd write it like this:
def sum_to_n?(arr, n)
return true if arr.empty? && n.zero?
arr.combination(2).any? {|a, b| a + b == n }
end
That seems to be a pretty Rubyish solution.
I came across this on CodeWars. The accepted answer sure does look very Rubyish, but that is at the cost of performance. Calling arr.combination(2) results in a lot of combinations, it'd be simpler to go over the array element by element and search whether the 'complement' sum - element exists. Here's how that'd look like -
def sum_to_n?(arr, n)
(arr.empty? and n.zero?) or arr.any? { |x| arr.include?(n - x) }
end
Beside #jorg-w-mittag's answer. I found another solution using 'permutation'.
https://stackoverflow.com/a/19351660/66493
def sum_to_n?(arr, n)
(arr.empty? && n.zero?) || arr.permutation(2).any? { |a, b| a + b == n }
end
I didn't know about permutation before.
Still like #jorg-w-mittag answer because its more readable.
This one will do it in O(n.log(n)) rather than O(n²):
a = 1, 2, 3, 4
class Array
def sum_to? n
unless empty?
false.tap {
i, j, sorted = 0, size - 1, sort
loop do
break if i == j
a, b = sorted[i], sorted[j]
sum = a + b
return a, b if sum == n
sum < n ? i += 1 : j -= 1
end
}
end
end
end
a.sum_to? 7 #=> [3, 4]
I had a thought that the beginning of any answer to this question should probably start with pruning the array for superfluous data:
Can't use this:
arr.select! { |e| e <= n } # may be negative values
But this might help:
arr.sort!
while arr[0] + arr[-1] > n # while smallest and largest value > n
arr.delete_at(-1) # delete largest vaue
end
i wonder why no answers here using hash ?
def sum_to_n?(arr, n)
return true if arr.empty? && n.zero?
h = {}
arr.any? { |x| complement = h[n-x]; h[x] = true; complement }
end
puts sum_to_n?([1,2,3,4,5,7], 6) # true
puts sum_to_n?([6,2,3,5,7,9], 6) # false
puts sum_to_n?([3,4,5,3], 6) # true
puts sum_to_n?([3,4,5,7], 6) # false
puts sum_to_n?([], 6) # false
puts sum_to_n?([], 0) # true
I like rohitpaulk's answer but it fails when n doubles x. We should remove x from the array before sending include? n - x.
def sum_to_n?(arr, n)
return true if arr.empty? && n.zero?
arr.any? { |x| arr.tap { arr.delete_at arr.index x }.include? n - x }
end
Lam Phan's answer using a hash is the best
Related
I have a following programm
def calc_res(a)
n = a.length
result = 0
for i in 0 .. (n - 1)
for j in i .. (n - 1)
if (a[i] != a[j] && j - i > result) then
result = j - i
end
end
end
return result
end
which return following output
irb(main):013:0> calc_res([4, 6, 2, 2, 6, 6, 4])
=> 5
but it is taking time if array size is too large e.g. [0,1,2,3,.....70000]
can any one suggest me how can I optimize it.
Thanks
If I have understood the problem you are trying to solve (from code)
def calc_res(a)
last_index = a.length - 1
index = 0
while a[index] == a.last do
index = index + 1
break if index == last_index
end
last_index - index
end
It checks items from start if they are equal to items from end, end it moves the index toward the last element. As I understood you search for max length between different elements.
For you problem with [4, 6, 2, 2, 6, 6, 4] it will have one iteration and return 5, for the problem with [1...70000] it will have zero iterations and will return the difference in positions for those two (size of the array - 1)
My understanding is that the problem is to find two unique elements in the array whose distance apart (difference in indices) is maximum, and to return the distance they are apart. I return nil if all elements are the same.
My solution attempts to minimize the numbers of pairs of elements that must be examined before an optimal solution is identified. For the example given in the question only two pairs of elements need be considered.
def calc_res(a)
sz = a.size-1
sz.downto(2).find { |n| (0..sz-n).any? { |i| a[i] != a[i+n] } }
end
a = [4,6,2,2,6,6,4]
calc_res a
#=> 5
If sz = a.size-1, sz is the greatest possible distance two elements can be apart. If, for example, a = [1,2,3,4], sz = 3, which is the number of positions 1 and 4 are apart.
For a, sz = a.size-1 #=> 6. I first determine if any pair of elements that are n = sz positions apart are unique. [a[0], a[6]] #=> [4,4] is the only pair of elements 6 positions apart. Since they are not unique I reduce n by one (to 5) and examine all pairs of elements n positions apart, looking for one whose elements are unique. There are two pairs 5 positions apart: [a[0], a[5]] #=> [4,6] and [a[1], a[6]] #=> [6,4]. Both of these meet the test, so we are finished, and return n #=> 5. In fact we are finished after testing the first of these two pairs. Had neither these pairs contained unique values n would have been reduced by 1 to 4 and the three pairs [a[0], a[4]] #=> [4,6], [a[1], a[5]] #=> [6,6] and [a[2], a[6]] #=> [2,6] would have been searched for one with unique values, and so on.
See Integer#downto, Enumerable#find and Enumerable#any?.
A more rubyesque versions include:
def calc_res(a)
last = a.last
idx = a.find_index {|e| e != last }&.+(1) || a.size
a.size - idx
end
def calc_res(a)
last = a.last
a.size - a.each.with_index(1).detect(->{[a.size]}) {|e,_| e != last }.last
end
def calc_res(a)
last = a.last
a.reduce(a.size) do |memo, e|
return memo unless e == last
memo -= 1
end
end
def calc_res(a)
return 0 if b = a.uniq and b.size == 1
a.size - a.index(b[-1]).+(1)
end
Below I have posted the instructions for this problem along with my solution. A few test case scenarios have failed, but seem to be working for most. Can anybody help out at which point I've gone wrong? Any help is much appreciated!!
Using the Ruby language, have the function ArithGeo(arr) take the array of numbers stored in arr and return the string "Arithmetic" if the sequence follows an arithmetic pattern or return "Geometric" if it follows a geometric pattern.
If the sequence doesn't follow either pattern return -1.
An arithmetic sequence is one where the difference between each of the numbers is consistent
Arithmetic example: [2, 4, 6, 8]
In a geometric sequence, each term after the first is multiplied by some constant or common ratio.
Geometric example: [2, 6, 18, 54]
Negative numbers may be entered as parameters, 0 will not be entered, and no array will contain all the same elements.
Code:
def arithGeo(num)
idx = 0
while idx < num.length
if ((num[idx] - num[idx + 1]) == (num[idx + 1] - num[idx + 2]))
return "Arithmetic"
elsif ((num[idx + 1] / num[idx]) == (num[idx + 2] / num[idx + 1]))
return "Geometric"
else
return "-1"
end
idx += 1
end
end
#Test Cases that Failed
p arithGeo([1, 2, 3, 4, 5, 10, 20])
p arithGeo([1, 2, 3, 4, 5, 6, 7, 88, 2])
p arithGeo([10, 110, 210, 310, 410, 511])
OK, lets do a much more "ruby like" way:
def arith?(arr)
check_arr = []
arr.reverse.inject {|memo, num| check_arr << (memo - num); num}
#loop through from highest to lowest, subtracting each from the next and store in check_arr
check_arr.all? {|num| num == check_arr[-1]}
#check that all results are the same in the arr i.e. [2,2,2,2,2]
end
This returns true if all of the operations return the same result, thus a linear progression.
def geo?(arr)
check_arr = []
arr.reverse.inject {|memo, num| check_arr << (memo / num); num}
#loop through from highest to lowest, dividing each by the next and store in check_arr
check_arr.all? {|x| x == check_arr[-1]}
#check that all products are the same in the arr i.e. [3,3,3,3,3]
end
This returns true if all of the operations return the same result, thus a geometric progression.
Now use those methods in your other method
def arith_geo?(arr)
if arith?(arr)
'Arithmetic'
elsif geo?(arr)
'Geometric'
else
-1
end
end
You did use a while but you do not loop over the data, because you write return you will only ever look at the first three numbers and then immediately return the result. You will have to keep the previous result, and make sure the result stays the same to return either geometric or arithmetic.
This should help you to complete the exercise :)
I was able to do the solution in JavaScript and this is what I came up with:
function algoGeo(arr){
var algo = true;
var geo = true;
//first check algo
for(var k = 1; k < arr.length; k++){
if( (arr[0] + (arr[1] - arr[0]) * k) !== arr[k] && algo ){
algo = false;
}
if( arr[0] * Math.pow(arr[1] / arr[0], k) !== arr[k] && geo){
geo = false;
}
}
return algo ? "Arithmetic" : geo ? "Geometric" : -1;
}
var arr = [5,12,19,26];
console.log(algoGeo(arr));
def ArithGeo(arr)
diff1 = []
diff2 = []
arr.each_index do |x|
if(x + 1 < arr.length)
diff1 << arr[x + 1] - arr[x]
diff2 << arr[x + 1] / arr[x]
end
end
diff1.uniq.size == 1 ? "Arithmetic" : diff2.uniq.size == 1 ? "Geometric" : -1
end
A little late but this is what i came up with when trying to solve this same question.
What is the appropriate Method that takes 2 arguments array and n , and return true if sum of 2 array elements = n using ruby?
i know detect and inject(:+) but don't know how to combine both of them.
You can use Array#combination if the array length can be more than 2.
combination(2) returns all the combinations of length 2 and then any? returns true if at least one combination has sum n.
def foo(arr,n)
arr.combination(2).any?{|a,b| a + b == n}
end
foo [1,2,3,4,5], 3 #=> true
foo [1,2,3,4,5], 10 #=> false
Not sure how this is a useful function, but here you go
def weird_function arr, n
return arr[0] + arr[1] === n
end
Usage
weird_function [1,3], 4 #=> true
weird_function [1,2], 4 #=> false
Noob to Ruby here. Working through some exercises and have hit a wall.
Exercise: Calculate the letter grade of a series of grades
Create a method get_grade that accepts an Array of test scores. Each score in the array should be between 0 and 100, where 100 is the max score.
Compute the average score and return the letter grade as a String, i.e., 'A', 'B', 'C', 'D', 'E', or 'F'.
I keep returning the error:
avg.rb:1: syntax error, unexpected tLBRACK, expecting ')'
def get_grade([100,90,80])
^
avg.rb:1: syntax error, unexpected ')', expecting $end
Here's what I have so far. I'd like to stick with the methods below or .join as I'm trying to work with the methods we're learning in class. So sum, inject, etc won't necessarily be helpful. And I apologize in advance for the specificity of the request :) I'm sure there's a way better way that is way less code, but I'm just trying to learn it this way to start.
def get_grade([100,90,80])
get_grade = (array[0] + array[1] + array[2]).to_i / array.length.to_i
case get_grade
when 90..100
"A"
when 80..90
"B"
when 70..80
"C"
when 60..70
"D"
when 0..60
"F"
else
"Error"
end
end
puts get_grade([100,90,80])
You can't just randomly dump an array literal like [100,90,80] into the parameter list of a function definition. Judging by the function body, I think you meant to accept a single parameter array:
def get_grade(array)
grade = (array[0].to_i + array[1].to_i + array[2].to_i) / array.length
case grade
# unchanged
end
end
A terse replacement of the big case statement, for fun:
def letter_grade( score ) # assumes that score is between 0 and 100 (not 0-1)
%w[F F F F F F D C B A][ (score/10.0).floor ] || 'A' # handles grades >=100
end
Or, for more granularity:
def letter_grade( score ) # score is between 0 and 100 (not 0-1)
grades = %w[F F F F F F F F F F F F F F F F F F D- D D+ C- C C+ B- B B+ A- A A+ A+]
grades[ (3.0*score/10).floor ]
end
Thanks for the help today! Here's what I ended up doing to make it work with more than just 3 arguments. I used an Array#each method. I imagine there's a more elegant solution out there, but it worked! Worked on this since 10:00 AM, greatly appreciate the help!
def get_grade(array)
sum = 0
array.each do |element|
sum += element
end
average = sum / array.length
if average >= 90
grade = "A"
elsif average >= 80
grade = "B"
elsif average >= 70
grade = "C"
elsif average >= 60
grade = "D"
elsif average >= 0
grade = "F"
else
"Error"
end
end
puts get_grade([70,80,80,90,100])
puts get_grade([100,80,90,11,20])
puts get_grade([30,20,10,60,75])
Remember that the max score is 100 (and it can be assumed that the min is 0).
def get_grade(array)
sum = 0
array.each do |x|
sum += x
end
average = sum / array.length
if average > 100
print "Grades must be no more than 100!"
elsif average >= 90
grade = "A"
elsif average >= 80
grade = "B"
elsif average >= 70
grade = "C"
elsif average >= 60
grade = "D"
elsif average >=0
grade = "F"
else
print "Grades must be no less than 0!"
end
grade
end
puts get_grade([100,90,80]) == "A"
puts get_grade([98,90,80]) == "B"
puts get_grade([80,80,80]) == "B"
puts get_grade([55,45,35]) == "F"
puts get_grade([101,100,104])
puts get_grade([-2,-3,-4])
Added a proc so that even if a user enters a score over 100 it won't be calculated into the average.
Also refactored the switch statements to one line each. Let me know if this helps. Good luck.
def get_grade array
scores_under_100 = Proc.new {|score| score <= 100 && score > 0}
scores = array.select(&scores_under_100)
average = scores.inject(:+) / scores.size
case average
when 90..100 then puts "A."
when 80..89 then puts "B."
when 70..79 then puts "C."
when 60..69 then puts "D."
else puts "F."
end
end
puts get_grade([100, 100, 90, 67, 85, 200, 290, 299, 299])
How can we achieve this in Ruby?
xs = [1,2,3]
x = 5
Then I need that sum = 1+2+3+1+2 = 9
You have the abstractions you need in the core, just wire them together: Enumerable#cycle, Enumerable#take and Enumerable#inject:
>> [1, 2, 3].cycle.take(5).inject(0, :+)
=> 9
That's the functional/declarative approach: use abstractions (either existing or those you implement yourself) so you can write code that describes what you are doing instead of how you are doing it.
def get_sum_cyclic(array, number)
sum = 0
0.upto(number - 1) do |i|
sum += array[i % array.size]
end
return sum
end
Another option is using map over a range:
(0...5).map{|i| a[i % a.size]}.inject(0){|t,v| t + v}
I don't think that there is a special way to do it in Ruby, but you can achieve this with the following snippet.
(0..x).inject(0) do |sum, i|
sum += arr[ i % arr.size ]
end