Replace multiples characters at same time in ruby - ruby

Are there a better way to wrote the same code below?
I am looking for a clean and minimal code.
val.gsub!('A', 'Q')
val.gsub!('B', 'W')
val.gsub!('C', 'E')
val.gsub!('D', 'R')
val.gsub!('E', 'T')
val.gsub!('F', 'Y')

Use tr, it's purpose-built for the problem you're describing:
> val = "ASDFGHJKL"
=> "ASDFGHJKL"
> val.tr("ABCDEF", "QWERTY")
=> "QSRYGHJKL"
Without using any other methods than the ones you already know about, you could build a key/value mapping, and then iterate over the pairs:
{ 'A' => 'Q', 'B' => 'W', 'C' => 'E' ...}.each { |x,y| val.gsub(x, y) }

Related

Rubocop Offense: Use the return of the conditional for variable assignment and comparison

My Rubocop offense is telling me I need to 'Use the return of the conditional for variable assignment and comparison'
While I tried fixing it, it gave me another offense that my 'method line is too long'.
I've tried refactoring to another method but my code broke.
How do I shorten or refactor this code ?
HSH = { 'a' => 'z', 'b' => 'y', 'c' => 'x', 'd' => 'w', 'e' => 'v', \
'f' => 'u', 'g' => 't', 'h' => 's', \
'i' => 'r', 'j' => 'q', 'k' => 'p', 'l' => 'o', 'm' => 'n' }.freeze
def encoder(str)
encoded_string = ''
str.chars.each do |char|
encoded_string = if HSH.key?(char) then encoded_string += HSH[char]
elsif HSH.invert.key?(char) then encoded_string += HSH.invert[char]
else encoded_string += char
end
end
encoded_string
end
When I ran my test suite, everything was ok.
But the rubocop offense gave me method line is too long.
No hash:
ALPHABET = ("a".."z").to_a.join
def encoder(str)
str.tr(ALPHABET, ALPHABET.reverse)
end
HSH = {
'a' => 'z', 'b' => 'y', 'c' => 'x',
'd' => 'w', 'e' => 'v', 'f' => 'u',
'g' => 't', 'h' => 's', 'i' => 'r',
'j' => 'q', 'k' => 'p', 'l' => 'o',
'm' => 'n'
}.freeze
def encoder(str)
str.chars.map { |char| HSH[char] || HSH.invert[char] || char }.join
end
NB! Do not use this answer! the proper way to go is provided by #steenslag here.
Use the hash with all the letters explicitly mapped and the default proc:
HSH =
(?a..?z).zip((?a..?z).to_a.reverse).to_h.
tap { |h| h.default_proc = ->(_, k) { k }}.
freeze
def encoder(str)
str.chars.map(&HSH.method(:[])).join
end
Several of us suggested better ways to implement your encoder method. But all of us (myself included) didn't actually answer your question, or see a central problem in your code:
My Rubocop offense is telling me I need to 'Use the return of the conditional for variable assignment and comparison'
While I tried fixing it, it gave me another offense that my 'method line is too long'.
encoded_string = if HSH.key?(char) then encoded_string += HSH[char]
elsif HSH.invert.key?(char) then encoded_string += HSH.invert[char]
else encoded_string += char
end
You sort of followed Rubocop's advice...you assigned a conditional result to a value...but I think you missed the mark. I'm not even sure that's the conditional result it was referring to. I'm assuming you added the encoded_string = ... assignment.
That is a uselesss assignment, as you already appended the character to encoded_string within the if blocks. You don't have to assign it again.
Rewinding to what I'm guessing was your version 1 code, here's a more efficient way to follow Rubocop's advice. Don't make an assignment within each condition...only do one assignment, with the result of the conditionals:
encoded_string += if HSH.key?(char) then HSH[char]
elsif HSH.invert.key?(char) then HSH.invert[char]
else char
end
That ends up with less code, and matches your original coding style and approach. It might even make Rubocop happy. The next step to lovely code would be to eliminate the excessive key? tests:
encoded_string += if HSH[char] then HSH[char]
elsif HSH.invert[char] then HSH.invert[char]
else char
end
From there, it's a small step to eliminate the if/elsif blocks with ||'s. And while we're at it, we'll change += to << to avoid, "produce a gazillion of intermediate unnecessary String instances." (Thanks for the suggestion, #Aleksei Matiushkin )
encoded_string << HSH[char] || HSH.invert[char] || char
For your approach to this problem, that's about the minimal level of conciseness and readability that production code should strive for. Anyone can understand it without thinking real hard or hitting Stack Overflow.
As with #Steenslag's answer, there's no need to convert the string to an array, map each element of the array and join the result back into a string. The following is defined to be efficient, by avoiding the need for a linear search for each letter.
def encode_decode(str)
rng = 'a'..'z'
str.gsub(/./) { |c| rng.cover?(c) ? (219-c.ord).chr : c }
end
plain_text = "The launch code is 'Bal3De8Rd0asH'."
#=> "Tsv ozfmxs xlwv rh 'Bzo3Dv8Rw0zhH'."
coded_text = encode_decode(plain_text)
#=> "Tsv ozfmxs xlwv rh 'Bzo3Dv8Rw0zhH'."
encode_decode(coded_text)
#=> "The launch code is 'Bal3De8Rd0asH'."
I would go ahead and expand your hash to all 26 letters, so you can avoid the inverse lookup. That simplifies your code by removing one case, which will may appease Rubocop... But more importantly, you'll be using the hash index for greater efficiency and performance. Inverse hash lookups are expensive, as it must read (up to) every value.
Consider encoding "1+2”. It will do three quick index scans, then three full array scans, just to return the original string.
With a fully populated hash, it would only take three quick scans.
Here is your original code with minimal changes to meet your goal: (There are shorter ways to do this (hint: tr or map), but shorter is not as important as easy and comfortable to the programmer using the code.)
translation = {
'a' => 'z', 'b' => 'y', 'c' => 'x', 'd' => 'w', 'e' => 'v', 'f' => 'u', 'g' => 't',
'h' => 's', 'i' => 'r', 'j' => 'q', 'k' => 'p', 'l' => 'o', 'm' => 'n', 'n' => 'm',
'o' => 'l', 'p' => 'k', 'q' => 'j', 'r' => 'i', 's' => 'h', 't' => 'g', 'u' => 'f',
'v' => 'e', 'w' => 'd', 'x' => 'c', 'y' => 'b', 'z' => 'a'
}.freeze
def encoder(str)
encoded_string = ''
str.chars.each do |char|
encoded_string << translation[char] || char
end
encoded_string
end
You might even consider expanding the hash to upper and lower case letters, or even all 256 char values, depending on what problem you're solving. But let's agree to ignore Unicode chars!
Back to Rubocop... The easiest, sure-fire solution to any sort of "too long/too complex" warning is to pull code out to a new method. Write def charswap and use that as the body of your loop. That will make writing tests easier, to boot. But, by expanding your translation array to all 26 letters, the code gets so simple that refactoring isn't really needed.

Refactor multiple gsub statements into 1

Trying to refactor this into one line to get all vowels in a string to be capitalized. I tried using a hash, but that failed. Still too new at Ruby to know of any alternatives, despite my best efforts to look it up. something like.... str.gsub!(/aeiou/
def LetterChanges(str)
str.gsub!(/a/, "A") if str.include? "a"
str.gsub!(/e/, "E") if str.include? "e"
str.gsub!(/i/, "I") if str.include? "i"
str.gsub!(/o/, "O") if str.include? "o"
str.gsub!(/u/, "U") if str.include? "u"
puts str
end
The best way is
str.tr('aeiou', 'AEIOU')
String#tr
Returns a copy of str with the characters in from_str replaced by the corresponding characters in to_str. If to_str is shorter than from_str, it is padded with its last character in order to maintain the correspondence.
You can use gsub's second parameter, which is a replacement hash:
str.gsub!(/[aeiou]/, 'a' => 'A', 'e' => 'E', 'i' => 'I', 'o' => 'O', 'u' => 'U')
or, alternatively, pass a block:
str.gsub!(/[aeiou]/, &:upcase)
Both will return:
'this is a test'.gsub!(/[aeiou]/, 'a' => 'A', 'e' => 'E', 'i' => 'I', 'o' => 'O', 'u' => 'U')
# => "thIs Is A tEst"
'this is a test'.gsub!(/[aeiou]/, &:upcase)
# => "thIs Is A tEst"

How do you define element uniqueness by multiple keys/attributes?

I have queried my database which gave me an array of hashes, where the keys in the hash are the column names. I want to keep only the hashes(array elements), that are unique according to multiple (3 columns). I have tried:
array.uniq { |item| item[:col1], item[:col2], item[:col3] }
as well as
array = array.inject([{}]) do |res, item|
if !res.any? { |h| h[:col1] == item[:col1] &&
h[:col2] == item[:col2] &&
h[:col3] == item[:col3] }
res << item
end
end
Does anyone have any ideas as to what's wrong or another way of going about this?
Thanks
It's unclear to me what you're asking for. My best guess is that given the array of single-association Hashes:
array = [{:col1 => 'aaa'}, {:col2 => 'bbb'}, {:col3 => 'aaa'}]
You'd like to have only one Hash per hash value; that is, remove the last Hash because both it and the first one have 'aaa' as their value. If so, then this:
array.uniq{|item| item.values.first}
# => [{:col1=>"aaa"}, {:col2=>"bbb"}]
Does what you want.
The other possibility I'm imagining is that given an array like this:
array2 = [{:col1 => 'a', :col2 => 'b', :col3 => 'c', :col4 => 'x'},
{:col1 => 'd', :col2 => 'b', :col3 => 'c', :col4 => 'y'},
{:col1 => 'a', :col2 => 'b', :col3 => 'c', :col4 => 'z'}]
You'd like to exclude the last Hash for having the same values for :col1, :col2, and :col3 as the first Hash. If so, then this:
array2.uniq{|item| [item[:col1], item[:col2], item[:col3]]}
# => [{:col1=>"a", :col2=>"b", :col3=>"c", :col4=>"x"},
# {:col1=>"d", :col2=>"b", :col3=>"c", :col4=>"y"}]
Does what you want.
If neither of those guesses are really want you're looking for, you'll need to clarify what you're asking for, preferably including some sample input and desired output.
I'll also point out that it's quite possible that you can accomplish what you want at the database query level, depending on many factors not presented.
If the no. of column is constant i.e. 3 you are better off creating a 3 level hash something like below
where whatever value you want to store is at 3rd level.
out_hash = Hash.new
array.each do |value|
if value[:col1].nil?
out_hash[value[:col1]] = Hash.new
out_hash[value[:col1]][value[:col2]] = Hash.new
out_hash[value[:col1]][value[:col2]][value[:col3]] = value
else if value[:col1][:col2].nil?
out_hash[value[:col1]][value[:col2]] = Hash.new
out_hash[value[:col1]][value[:col2]][value[:col3]] = value
else if value[:col1][:col2][:col3].nil?
out_hash[value[:col1]][value[:col2]][value[:col3]] = value
end
end
I have not tested the code above its for giving you a idea...

Local Variables at Intermediate Steps

Hi I was wondering if someone could explain to me why the map function written in the below code is written in the way its written. Specifically why do we need to do
results = letters.map do |letter| encrypted_letter = encrypt_letter(letter)
instead of just doing
results = letters.map do |letter| encrypt_letter(letter)
class Encryptor
def cipher
{"a" => "n", "b" => "o", 'c' => 'p', 'd' => 'q',
'e' => 'r', 'f' => 's', 'g' => 't', 'h' => 'u',
'i' => 'v', 'j' => 'w', 'k' => 'x', 'l' => 'y',
'm' => 'z', 'n' => 'a', 'o' => 'b', 'p' => 'c',
'q' => 'd', 'r' => 'e', 's' => 'f', 't' => 'g',
'u' => 'h', 'v' => 'i', 'w' => 'j', 'x' => 'k',
'y' => 'l', 'z' => 'm'}
end
def encrypt_letter(letter)
lowercase_letter = letter.downcase
cipher[lowercase_letter]
end
def encrypt(string)
letters = string.split("")
results = letters.map do |letter|
encrypted_letter = encrypt_letter(letter)
end
results.join
end
def decrypt_letter(letter)
lowercase_letter = letter.downcase
cipher.key(lowercase_letter)
end
def decrypt(string)
letters = string.split("")
results = letters.map do |letter|
decrypted_letter = decrypt_letter(letter)
end
results.join
end
end
No reason; the variable is immediately discarded.
I'd argue it's misleading and uncommunicative on top of it.
Most of the code seems a bit verbose, for example:
def encrypt(string)
letters = string.split("")
results = letters.map do |letter|
encrypted_letter = encrypt_letter(letter)
end
results.join
end
IMO this would be more Ruby-esque as something closer to:
def encrypt(str)
str.chars.collect { |c| encrypt(c) }.join
end
It could be tighter than that, or written in other ways, although some of it is a matter of preference. For example, each_with_object could be used with the shovel operator, but that's less "functional".
(I prefer collect over map when collecting; a preference I find more communicative, if longer.)
Spreading functionality over more lines doesn't make things readable, but it depends on context. People new to Ruby or method chaining might be confused by the (IMO more canonical) one-liner.
As others say, it has no reason. It is obviously a code written by a beginner. In addition to Dave Newton's point, it is a bad habit to define a constant hash as a method cipher. Each time that code is called, a new hash is created. And this has to be done for each letter. That is a huge waste of resource.
Using the hash, you can simply do this:
h = {"a" => "n", "b" => "o", 'c' => 'p', 'd' => 'q',
'e' => 'r', 'f' => 's', 'g' => 't', 'h' => 'u',
'i' => 'v', 'j' => 'w', 'k' => 'x', 'l' => 'y',
'm' => 'z', 'n' => 'a', 'o' => 'b', 'p' => 'c',
'q' => 'd', 'r' => 'e', 's' => 'f', 't' => 'g',
'u' => 'h', 'v' => 'i', 'w' => 'j', 'x' => 'k',
'y' => 'l', 'z' => 'm'}
h.default_proc = ->x{x}
"hello world".gsub(/./, h)
# => "uryyb jbeyq"
But I would rather go with this:
from = "abcdefghijklmnopqrstuvwxyz"
to = "nopqrstuvwxyzabcdefghijklm"
"hello world".tr(from, to)
# => "uryyb jbeyq"
There is no functional reason for it. Sometimes programmers feel more comfortable having an explicit variable destination for their results. Maybe this is one of those cases. Same with the decrypted_letter case.

Conditional key/value in a ruby hash

Is there a nice (one line) way of writing a hash in ruby with some entry only there if a condition is fulfilled? I thought of
{:a => 'a', :b => ('b' if condition)}
But that leaves :b == nil if the condition is not fulfilled. I realize this could be done easily in two lines or so, but it would be much nicer in one line (e.g. when passing the hash to a function).
Am I missing (yet) another one of ruby's amazing features here? ;)
UPDATE Ruby 2.4+
Since ruby 2.4.0, you can use the compact method:
{ a: 'a', b: ('b' if cond) }.compact
Original answer (Ruby 1.9.2)
You could first create the hash with key => nil for when the condition is not met, and then delete those pairs where the value is nil. For example:
{ :a => 'a', :b => ('b' if cond) }.delete_if{ |k,v| v.nil? }
yields, for cond == true:
{:b=>"b", :a=>"a"}
and for cond == false
{:a=>"a"}
UPDATE for ruby 1.9.3
This is equivalent - a bit more concise and in ruby 1.9.3 notation:
{ a: 'a', b: ('b' if cond) }.reject{ |k,v| v.nil? }
From Ruby 1.9+, if you want to build a hash based on conditionals you can use tap, which is my new favourite thing. This breaks it onto multiple lines but is more readable IMHO:
{}.tap do |my_hash|
my_hash[:a] = 'a'
my_hash[:b] = 'b' if condition
end
>= Ruby 2.4:
{a: 'asd', b: nil}.compact
=> {:a=>"asd"}
Interested in seeing other answers, but this is the best I can think up of for a one-liner (I'm also notoriously bad at one-liners :P)
{:a => 'a'}.merge( condition ? {:b => 'b'} : {} )
There's a lot of clever solutions in here, but IMO the simplest and therefore best approach is
hash = { a: 'a', b: 'b' }
hash[:c] = 'c' if condition
It goes against the OP's request of doing it in two lines, but really so do the other answers that only appear to be one-liners. Let's face it, this is the most trivial solution and it's easy to read.
In Ruby 2.0 there is a double-splat operator (**) for hashes (and keyword parameters) by analogy to the old splat operator (*) for arrays (and positional parameters). So you could say:
{a: 'b', **(condition ? {b: 'b'} : {})}
Hash[:a, 'a', *([:b, 'b'] if condition1), *([:c, 'c'] if condition2)]
This relies on the fact that *nil expands to vacuity in ruby 1.9. In ruby 1.8, you might need to do:
Hash[:a, 'a', *(condition1 ? [:b, 'b'] : []), *(condition2 ? [:c, 'c'] : [])]
or
Hash[:a, 'a', *([:b, 'b'] if condition1).to_a, *([:c, 'c'] if condition2).to_a]
If you have multiple conditions and logic that others will need to understand later then I suggest this is not a good candidate for a 1 liner. It would make more sense to properly create your hash based on the required logic.
This one is nice for multiple conditionals.
(
hash = {:a => 'a'}.tap {|h|
h.store( *[(:b if condition_b), 'b'] )
h.store( *[(:c if condition_c), 'c'] )
}
).delete(nil)
Note that I chose nil as the "garbage" key, which gets deleted when you're done. If you ever need to store a real value with a nil key, just change the store conditionals to something like:
(condition_b ? :b : garbage_key)
then delete(garbage_key) at the end.
This solution will also keep existing nil values intact, e.g. if you had :a => nil in the original hash, it won't be deleted.
My one-liner solution:
{:a => 'a'}.tap { |h| h.merge!(:b => 'b') if condition }
hash, hash_new = {:a => ['a', true], :b => ['b', false]}, {}
hash.each_pair{|k,v| hash_new[k] = v[1] ? v : nil }
puts hash_new
eval("{:a => 'a' #{', :b => \'b\'' if condition }}")
or even
eval("{#{[":a => 'a'", (":b=>'b'" if ax)].compact.join(',')}}")
for more simple add conditions

Resources