I have a method where I am regularly performing Logical AND on a variable, can it be refactored? This is sort of a 2 part question.
Is there a short circuit way of doing var = var && condition?
Is there a better way to write this based on multiple conditions and still only return a single boolean?
I'm hoping for something similar to a += kind of thing.
def my_method
var = true
if condition
var = var && cond1
end
if other_condition
var = var && cond2
end
var
end
I would use statement modifiers:
def my_method
res = true
res &&= cond1 if condition
res &&= cond2 if other_condition
res
end
Is there a short circuit way of doing var = var && condition?
Yes.
var &&= condition
Is there a better way to write this based on multiple conditions and still only return a single boolean?
def my_method
(cond1 || !condition ) &&
(cond2 || !other_condition)
end
Edit. p11y's comment is right.
If you don't like it, you can also write like this using keywords:
def my_method
(cond1 if condition ) and
(cond2 if other_condition)
end
Related
I try to programm snakes in Ruby. In order to get myself more familiar with Ruby. I define the position of every part of the snake through saving its X and Y value in two 1D arrays one for a X value and one for a Y value.
$x = [2,...]
$y = [2,...]
(What I forgot to tell is that the head of the Snake moves through user input while the rest just inherits its position from the part before like this.)
def length(f)
if $length >= f
$y[f] = $y[f-1]
$x[f] = $x[f-1]
end
end
In order to get a field for the Snake to move around I programmed this.
for a in (1..20)
for b in (1..20)
print " X "
end
puts" "
end
Which gives me a 20*20 field.
I then tried to display every part of the snake like on the field like this.(While also drawing a boarder around the field.)
for a in (1..20)
for b in (1..20)
if a == 1 || a == 20
if b == 1 || b == 20
print " + "
else
print " - "
end
elsif b == 1 || b == 20
print " | "
elsif a == $x[0] && b == $y[0]
body
elsif a == $x[1] && b == $y[1]
body
elsif a == $x[2] && b == $y[2]
body
elsif a == $x[3] && b == $y[3]
body
elsif a == $x[4] && b == $y[4]
body
else
print " "
end
end
puts""
end
This works but if the user is really good/ has a lot of spare time I need to make allot of elsif for every one represents a part of the snake if the snake should have as a limit a length of 100 I would need to make 100 elsif statements.(The body is just:
def body
print " # ".green
end
)
I tried fixing it with a for loop like this:
for c in (1..100)
if a == $x[c] && b == $y[c]
body
end
end
and this
loop do
$x.size.times do |index|
if $x[index] == a && $y[index] == b
body
end
end
break
end
But sadly this didn't gave the desired outcome for this interfered with the ifthat draws the boarders of the field.
Is there a way to combine these multiple elsif statements?
Every help would be highly appreciated. ( Sorry for being to vague in the first draft.)
Recommended Refactorings
NB: You included no sample data in your original post, so your mileage with answers will vary.
You have a number of issues, not just one. Besides not being DRY, your code is also not very testable because it's not broken out into discrete operations. There are a number of things you can (and probably should) do:
Break your "body" stuff into discrete methods.
Use Array or Enumerator methods to simplify the data.
Use dynamic methods to loop over your arrays, rather than fixed ranges or for-loops.
Use case/when statements inside your loop to handle multiple conditionals for the same variable.
In short, you need to refactor your code to be more modular, and to leverage the language to iterate over your data objects rather than using one conditional per element as you're currently doing.
Simplify Your Data Set and Handle Procedurally
As an example, consider the following:
def handle_matched_values array
end
def handle_mismatched_values array
end
paired_array = a.zip b
matched_pairs = paired_array.select { |subarray| subarray[0] == subarray[1] }
unmatched_pairs = paired_array.reject { |subarray| subarray[0] == subarray[1] }
matched_pairs.each { |pair| handle_matched_values pair }
matched_pairs.each { |pair| handle_mismatched_values pair }
In this example, you may not even need an if statement. Instead, you could use Array#select or Array#reject to find indices that match whatever criteria you want, and then call the relevant handler for the results. This has the advantage of being very procedural, and makes it quite clear what data set and handler are being paired. It's also quite readable, which is extremely important.
Dynamic Looping and Case Statements
If you truly need to handle your data within a single loop, use a case statement to clean up your conditions. For example:
# Extract methods to handle each case.
def do_something_with data; end
def do_something_else_with data; end
def handle_borders data; end
# Construct your data any way you want.
paired_array = a.zip b
# Loop over your data to evaluate each pair of values.
paired_array.each do |pair|
case pair
when a == b
do_something_with pair
when a == paired_array.first || paired_array.last
handle_borders pair
else
do_something_else_with pair
end
end
There are certainly plenty of other ways to work pairwise with a large data set. The goal is to give you a basic structure for refactoring your code. The rest is up to you!
I would start with something like this:
(1..20).each do |a|
(1..20).each do |b|
if [1, 20].include?(a)
print([1, 20].include?(b) ? ' + ' : ' - ')
elsif (1..100).any? { |i| a == $x[i] && b == $y[i] }
body
else
print(' ')
end
puts('')
end
end
I suppose this would work as a solution even if it is not that advanced?
loop do
$x.size.times do |index|
if $x[index] == a && $y[index] == b
body
end
end
break
end
Is there a more concise way to loop through a number of conditions than this?
def is_this_whole_thing_true?
result = false
result = true if condition_1?
result = true if condition_2?
result = true if condition_3?
result = true if condition_4?
result = true if condition_5?
result = true if condition_6?
result = true if condition_7?
result
end
Thanks for any help.
If you don't care to create a whole array I think this is the best option
def is_this_whole_thing_true?
conditions = [condition1?, condition2?, condition3?, condition4?]
conditions.any?
end
you can simply put && condition here like:
def is_this_whole_thing_true?
condition_1? && condition_2? && condition_3? && ...
end
Infact, what you are trying to do in the method is return true if any of the condition is true, then use ||
def is_this_whole_thing_true?
condition_1? || condition_2? || condition_3? || ...
end
An advantage of this is, it won't check all of the conditions, as soon as any one of the condition turns out to be true, it will return.
if j.job_type_name
j.type = j.job_type_name
elsif j.type.length > 1
# jty = Jobtype.find_by_id(j.type)
# We don't want to relentlessly hit the db for JobTypes, Colors, Priorities, etc. so we use the stored 'all' variable
jty = allJobTypes.find { |h| h['_id'] == BSON::ObjectId(j.type) }
j.type = jty && jty.job_type ? jty.job_type : 'N/A'
end
if j.priority_name
j.priority = j.priority_name
elsif j.priority
pri = Jobpriority.find_by_id(j.priority)
j.priority = !pri.blank? && !pri.job_priority.blank? ? pri.job_priority : 'N/A'
end
I have these two if else blocks and I think they are better off as Procs, but I'm still wrapping my head around what a Proc is. It's just a piece of code that I want to call often, and put in one place so that I only have to update it in that one place. It's not a method on an object per-se, but just some repetitious code.
So what's the syntax for the above?
The code that you don't see (where 'j' is set) is an .each loop over a database cursor.
So Job.where(stuff).each do |j|
etc.
The syntax for making a proc is just:
my_proc = proc do |arglist|
# code here
end
You can also use Proc.new instead of proc if you want.
You should be able to make a proc and then pass it in as the block to .each, (but I don't see what this would get you):
def process_jobs(stuff)
process_job = proc do |j|
if j.job_type_name
#...
end
end
Job.where(stuff).each(&process_job)
end
Is there a simple way of testing that several variables have the same value in ruby?
Something linke this:
if a == b == c == d #does not work
#Do something because a, b, c and d have the same value
end
Of course it is possible to check each variable against a master to see if they are all true, but that is a bit more syntax and is not as clear.
if a == b && a == c && a == d #does work
#we have now tested the same thing, but with more syntax.
end
Another reason why you would need something like this is if you actually do work on each variable before you test.
if array1.sort == array2.sort == array3.sort == array4.sort #does not work
#This is very clear and does not introduce unnecessary variables
end
#VS
tempClutter = array1.sort
if tempClutter == array2.sort && tempClutter == array3.sort && tempClutter == array4.sort #works
#this works, but introduces temporary variables that makes the code more unclear
end
Throw them all into an array and see if there is only one unique item.
if [a,b,c,d].uniq.length == 1
#I solve almost every problem by putting things into arrays
end
As sawa points out in the comments .one? fails if they are all false or nil.
tokland suggested a very nice approach in his comment to a similar question:
module Enumerable
def all_equal?
each_cons(2).all? { |x, y| x == y }
end
end
It's the cleanest way to express this I've seen so far.
How about:
[a,b,c,d] == [b,c,d,a]
Really just:
[a,b,c] == [b,c,d]
will do
a = [1,1,1]
(a & a).size == 1 #=> true
a = [1,1,2]
(a & a).size == 1 #=> false
[b, c, d].all?(&a.method(:==))
Lua 5.1.4
For example:
bar = {}
bar.name = 'test'
bar['123.com'] = function(self) print(self.name) end
I can't call the method like below:
bar:['123.com']()
stdin:1: '<name>' expected near '['
Althought below works:
bar['123.com'](bar)
But I this is somehow ugly. Is there a syntax sugar for this situation?
Or if it really cannot do it, will Lua team add this syntax in future?
Or they did this intentionally?
No we can not call the method like you want. Your suppose to call the method as following syntax only.
bar['123.com'](bar)
Just make an alias that doesn't begin with numbers and use that.
bar.name123 = bar['123name']
bar:name123()
Nobody knows what the Lua maintainers will add in future versions (they're pretty close-mouthed), but my guess would be that it's unlikely they'll add it without at least being asked—and I've never seen a request for such a thing on the Lua mailing list...
My intuition, though, is that this functionality seems obscure: how often do people really want to call methods with "weird" names like that? Given that, and that the "workaround" really isn't bad at all, it's unclear whether it's worth adding complexity to the implementation to support such a feature (unless it's completely trivial).
If you want to try asking, you should post about it to the Lua mailing list, which is where most such discussion takes place: lua-l#lists.lua.org
You might want to consider something like below (you can test online at http://www.lua.org/cgi-bin/demo):
bar = {}
bar.name = 'test'
bar['123.com'] = function(self) print(self.name) end
bar2 = setmetatable({}, {
__index = function(t, key)
return function(...)
return bar[key](bar, ...)
end
end
})
bar2['123.com']()
-- output: test
You can also change bar itself to behave in a similar way, only you must do it before you assign any values - e.g.:
bar = {}
local privatekey = {}
setmetatable(bar, {
__index = function(t, key)
local value = rawget(t, privatekey)[key]
if type(value) ~= 'function' then
return value
end
return function(...)
return value(t, ...)
end
end,
__newindex = function(t, key, value)
rawset(t, privatekey, rawget(t, privatekey) or {})
rawget(t, privatekey)[key] = value
end
})
bar.name = 'test'
bar['123.com'] = function(self) print(self.name) end
bar['123.com']()
-- output: test
You can use a closure:
bar = {}
bar.name = 'test'
bar['123.com'] = function() print(bar.name) end
bar['123.com']()
function bar:addBar(name)
bar[name] = function() print(self.name) end
end
bar:addBar('456.com')
bar['456.com']()
Oh god...
bar = {
['123.com'] = function(self)
print("YAY!", self)
end
}
function f()
print(bar)
bar:WHATTHE()
end
function crazy(f, patt, repl)
local f_str = string.dump(f)
local newf_str = string.gsub(f_str, "WHATTHE", "123.com")
assert(#newf_str == #f_str, "replacement must be of same length")
local newf = loadstring(newf_str)
setfenv(newf, getfenv(f))
return newf
end
f = crazy(f, "WHATTHE", "123.com")
f()
--[[ Output:
table: 005EB688
YAY! table: 005EB688
]]
Don't do this - ever.