What is the best way to handle this type of inclusive logic in Ruby? - ruby

Is there a better way of handling this in Ruby, while continuing to use the symbols?
pos = :pos1 # can be :pos2, :pos3, etc.
if pos == :pos1 || pos == :pos2 || pos == :pos3
puts 'a'
end
if pos == :pos1 || pos == :pos2
puts 'b'
end
if pos == :pos1
puts 'c'
end
The obvious way would be swapping out the symbols for number constants, but that's not an option.
pos = 3
if pos >= 1
puts 'a'
end
if pos >= 2
puts 'b'
end
if pos >= 3
puts 'c'
end
Thanks.
EDIT
I just figured out that Ruby orders symbols in alpha/num order. This works perfectly.
pos = :pos2 # can be :pos2, :pos3, etc.
if pos >= :pos1
puts 'a'
end
if pos >= :pos2
puts 'b'
end
if pos >= :pos3
puts 'c'
end

Not sure if this is the best way......
I would make use of the include? method from array:
puts 'a' if [:pos1, :pos2, :pos3].include? pos
puts 'b' if [:pos1, :pos2].include? pos
puts 'c' if [:pos1].include? pos

Just use the case statement
pos = :pos1 # can be :pos2, :pos3, etc.
case pos
when :pos1 then %w[a b c]
when :pos2 then %w[a b]
when :pos3 then %w[a]
end.each {|x| puts x }

There are lots of different ways to get your output. Which one you
want depends on your specific objections to your if statements.
I've added a bunch of extra formatting to make the output easier
to read.
If you don't like the logical ORs and how they separate the results
from the output, you can use a lookup table:
puts "Lookup table 1:"
lookup_table1 = {
:pos1 => %w{a b c},
:pos2 => %w{a b },
:pos3 => %w{a },
}
[:pos1, :pos2, :pos3].each { |which|
puts "\t#{which}"
lookup_table1[which].each { |x| puts "\t\t#{x}" }
}
Or, if you want all the "work" in the lookup table:
puts "Lookup table 2:"
lookup_table2 = {
:pos1 => lambda do %w{a b c}.each { |x| puts "\t\t#{x}" } end,
:pos2 => lambda do %w{a b }.each { |x| puts "\t\t#{x}" } end,
:pos3 => lambda do %w{a }.each { |x| puts "\t\t#{x}" } end,
}
[:pos1, :pos2, :pos3].each { |which|
puts "\t#{which}"
lookup_table2[which].call
}
If your problem is that symbols aren't ordinals, then you can
ordinalize them by converting them to strings:
puts "Ordinals by .to_s and <="
[:pos1, :pos2, :pos3].each { |which|
puts "\t#{which}"
if which.to_s <= :pos3.to_s
puts "\t\ta"
end
if which.to_s <= :pos2.to_s
puts "\t\tb"
end
if which.to_s <= :pos1.to_s
puts "\t\tc"
end
}
Or you could monkey patch a comparison operator into the Symbol
class (not recommended):
puts "Ordinals by Symbol#<="
class Symbol
def <= (x)
self.to_s <= x.to_s
end
end
[:pos1, :pos2, :pos3].each { |which|
puts "\t#{which}"
if which <= :pos3
puts "\t\ta"
end
if which <= :pos2
puts "\t\tb"
end
if which <= :pos1
puts "\t\tc"
end
}
Or you could use a lookup table to supply your ordinal values:
puts "Ordinals through a lookup table:"
ordinal = {
:pos1 => 1,
:pos2 => 2,
:pos3 => 3,
}
[:pos1, :pos2, :pos3].each { |which|
puts "\t#{which}"
if ordinal[which] <= 3
puts "\t\ta"
end
if ordinal[which] <= 2
puts "\t\tb"
end
if ordinal[which] <= 1
puts "\t\tc"
end
}
Those are the obvious ones off the top of my head. It is hard to say what would be best without more specifics on what your problem with your if approach is; your second example indicates that what you really want is a way to make symbols into ordinals.

More generically, you can use this:
pos = :pos3
arr = [:pos1,:pos2,:pos3]
curr = 'a'
idx = arr.length
while idx > 0
puts curr if arr.last(idx).include? pos
curr = curr.next
idx -= 1
end
Or this, for your specific example:
puts 'a'
puts 'b' if pos != :pos3
puts 'c' if pos == :pos1

Related

What's wrong with my code?

def encrypt(string)
alphabet = ("a".."b").to_a
result = ""
idx = 0
while idx < string.length
character = string[idx]
if character == " "
result += " "
else
n = alphabet.index(character)
n_plus = (n + 1) % alphabet.length
result += alphabet[n_plus]
end
idx += 1
end
return result
end
puts encrypt("abc")
puts encrypt("xyz")
I'm trying to get "abc" to print out "bcd" and "xyz" to print "yza". I want to advance the letter forward by 1. Can someone point me to the right direction?
All I had to do was change your alphabet array to go from a to z, not a to b, and it works fine.
def encrypt(string)
alphabet = ("a".."z").to_a
result = ""
idx = 0
while idx < string.length
character = string[idx]
if character == " "
result += " "
else
n = alphabet.index(character)
n_plus = (n + 1) % alphabet.length
result += alphabet[n_plus]
end
idx += 1
end
return result
end
puts encrypt("abc")
puts encrypt("xyz")
Another way to solve the issue, that I think is simpler, personally, is to use String#tr:
ALPHA = ('a'..'z').to_a.join #=> "abcdefghijklmnopqrstuvwxyz"
BMQIB = ('a'..'z').to_a.rotate(1).join #=> "bcdefghijklmnopqrstuvwxyza"
def encrypt(str)
str.tr(ALPHA,BMQIB)
end
def decrypt(str)
str.tr(BMQIB,ALPHA)
end
encrypt('pizza') #=> "qjaab"
decrypt('qjaab') #=> "pizza"
Alternatively if you don't want to take up that memory storing the alphabet you could use character codings and then just use arithmetic operations on them to shift the letters:
def encrypt(string)
result = ""
idx = 0
while idx < string.length
result += (string[idx].ord == 32 ? (string[idx].chr) : (string[idx].ord+1).chr)
idx += 1
end
result
end
Other strange thing about ruby is that you do not need to explicitly return something at the end of the method body. It just returns the last thing by default. This is considered good style amongst ruby folks.
Your question has been answered, so here are a couple of more Ruby-like ways of doing that.
Use String#gsub with a hash
CODE_MAP = ('a'..'z').each_with_object({}) { |c,h| h[c] = c < 'z' ? c.next : 'a' }
#=> {"a"=>"b", "b"=>"c",..., "y"=>"z", "z"=>"a"}
DECODE_MAP = CODE_MAP.invert
#=> {"b"=>"a", "c"=>"b",..., "z"=>"y", "a"=>"z"}
def encrypt(word)
word.gsub(/./, CODE_MAP)
end
def decrypt(word)
word.gsub(/./, DECODE_MAP)
end
encrypt('pizza')
#=> "qjaab"
decrypt('qjaab')
#=> "pizza"
Use String#gsub with Array#rotate
LETTERS = ('a'..'z').to_a
#=> ["a", "b", ..., "z"]
def encrypt(word)
word.gsub(/./) { |c| LETTERS.rotate[LETTERS.index(c)] }
end
def decrypt(word)
word.gsub(/./) { |c| LETTERS.rotate(-1)[LETTERS.index(c)] }
end
encrypt('pizza')
#=> "qjaab"
decrypt('qjaab')
#=> "pizza"

Rewriting Ruby Inject

Here is a brain bender.
I am trying to rewrite the Ruby Inject method. I have got as far as below.
class Array
def injector(input = nil)
if input == nil
num = self.first
else
num = input
end
self[0..-1].each do |x|
num = yield(num, x)
end
return num
end
end
It is passing some tests, but it is not fully accurate, for example;
[1,2,3,4,5].injector(0) {|x,y| x + y} #=> 14
As opposed to the expected output 15, is it a rounding error? I cannot seem to figure this one out
Additional example (above updated [0..-1]):
[9,8,7,6,5].injector {|x,y| x * y} #=> 136080
Ruby .inject outputs 15120
The starting index is important as it depends on your input.
class Array
def injector(input = nil)
if input.nil?
start = 1
num = self.first
else
start = 0
num = input
end
self[start..-1].each do |x|
num = yield(num, x)
end
return num
end
end
Using nil as the default is probably wrong, I should be able to pass nil in as the default memo.
class Array
def injector(memo = (i=1; first))
(i||0).upto(length-1) { |i| memo = yield memo, self[i] }
memo
end
end
[1,2,3,4,5].injector(1) { |sum, n| sum + n }
[1,2,3,4,5].injector(0) { |sum, n| sum + n }
[1,2,3,4,5].injector { |sum, n| sum + n }
[1,2,3].injector(2) { |product, n| product * n }
[1,2,3].injector(1) { |product, n| product * n }
[1,2,3].injector { |product, n| product * n }
['b', 'c', 'd'].injector('a') { |str, char| str + char } # => "abcd"
['b', 'c', 'd'].injector { |str, char| str + char } # => "bcd"
seen = []
[1].injector(nil) { |prev, crnt| seen << prev << crnt }
seen # => [nil, 1]

Is there a better way to write multiple OR statements in an if-statement?

def get_string(no_of_times)
1.upto(no_of_times) do
string_input = gets.chomp
count_holes(string_input)
end
end
def count_holes(word)
count = 0
word.each_char do |char|
if char == "A" || char == "D" || char == "O" || char == "P" || char == "Q" || char == "R"
count += 1
elsif char == "B"
count += 2
end
end
$arr_of_holes << count
end
test_cases = gets.chomp.to_i
$arr_of_holes = []
get_string(test_cases)
puts $arr_of_holes
Hi all. I do not like the long condition in if statement while iterating over each character. So i wanted to ask you all if there is a better way to do this in ruby.
Thanks
This can be done with a case selection, as multiple terms can be supplied to each when:
case char
when "A", "D", "O", "P", "Q", "R"
count += 1
when "B"
count += 2
end
You can use Array#include?:
if %q{A D O P Q R}.include? char
count += 1
elsif char == "B"
count += 2
end
Alternative way using Hash:
def count_holes(word)
holes = {
'A' => 1,
'D' => 1,
'O' => 1,
'P' => 1,
'Q' => 1,
'B' => 2,
}
count = word.chars.map { |char| holes.fetch(char, 0) }.inject :+
$arr_of_holes << count
end
Slightly more compact than nacyot's answer:
count += case char
when "B" then 2
when "A", "D", "O".."R" then 1
else 0
end
The else line may not be required if there is not such case.
One more way:
word = "BROADLY"
"ADOPQR".each_char.reduce(0) { |t,c| t + word.count(c) } + 2*word.count("B")
#=> 6

Remove duplicate text from multiple strings

I have:
a = "This is Product A with property B and propery C. Buy it now!"
b = "This is Product B with property X and propery Y. Buy it now!"
c = "This is Product C having no properties. Buy it now!"
I'm looking for an algorithm that can do:
> magic(a, b, c)
=> ['A with property B and propery C',
'B with property X and propery Y',
'C having no properties']
I have to find for duplicates in 1000+ texts. Super performance isn't a must, but would be nice.
-- Update
I'm looking for sequence of words. So if:
d = 'This is Product D with text engraving: "Buy". Buy it now!'
The first "Buy" should not be a duplicate. I'm guessing I have to use a threshold of n words following eachother in order to be seen as duplicate.
def common_prefix_length(*args)
first = args.shift
(0..first.size).find_index { |i| args.any? { |a| a[i] != first[i] } }
end
def magic(*args)
i = common_prefix_length(*args)
args = args.map { |a| a[i..-1].reverse }
i = common_prefix_length(*args)
args.map { |a| a[i..-1].reverse }
end
a = "This is Product A with property B and propery C. Buy it now!"
b = "This is Product B with property X and propery Y. Buy it now!"
c = "This is Product C having no properties. Buy it now!"
magic(a,b,c)
# => ["A with property B and propery C",
# "B with property X and propery Y",
# "C having no properties"]
Your data
sentences = [
"This is Product A with property B and propery C. Buy it now!",
"This is Product B with property X and propery Y. Buy it now!",
"This is Product C having no properties. Buy it now!"
]
Your magic
def magic(data)
prefix, postfix = 0, -1
data.map{ |d| d[prefix] }.uniq.compact.size == 1 && prefix += 1 or break while true
data.map{ |d| d[postfix] }.uniq.compact.size == 1 && prefix > -postfix && postfix -= 1 or break while true
data.map{ |d| d[prefix..postfix] }
end
Your output
magic(sentences)
#=> [
#=> "A with property B and propery C",
#=> "B with property X and propery Y",
#=> "C having no properties"
#=> ]
Or you can use loop instead of while true
def magic(data)
prefix, postfix = 0, -1
loop{ data.map{ |d| d[prefix] }.uniq.compact.size == 1 && prefix += 1 or break }
loop{ data.map{ |d| d[postfix] }.uniq.compact.size == 1 && prefix > -postfix && postfix -= 1 or break }
data.map{ |d| d[prefix..postfix] }
end
edit: this code has bugs. Just leaving my answer for reference and because i dont like it if people delete answers after being downvoted. Everyone makes mistakes :-)
I liked #falsetru's approach but felt the code was unnecessarily complex. Here's my attempt:
def common_prefix_length(strings)
i = 0
i += 1 while strings.map{|s| s[i] }.uniq.size == 1
i
end
def common_suffix_length(strings)
common_prefix_length(strings.map(&:reverse))
end
def uncommon_infixes(strings)
pl = common_prefix_length(strings)
sl = common_suffix_length(strings)
strings.map{|s| s[pl...-sl] }
end
As the OP may be concerned about performance, i did a quick benchmark:
require 'fruity'
require 'securerandom'
prefix = 'PREFIX '
suffix = ' SUFFIX'
test_data = Array.new(1000) do
prefix + SecureRandom.hex + suffix
end
def fl00r_meth(data)
prefix, postfix = 0, -1
data.map{ |d| d[prefix] }.uniq.size == 1 && prefix += 1 or break while true
data.map{ |d| d[postfix] }.uniq.size == 1 && postfix -= 1 or break while true
data.map{ |d| d[prefix..postfix] }
end
def falsetru_common_prefix_length(*args)
first = args.shift
(0..first.size).find_index { |i| args.any? { |a| a[i] != first[i] } }
end
def falsetru_meth(*args)
i = falsetru_common_prefix_length(*args)
args = args.map { |a| a[i..-1].reverse }
i = falsetru_common_prefix_length(*args)
args.map { |a| a[i..-1].reverse }
end
def padde_common_prefix_length(strings)
i = 0
i += 1 while strings.map{|s| s[i] }.uniq.size == 1
i
end
def padde_common_suffix_length(strings)
padde_common_prefix_length(strings.map(&:reverse))
end
def padde_meth(strings)
pl = padde_common_prefix_length(strings)
sl = padde_common_suffix_length(strings)
strings.map{|s| s[pl...-sl] }
end
compare do
fl00r do
fl00r_meth(test_data.dup)
end
falsetru do
falsetru_meth(*test_data.dup)
end
padde do
padde_meth(test_data.dup)
end
end
These are the results:
Running each test once. Test will take about 1 second.
fl00r is similar to padde
padde is faster than falsetru by 30.000000000000004% ± 10.0%

Filter arrays with bitmask or other array in Ruby

I was wondering if there was an Array method in Ruby that allows to filter an array based on another array or a bitmask.
Here is an example and a quick implementation for illustration purposes:
class Array
def filter(f)
res = []
if f.is_a? Integer
(0...self.size).each do |i|
res << self[i] unless f[i].nil? || 2**i & f == 0
end
else
(0...self.size).each do |i|
res << self[i] unless f[i].nil? || f[i] == 0
end
end
return res
end
end
Example:
%w(a b c).filter([1, 0, 1]) ==> ['a', 'c']
%w(a b c).filter(4) ==> ['c']
%w(a b c).filter([1]) ==> ['a']
Thanks!
In ruby 1.9 Fixnum#[] gives you bit values at a particular position, so it will work for both integers and arrays. I'm thinking something like this:
class Array
def filter f
select.with_index { |e,i| f[i] == 1 }
end
end
%w(a b c).filter([1, 0, 1]) #=> ['a', 'c']
%w(a b c).filter(4) #=> ['c']
%w(a b c).filter(5) #=> ['a', c']
%w(a b c).filter([1]) #=> ['a']
class Array
def filter(f)
f = f.to_s(2).split("").map(&:to_i) unless Array === f
reverse.reject.with_index{|_, i| f[-i].to_i.zero?}
end
end

Resources