Whats wrong with this if statement? Rails - ruby-on-rails

I am building a reputation system where users get points if milestones (10, 100, 1000, ...) are archieved. I have this if statement line:
if (before_points < ((10 || 100 || 1000 || 10000 || 100000 || 1000000))) && (after_points >= ((10 || 100 || 1000 || 10000 || 100000 || 1000000)))
It should return true if the points where either less than 10 or 100 or 1000 ...before, and if the points were more or equal to either 10 or 100 or 1000 ... afterwards.
It works if it was below 10 before, and more than 10 afterwards, and I am not quite sure if it works with 100, but it doesnt work if the points were below 1000 before and more than 1000 afterwards.
Is this the correct way to do this? Is it better to do this with a switch/case?

A more compact way you could do it...
[10, 100, 1000, 10000, 100000, 1000000].any?{|n| before_points < n && after_points >= n}
That expression will return true if a boundary is crossed, or false otherwise

That's not really how logic operation work. The statement:
(10 || 100 || 1000 || 10000 || 100000 || 1000000)
will evaluate to 10. The || operator between 2 or more numbers will return first non-nil value, in this case that's 10, the first value. Related question.
And even if that weren't the case, if the before_points < 10 is true, the before_points < 1000000 would also be true and if only before_points < 1000000 was true, the if statement would still execute just the same as with before_points < 10, so the logic would be wrong.
Depending on what you want to solve, you could either use case or define your milestones in array and iterate values 10,100,...,1000000, setting new milestone each time the condition is still true.

Your assumption is wrong.
if (before_points < ((10 || 100 || ...
will first evaluate the part
10 || 100
which will always return 10 because 10 evaluates to truthy, hence this line
if (before_points < ((10 || 100 || 1000 || 10000 || 100000 || 1000000))) && (after_points >= ((10 || 100 || 1000 || 10000 || 100000 || 1000000)))
is effectively the same of
if (before_points < 10) && (after_points >= 10)
I'm not sure what you want to achieve, but it's probably better to use a case (this is just an example)
case
when before_points < 10 && after_points >= 10
# ...
when before_points < 100 && after_points >= 100
# ...
else
# ...
end

Related

Is "or" defined differently for Mathematica and Mathics?

In Mathematica, "or" looks to be defined with a double vertical bar. See https://reference.wolfram.com/language/ref/Or.html or https://mathematica.stackexchange.com/a/119763.
In Mathics 2.1.0, that doesn't seem to work:
In[16]:= If[1<0 || 2<3 || 3<4, 0, 1]
Syntax::sntxf: "If[1<0 " cannot be followed by " 2<3  3<4, 0, 1]" (line 1 of "<stdin>").
whereas the word "or" seems to work:
In[16]:= If[1<0 or 2<3 or 3<4, 0, 1]
Out[16]= 1
So do I have to use || in Mathematica and or in Mathics, or am I mistaken?
Mathics has a documentation, though it's a pdf for some reason, https://mathics.org/docs/mathics-latest.pdf
Page 11 at the moment (part of 3. Language Tutorials):
Comparisons and Boolean Logic
[...]
Truth values can be negated using ! (logical not) and combined using && (logical and) and || (logical or):
>> !True
False
>> !False
True
>> 3 < 4 && 6 > 5
True
&& has higher precedence than ||, i.e. it binds stronger:
>> True && True || False && False
True
>> True && (True || False) && False
False
based on that I would indeed expect || and && to work.
Also, there is an example on page 44 with If[x == 0.0 && y <= 0, 0.0, Sin[x ^ y] + 1 / Min[x, 0.5]] + 0.5].
Your error message may be the key here as the || became those boxed question marks, which may be something about character encoding. If it is a file you are running, it may be worth checking if it's ASCII.

Shortening a long if else statement

I have a long if else statement:
rnd = rand(1..1000)
if rnd >= 600
0
elsif rnd < 600 && rnd >= 350
1
elsif rnd < 350 && rnd >= 270
2
elsif rnd < 270 && rnd >= 200
3
elsif rnd < 200 && rnd >= 150
4
elsif rnd < 150 && rnd >= 100
5
elsif rnd < 100 && rnd >= 80
6
elsif rnd < 80 && rnd >= 50
7
elsif rnd < 50 && rnd >= 30
8
else
9
end
I would like to shorten it. Is it possible?
My rubocop swears at this long method.
I would start with something like this:
RANGES = {
(0...30) => 9,
(30...50) => 8,
(50...80) => 7,
# ...
(350...600) => 1,
(600...1000) => 0
}
rnd = rand(1..1000)
RANGES.find { |k, _| k.cover?(rnd) }.last
Great answers already! Just chiming in since I had a suspicion that ruby could handle this with a case statement, and it appears to be able to do so:
rnd = rand(1..1000)
case rnd
when 600.. then 0
when 350...600 then 1
when 270...350 then 2
...
else 9
end
Regardless of the approach taken, you're going to have to specify the ranges somewhere, so I think using something like a case statement is appropriate here (sorry! It doesn't shorten the code more than a few lines). Using a hash would also be a great approach (and might allow you to move the hash elsewhere), as other commenters have already shown.
It's worth mentioning, with ruby ranges, .. means that the range is inclusive and includes the last value (1..10 includes the number 10), and ... means the range is exclusive where it does not include the last value.
The top case 600.. is an endless range, which means it will match anything greater than 600. (That functionality was added in ruby 2.6)
You can simplify your conditions by using only the lower bound.
And you can avoid repeting elsif because it is cumbersome
rnd = rand(1..1000)
lower_bounds = {
600 => 0,
350 => 1,
270 => 2,
200 => 3,
150 => 4,
100 => 5,
80 => 6,
50 => 7,
30 => 8,
0 => 9,
}
lower_bounds.find { |k, _| k <= rnd }.last
MX = 1000
LIMITS = [600, 350, 270, 200, 150, 100, 80, 50, 30, 0]
The required index can be computed as follows.
def doit
rnd = rand(1..MX)
LIMITS.index { |n| n <= rnd }
end
doit
#=> 5
In this example rnd #=> 117.
If this must be repeated many times, and speed is paramount, you could do the following.
LOOK_UP = (1..MX).each_with_object({}) do |m,h|
h[m] = LIMITS.index { |n| n <= m }
end
#=> {1=>9, 2=>9,..., 29=>9,
# 30=>8, 31=>8,..., 49=>8,
# ...
# 600=>0, 601=>0,..., 1000=>0}
Then simply
def doit
LOOK_UP[rand(1..MX)]
end
doit
#=> 3
In this example rand(1..MX) #=> 262.
If speed were paramount but MX were so large that the previous approach would require excessive memory, you could use a binary search.
def doit
rnd = rand(1..MX)
LIMITS.bsearch_index { |n| n <= rnd }
end
doit
#=> 5
In this example rnd #=> 174.
See Array#bsearch_index. bsearch_index returns the correct index in O(log n), n being LIMITS.size). bsearch_index requires the array on which it operates to be ordered.

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.

Why does my code only select year that is equal 10?

I'm a beginner in programming and have problems with this if statement:
if (f.year == (10 || 20 || 30 || 40 || 50 || 60 || 70 || 80 || 90 || 100 || 110 || 120)) && (f.rund != true)
The first problem is that this code is very complicated. Actually I only want to check if the f.year is a round two-digit number.
Next my code does not work correctly. Somehow it only selects the f.year that are equal 10.
How can I solve these problems?
It's because
(10 || 20 || 30 || 40 || 50 || 60 || 70 || 80 || 90 || 100 || 110 || 120)
expression always evaluates to 10.
You can solve the problem with, for example:
(1..12).map { |el| el * 10 }.include?(f.year)
or, as suggested by #AurpRakshit:
(1..12).map(&10.method(:*)).include?(f.year)
Here you have more examples of generating this kind of array.
Or, if you really want to check if f.year is round two-digit number, you can:
(10...100).include?(f.year) && f.year % 1 == 0
You can use Range#step or Numeric#step:
(10..120).step(10).to_a #=> [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]
10.step(120, 10).to_a #=> [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]
And call Enumerable#include?:
(10..120).step(10).include? year
10.step(120, 10).include? year
To answer your first point, the code should read:
if (f.year == 10 || f.year == 20 || f.year == 30 ...
Your expression f.year == (10 || 20 || 30 ... doesn't work, because it is evaluated by ruby as follows:
The brackets force 10 || 20 || 30 ... to be evaluated first
The || operator returns its left operand if it is true, otherwise it returns its right operand
Ruby considers anything that isn't nil or false to be "true", so the expression 10 || 20 || 30 ... evaluates to 10
So your expression boils down to (f.year == 10) && (f.rund != true)
You are already told why your code doesn't work as expected, I'm answering just to suggest to use a mathematical approach here instead of using include?, your condition could be written as:
if f.year.modulo(10).zero? && f.year.between?(10, 120) && !f.rund
...
It may be a little less clear but it is much faster.
Update
The drawback of this solution is that it fails when f.year is not a Numeric object:
nil.modulo(10)
# NoMethodError: ...
While:
[10].include?(nil)
# => false
The benchmarck:
require 'fruity'
a = (1..10000)
compare do
map_include do
a.each do |i|
(1..12).map(&10.method(:*)).include?(i)
end
end
step_include do
a.each do |i|
(10..120).step(10).include?(i)
end
end
divmod_include do
a.each do |i|
q, r = i.divmod(10); (1..12).include?(q) && r.zero?
end
end
math do
a.each do |i|
i.modulo(10).zero? && i.between?(10, 120)
end
end
end
Running each test once. Test will take about 2 seconds.
math is faster than divmod_include by 1.9x ± 0.01
divmod_include is faster than step_include by 9x ± 0.1
step_include is faster than map_include by 3.4x ± 0.1
I am not sure about your question, but the first condition can be written as
q, r = f.year.divmod(10); (1..12).include?(q) && r.zero?
or
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120].include?(f.year)
It's hard to tell what the OP wants but...
require 'fruity'
ARY = (1..1000).to_a
compare do
test_mod_and_le do
ARY.each do |i|
(i % 10 == 0) && (i <= 120)
end
end
test_mod_and_range do
ARY.each do |i|
(i % 10 == 0) && ((10..120) === i)
end
end
test_case_when do
ARY.each do |i|
case i
when 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120
true
else
false
end
end
end
map_include do
ARY.each do |i|
(1..12).map(&10.method(:*)).include?(i)
end
end
step_include do
ARY.each do |i|
(10..120).step(10).include?(i)
end
end
divmod_include do
ARY.each do |i|
q, r = i.divmod(10); (1..12).include?(q) && r.zero?
end
end
math do
ARY.each do |i|
i.modulo(10).zero? && i.between?(10, 120)
end
end
end
Which outputs:
Running each test 32 times. Test will take about 4 seconds.
test_case_when is similar to test_mod_and_le
test_mod_and_le is faster than test_mod_and_range by 19.999999999999996% ± 10.0%
test_mod_and_range is faster than math by 50.0% ± 10.0%
math is faster than divmod_include by 80.0% ± 10.0%
divmod_include is faster than step_include by 5.9x ± 0.1
step_include is faster than map_include by 2.9x ± 0.1
Conditions do not work this way. All numbers within the brackets, connected with OR are evaluated before checking the equality with f.year.
Most answers here seem overcomplicated. You can use basic math to solve your issue:
if year % 10 == 0 && year.to_s.size == 2
# do stuff
end
The modulo operator % returns the remainder when dividing by 10 in this example. If the remainder is 0, it's a multiple of 10. You can use any number. Modulo 2 would check whether the number is even.
The second part checks the number of digits. It converts it to a string first with to_s and then checks the length of it, basically how many characters are in there. Converting 10 to string results in '10' which has 2 characters.
Your question seems a bit unclear. Do you want to include the numbers 100, 110 and 120 like in your code example? Or do you want only two digit numbers like stated in your text?

all values same sign validation

User should insert all the values either positive or negative.
How may i set same sign validation ?
Right i have written this on before_save ..
unless (self.alt_1 >= 0 && self.alt_2 >=0 && self.alt_3 >= 0 &&
self.alt_4 >= 0 && self.alt_5 >= 0 && self.alt_6 >= 0) ||
(self.alt_1 <= 0 && self.alt_2 <=0 && self.alt_3 <= 0 &&
self.alt_4 <= 0 && self.alt_5 <= 0 && self.alt_6 <= 0)
self.errors.add_to_base(_("All values sign should be same."))
end
first_sign = self.alt_1 <=> 0
(2..6).each do |n|
unless (self.send("alt_#{n}") <=> 0) == first_sign
errors.add_to_base(_("All values' signs should be same."))
break
end
end
With this method we first get the sign of alt_1, and then see if the signs of the rest of the elements (alt_2 through alt_6) match. As soon as we find one that doesn't match we add the validation error and stop. It will run a maximum of 6 iterations and a minimum of 2.
Another more clever, but less efficient method, is to use the handy method Enumerable#all?, which returns true if the block passed to it returns true for all elements:
range = 1..6
errors.add_to_base(_("All values' signs should be same.")) unless
range.all? {|n| self.send("alt_#{n}") >= 0 } ||
range.all? {|n| self.send("alt_#{n}") <= 0 }
Here we first check if all of the elements are greater than 0 and then if all of the elements are less than 0. This method iterates a maximum of 12 times and a minimum of 6.
Here's a slightly different approach for you:
irb(main):020:0> def all_same_sign?(ary)
irb(main):021:1> ary.map { |x| x <=> 0 }.each_cons(2).all? { |x| x[0] == x[1] }
irb(main):022:1> end
=> nil
irb(main):023:0> all_same_sign? [1,2,3]
=> true
irb(main):024:0> all_same_sign? [1,2,0]
=> false
irb(main):025:0> all_same_sign? [-1, -5]
=> true
We use the spaceship operator to obtain the sign of each number, and we make sure that each element has the same sign as the element following it. You could also rewrite it to be more lazy by doing
ary.each_cons(2).all? { |x| (x[0] <=> 0) == (x[1] <=> 0) }
but that's less readable in my opinion.
unless
[:<=, :>=].any? do |check|
# Check either <= or >= for all values
[self.alt1, self.alt2, self.alt3, self.alt4, self.alt5, self.alt6].all? do |v|
v.send(check, 0)
end
end
self.errors.add_to_base(_("All values sign should be same."))
end

Resources