The question is very simple and probably have thousand of answers, but i am looking for some magical ruby function.
Problem:
To determine whether a letter is upcase or not i.e belongs to A-Z.
Possible Solution:
array = ["A","B", ....., "Z"]
letter = "A"
is_upcase = array.include? letter
Please note that "1" is not an uppercase letter.
Is there any magical ruby function which solve the problem with less code?
You can use POSIX character classes:
/[[:lower:]]/ - Lowercase alphabetical character
/[[:upper:]]/ - Uppercase alphabetical
Example:
def which_case(letter)
case letter
when /[[:upper:]]/
:uppercase
when /[[:lower:]]/
:lowercase
else
:other
end
end
which_case('a') #=> :lowercase
which_case('ä') #=> :lowercase
which_case('A') #=> :uppercase
which_case('Ä') #=> :uppercase
which_case('1') #=> :other
Or with a simple if statement:
puts 'lowercase' if /[[:lower:]]/ =~ 'a'
#=> lowercase
Use ===
?> ('A'..'Z') === 'C'
=> true
>> ('A'..'Z') === 'c'
=> false
>> ('A'..'Z') === '1'
=> false
>> ('A'..'Z') === '&'
=> false
>> ('A'..'Z') === 'Z'
=> true
Also lacks support for umlauts, diacritcs etc. und needs ActiveSupport, but I like the syntax:
'A'.in?('A'..'Z') # => true
'a'.in?('A'..'Z') # => false
'A' =~ /[A-Z]/ #=> 0 (boolean true)
'a' =~ /[A-Z]/ #=> nil (boolean false)
def is_upcase? x
('A'..'Z').cover? x
end
Edit: .cover? is a new function in 1.9 that checks if value is in range by only checking the endpoints. In that way the computer does not need to convert it into an array and store it in memory, making it faster.
It is basically another way of writing x >= 'A' && x <= 'Z'
x >= 'A' && x <= 'Z'
There are several ways to check if the character is uppercase
# false
c = 'c'
p c=~/[A-Z]/
p c==c.upcase
p /[A-Z]/===c
p (?A..?Z)===c
p ?A<=c&&c<=?Z
p (?A..?Z).cover?c
p c=~/[[:upper:]]/
p /[[:upper:]]/===c
# true
C = 'C'
p C=~/[A-Z]/
p C==C.upcase
p /[A-Z]/===C
p (?A..?Z)===C
p ?A<=C&&C<=?Z
p (?A..?Z).cover?C
p C=~/[[:upper:]]/
p /[[:upper:]]/===C
=~ returns a nil or 0.
!!nil == false; !!0 == true.
P.S. Not all of them works in the same way
'.' == '.'.upcase => true but it's not a capital letter
If you need to check letters with diphthongs use
/[[:upper:]]/==='Ñ' => true as expected
In this case you can to add interested letters manually:
/[A-ZÑ]/==='Ñ' => true
Related
This function should take in two strings "daBcD" and "ABC". It is trying to create the string "b" from the letters in "a". You can only delete or capitalize letters, you cant change them. b will always contain all uppercase letters.
def abbreviation(a, b)
aArray = a.split('')
idx = 0
aArray.each do |char|
#print "char: #{char}\n"
#print "Before loops: #{aArray}\n"
if char.casecmp(b[idx]) == 0
char.upcase!
idx += 1
#print "char: #{char}\nArry: #{aArray}\n"
#print "idx: #{idx}\n siz: #{b.size}\n"
if idx == b.size
aArray.reject! {|i| i == 'delete'}
aArray.slice!(b.size)
break
end
else
aArray[aArray.index(char)] = 'delete'
#print "deleted, now is: #{aArray}\n"
end
end
res = aArray.join('')
if res == b
return 'YES'
else
return 'NO'
end
end
This works for a couple test cases, but fails most of them. Can someone describe a better approach?
I have assumed the problem is to determine whether the characters in b appear in a (case indifferent), in the same order as in b, but not necessarily contiguous in a (see the second example below). If they do I return an array of the indices at which they appear in a. If there is no match, nil is returned.
def doit(a, b)
m = a.match(Regexp.new(b.each_char.map { |c| "(#{c})" }.join('.*'),
Regexp::IGNORECASE))
return nil if m.nil?
(1..b.size).map { |i| m.begin(i) }
end
doit "daBcD", "ABC"
#=> [1, 2, 3]
doit "daXBDecf", "ABC"
#=> [1, 3, 6]
doit "dacBD", "ABC"
#=> nil
For the first example the regular expression is as follows.
Regexp.new("ABC".each_char.map { |c| "(#{c})" }.join('.*'), Regexp::IGNORECASE)
#=> /(A).*(B).*(C)/i
The absolutely easiest way is via regular expression:
def abbreviation(a, b)
re = Regexp.new(b.each_char.map(&Regexp.method(:quote)).join('.*'), Regexp::IGNORECASE)
!!re.match(a)
end
abbreviation("daBcD", "ABC")
# => true
abbreviation("daCbD", "ABC")
# => false
For the input ABC, we'll construct a regular expression /A.*B.*C/i, then test the other string against it. The .* construct will account for "deletion"; the IGNORECASE option for "capitalisation".
EDIT: If the problem is further constrained that only lowercase letters can be deleted, as suggested by the comments,
def abbreviation(a, b)
# (b is uppercase only)
re_pat = b.each_char.map { |c| "[#{c}#{c.downcase}]"}.join('[[:lower:]]*')
re = Regexp.new(re_pat)
!!re.match(a)
end
p abbreviation("daBcD", "ABC") # => true
p abbreviation("daBdcD", "ABC") # => true
p abbreviation("daBDcD", "ABC") # => false
I want to check that in string are small and big letters
a.to_s.each_byte do |s|
if s >= 65 && s <= 90
big = true
elsif s >= 95 && s <= 122
small = true
end
end
Is it possible to write this in shortest form?
str = a.to_s
big = true if str =~ /[A-Z]/
small = true if str =~ /[a-z]/
If you stick to the English language then you can try something like this:
if a.to_s =~ /[a-z]/ and a.to_s =~ /[A-Z]/
puts 'f'
else
puts 'p'
end
But if your code needs to be able to also handle the alphabets of other languages then you should use:
if a.to_s =~ /[[:lower:]]/ and a.to_s =~ /[[:upper:]]/
puts 'f'
else
puts 'p'
end
Although I prefer the regex solutins from #axiac and #cmramseyerthis is a way your original implementementation could be made to work:
[65...90,95...122].all? do |range|
a.to_s.each_byte.any? { |byte| range.include? byte }
end
In pseudocode:
for all of the ranges, do any of the bytes fall within the range?
Here is a candidate for the "shortest form". If both lowercase and uppercase letters are present there is either an uppercase letter following a lowercase letter or vice-versa.
str.match? /\p{Ll}\p{Lu}|\p{Lu}\p{Ll}/
# Write a method that takes a string in and returns true if the letter
# "z" appears within three letters **after** an "a". You may assume
# that the string contains only lowercase letters.
I came up with this, which seems logical, but for some reason if "z" comes directly after "a", it returns false. Can someone explain why?
def nearby_az(string)
i = 0
if string[i] == "a" && string[i+1] == "z"
return true
elsif string[i] == "a" && string[i+2] == "z"
return true
elsif string[i] == "a" && string[i+3] == "z"
return true
else return false
end
i += 1
end
#shivram has given the reason for your problem. Here are a couple of ways to do it.
Problem is tailor-made for a regular expression
r = /
a # match "a"
.{,2} # match any n characters where 0 <= n <= 2
z # match "z"
/x # extended/free-spacing regex definition mode
!!("wwwaeezdddddd" =~ r) #=> true
!!("wwwaeeezdddddd" =~ r) #=> false
You would normally see this regular expression written
/a.{0,2}z/
but extended mode allows you to document each of its elements. That's not important here but is useful when the regex is complex.
The Ruby trick !!
!! is used to convert truthy values (all but false and nil) to true and falsy values (false or nil) to false:
!!("wwwaeezdddddd" =~ r)
#=> !(!("wwwaeezdddddd" =~ r))
#=> !(!3)
#=> !false
#=> true
!!("wwwaeezdddddd" =~ r)
#=> !(!("wwwaeeezdddddd" =~ r))
#=> !(!nil)
#=> !true
#=> false
but !! is not really necessary, since
puts "hi" if 3 #=> "hi"
puts "hi" if nil #=>
Some don't like !!, arguing that
<condition> ? true : false
is more clear.
A non-regex solution
def z_within_4_of_a?(str)
(str.size-3).times.find { |i| str[i]=="a" && str[i+1,3].include?("z") } ? true : false
end
z_within_4_of_a?("wwwaeezdddddd")
#=> true
z_within_4_of_a?("wwwaeeezdddddd")
#=> false
This uses the methods Fixnum#times, Enumerable#find and String#include? (and String#size of course).
Your solution is incorrect. You are considering only the case where String starts with a (with i = 0 at the start of your method). I can see you are incrementing i at the end, but its of no use as its not in a loop.
I can think of a solution as to find the index of a in string, then take substring from that index + 3 and look for z. Something like:
s = "wwwaeezdddddd"
s[s.index("a")..s.index("a")+3]
#=> "aeez"
s[s.index("a")..s.index("a")+3] =~ /z/ # checking if z is present
#=> 3
If a can occur more than once in input String, you need to find all indices of a and run the above logic in a loop. Something like:
s = "wwwaesezddddddaz"
indexes = (0 ... s.length).find_all { |i| s[i,1] == 'a' }
#=> [3, 14]
indexes.each { |i| break if #is_present = s[i..i+3] =~ /z/ }
#is_present
#=> 1
Let’s implement the FSM ourselves :)
input = "wwwaeezdddddd"
!(0...input.length).each do |idx|
next unless input[idx] == 'a' # skip unrelated symbols
current = (idx..[idx + 3, input.length - 1].min).any? do |i|
input[i] == 'z' # return true if there is 'z'
end
# since `each` returns truthy (range itself),
# in case of success we return falsey and negate
break false if current
end
#⇒ true
Please note, that the above implementation is O(length(input)) and does not use any built-in ruby helpers, it is just iterating a string char by char.
While the regexp solution is the most elegant, here is one for completion, which is more in spirit to your original attempt:
def nearby_az(string)
!!(apos = string.index('a') and string[apos,3].index('z'))
end
This is the question's prompt:
Write a method that takes a string and returns true if the letter
"z" appears within three letters after an "a". You may assume
that the string contains only lowercase letters.
I'm trying to use the ternary operator, and want to include the match or count methods. Any idea on how I can find the number of characters between "a" and "z" or the simplest way to solve this?
def nearby_az(string)
string.count <= 3 ? true : false
end
Regex would be a good way to solve this problem.
You can use online regex testers to experiment with different regexes, inputs and outputs.
The first solution that comes to my mind is to come up with a pattern for each possible correct input:
az
a[a-z]z
a[a-z][a-z]z
Which means:
Match the string "az"
Match a string with "a" and then a character from "a" to "z" and then a "z" character
Match a string with an "a" and then 2 characters from "a" to "z" and then a "z"
and then combine them with the 'or' operator (|)
az|a[a-z]z|a[a-z][a-z]z
Which means match on all three of those conditions.
A link to this example is here.
Doing it this way is a bit verbose so it can be improved by expressing this in a more compact way:
a[a-z]{0,2}z
This means:
Match an "a" then match a character from "a" to "z" 0, 1 or 2 times and then match a "z"
A link to this example is here
You use the method on ruby strings called match which takes in a regex object and then check the boolean return value.
Edit:
The ruby code would look something like this:
def nearby_az(string)
return string.match(/a[a-z]{0,2}z/) != nil
end
string.match() returns an object that you can query to get information about the match. If there is no match, string.match() will return nil.
!!("fjeioaeiz" =~ /a.{,2}z/) #=> true
!!("fjeioaz" =~ /a.{,2}z/) #=> true
!!("fjeioasbdz" =~ /a.{,2}z/) #=> false
Look, Ma! No regex!
def a_upto_4_z(str)
str.each_char.with_index.any? { |c,i| c == ?a && str[i+1,3].include?(?z) }
end
a_upto_4_z "rvaxxzo" #=> true
a_upto_4_z "rvaxxxzo" #=> false
a_upto_4_z "rvaxzo" #=> true
a_upto_4_z "rvazo" #=> true
a_upto_4_z "rvzao" #=> false
Edit: #Stefan makes a good point. Let's do it this way:
def mind_the_gap(str, max_gap=2)
gap = max_gap + 1 # or larger
str.each_char do |c|
case c
when ?z
return true if gap <= max_gap
when ?a
gap = 0
else
gap += 1
end
end
false
end
mind_the_gap "rvaxxzo" #=> true
mind_the_gap "rvaxxxzo" #=> false
mind_the_gap "rvaxzo" #=> true
mind_the_gap "rvazo" #=> true
mind_the_gap "rvzao" #=> false
Note it is not necessary to increment gap when c == ?z and gap > max_gap.
In writing a method to compare 2 words, how can I check to see if the words are only 1 letter different? I'm assuming words are same length and order of letters doesnt matter (see "cobra","bravo").
def one_letter_apart?(word1, word2)
I expect the results below:
one_letter_apart?("abra","abro") == true
one_letter_apart?("cobra","bravo") == true
one_letter_apart?("bravo","tabby") == false
one_letter_apart?("abc","cab") == false
I have tried a few ways of manipulating them (splitting,sorting,then setting equal and adding to new array, then counting), but none so far have worked. Any ideas are greatly appreciated.
This one makes use of the fact that String#sub substitutes only the first thing it finds.
def one_different_char?(str, other)
other_str = other.dup
str.chars{|char| other_str.sub!(char, '')} #sub! just replaces just one occurence of char
other_str.size == 1
end
test_set = [["abra","abro"],["cobra","bravo"],["bravo","tabby"],["abc","cab"]]
test_set.each{|first, second| puts one_different_char?(first, second) }
#true
#true
#false
#false
Check Levenshtein Distance
You want the Levenstein distance. For example, using the text gem:
require 'text'
def one_letter_apart? string1, string2
Text::Levenshtein.distance(string1, string2).eql? 1
end
one_letter_apart? "abra", "abro"
# => true
one_letter_apart? "cobra", "bravo"
# => false
def one_letter_apart?(s1, s2)
return false if s1.length != s2.length
a2 = s2.chars.to_a
s1.chars.each do |c|
if i = a2.index(c)
a2.delete_at(i)
end
end
a2.length == 1
end
one_letter_apart?("abra","abro") == true
# => true
one_letter_apart?("cobra","bravo") == true
# => true
one_letter_apart?("bravo","tabby") == false
# => true
one_letter_apart?("abc","cab") == false
# => true
Update: To answer your question of how it works: This is the exact same general algorithm as steenslag's, but I didn't think of using String#sub! to do the removal, so I converted to arrays and used a combination of index and delete_at to remove the first occurrence of the given character. The naïve approach is a2.delete_at(a2.index(c)), but if the character c doesn't exist in a2, then index returns nil, which is an invalid input for delete_at. The workaround is to only call delete_at if index returns something non-nil, which is what I've done. i is declared and set to a2.index(c), and the value of that assignment is evaluated by if. It's the same as:
i = a2.index(c)
if i
# ...
I much prefer steenslag's approach and would have done the exact same thing if I'd thought of String#sub!.
This function returns true if two strings have equal lengths and only one different letter while all the other letters are in the same positions:
def one_letter_apart? string1, string2
return false if string1.size != string2.size
found = false
(0...string1.size).each do |i|
next if string1[i] == string1[i]
return false if found # if found is already true, and we found another difference, then result is false.
found = true # We found the first difference.
end
found # True if only one difference was found.
end
This function handles letters in wrong positions (like "cobra" and "bravo") as well:
def one_letter_apart? string1, string2
letters1 = string1.chars.each_with_object(Hash.new(0)) { |c, h| h[c] += 1 }
letters2 = string2.chars.each_with_object(Hash.new(0)) { |c, h| h[c] -= 1 }
diff = letters1.merge(letters2) { |key, count1, count2| count1 + count2 }
return diff.values.select { |v| v != 0 } .sort == [-1, 1]
end