using nested case statements to return value - ruby-on-rails

I'm trying to use case statements to have a value returned, depending on the conditions. Unfortunately I'm getting a string of syntax errors because I'm not completely familiar with how a case statement should be set up.
Basically, a user can belong to a single state (region), so in the user table is where the user's "state_id" is stored (53 could be Oregon for example). I had most of this working using if statements, but it was far too messy, so I was told "case" would be the better way to go. I also have a model "tax" which belongs_to a user and a user can have many of.
Prior to changing to using case, I was doing if user.state_id == 53 for example, which was working, and then the conditions were if self.income <= 10276 which also worked. Now with the case however, although slightly more refactored, I can't get it working.. (many syntax errors due to the evaluators <= and all the others.)
def provincial
case user.state_id
when 53
case income
when <= 10276
return 0
when 10277..37568
return self.income * 0.0506
when 37569..75138
return ((self.income - 37568) * 0.077) + 1901
when 75139..86268
return ((self.income - 75138) * 0.105) + 4794
when 86269..104754
return ((self.income - 86268) * 0.1229) + 5963
when > 104754
return ((self.income - 104754) * 0.147) + 8235
end
when 52
case income
when <= 17593
return 0
when > 17593
return self.income * 0.10
end
end
end

You can't put the variable at the head of the case and then use a "dangling" inequality (an inequality where you only have an expression on one side). You would need the variable with the inequality, or you could use a proc.
So this when clause is fine:
when 10277..37568
return self.income * 0.0506
But this one isn't:
when > 17593
return self.income * 0.10
You can, however, replace it with this:
when proc {|n| n > 17593}
return self.income * 0.10
This gets rid of the "dangling" inequality by using a proc. You might also be able to replace it with this (a little weird, but it works):
Inf = 1.0/0 # Assigns "infinity" to Inf
when 17593..Inf
return self.income * 0.10
You could also move the variable to each when:
def provincial
case user.state_id
when 53
case
when income <= 10276
return 0
when (income >= 10277) and (income <= 37568)
return self.income * 0.0506
when (income >= 37569) and (income <= 75138)
return ((self.income - 37568) * 0.077) + 1901
...
end
when 52
case
when income <= 17593
return 0
when income > 17593
return self.income * 0.10
end
end
end
end
Overall, in this situation, I think I like the proc best. :)
Note you can't mix, in the same case statement, the variable at the head of the case and the variable at the when clauses. So this is not valid:
case x
when x < 3
# do stuff
when 5
# do stuff
end

Related

Is there a way to perform if-else using collect in ruby and update an array

$days = 25
action_count = [0,0,0]
if $days < 0
action_count[0] += 1
elsif $days <= 20
action_count[1] += 1
else
action_count[2] += 1
end
Can this if-else code be shortened using collect in ruby
I can't think of a way to do this using collect, but you could use Enumerable#bsearch_index.
If $days is an integer, this does the same thing as your if; but it is not very readable. (Integer, because there is a slight problem that you are using two different comparisons, and I'm assuming $days <= 20 is the same as $days < 21).
action_count[[0, 21].bsearch_index { |x| $days < x } || -1] += 1
bsearch_index finds whether $days is lower than 0, 21 or neither, returning 0, 1 or nil. We replace the nil case with -1 (last element), and we have an index we can use to increment an appropriate element of action_count.

How to reduce 'complexity too high' with || - or operator

I've got a simple method that counts total lesson hours in the university schedule for additional modules in the department (students can attend many departments)
def hours_total
#hours_total = user.open_departments.each_with_object({}) do |department, h|
h[department] = (sports_hours[department] || 0) +
(science_hours[department] || 0) +
(intership_sum[department] || 0) +
(art[department] || 0) -
((obligatory_topics[department] || 0) +
(base[department] || 0))
end
end
How can I fix here Cyclomatic complexity for hours_total is too high.? I have no idea how to not repeat || 0 cause in some departments sports_hours[department] can be nil value
The first step I'd take
def hours_total
#hours_total = user.open_departments.each_with_object({}) do |department, h|
positive = [sport_hours, science_hours, internship_sum, art].sum do |pos_h|
pos_h[department].to_i
end
negative = [obligatory_topics, base].sum do |neg_h|
neg_h[department].to_i
end
h[department] = positive - negative
end
end
Note: if your hours can be float values, substitute to_i with to_f.
Now if you and your Rubocop are ok with that, I'd probably leave it. If any of you is unhappy, the positive and negative should be extracted to a method.

Trying to refactor my ruby code

I have a score attribute starting from 30 points, and want to deduct 1 point for certain criteria it meets. Here is how I write it in a long way:
case
when user.growth_rate >= 0.1 && user.growth_rate <= 0.11
user.update(score: 29) if user.score != 29
when user.growth_rate >= 0.11 && user.growth_rate <= 0.12
user.update(score: 28) if user.score != 28
end
And the list goes on, Is there a better way/method to do logic like this?
It could be possible to calculate the score from the growth_rate maybe.
score = (30.0 - (user.growth_rate * 10)).floor
user.update(score: score) if user.score != score

checking user model variable using nested if statements

I'm trying to perform a check on my User model using a series of nested if statements. I have two models, User, which has_many :taxes, and tax which belongs_to :user. Within a column of the user table, each user has a specific "state id" which they select when they sign up (for example, state_id: 53 could mean California). Anyway, in my Tax model, I have several if statements which evaluate to see if a user is from a specific region, and if true, the conditions inside the if are performed.
For example:
if user_state_id == 53
# Do this
end
The problem is, if the user isn't a match to the first if statement, it doesn't continue to check the remaining statements to see if it matches any of the others. (See Below). If this user had a state_id of 53, it would work normally and run the remaining conditions inside. However, if the user had a state_id of 52 (next in the list) it would not evaluate.
def provincial
if user.state_id == 53 #BC
if calculation = self.income <= 10276
return 0
elsif calculation = self.income > 10276 && self.income <= 37568
return self.income * 0.0506
end
end
if user.state_id == 52 #AB
if calculation = self.income <= 17593
return 0
elsif calculation = self.income < 17593
return self.income * 0.10
end
end
if user.state_id == 60 #ON
if calculation = self.income <= 9574
return 0
elsif calculation = self.income > 9574 && self.income <= 39723
return self.income * 0.0505
end
end
end
I also tried using elsif statements for the primary nested condition checks (elsif user.state_id == 52...) but that did not work either.
I just posting a review of your code by using nested case statements.Sorry for the syntax errors(if any)
case user.state_id
when 53 #BC
case calculation
when <=10276
return 0
when 10277..37568
return self.income * 0.0506
when 52 #AB
case calculation
when 17593
return 0
when < 17593
return self.income * 0.10
when 60 #ON
case calculation
when <= 9754
return 0
when 9575..39793
return self.income * 0.0505
Hope it helps!
In your nested if statement for state_id == 52 you are checking twice whether income is smaller then 17593 (in both if and elseif).
if user.state_id == 52 #AB
if calculation = self.income <= 17593
return 0
elsif calculation = self.income < 17593 # shouldn't it be > 17593?
return self.income * 0.10
end
end
Also you are not covering all the cases! If state_id is equal to 53, and income is bigger than 37568, this will return nil.
Another thing, if you checked in you if statement that self.income <= 10276 (self is not needed btw), you don't need to recheck it again in elsesif (elseif self.income > 10276). There is no other option.
Your calculation variable is completely obsolete - your function returns if it is true and continues when its false.

How to create a nested loop with Ruby the "Right Way!"?

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

Resources