I need to check if two strings have two consecutive characters in common.
I wrote this so far:
def substring_test(str1, str2)
return false if str1 == "" || str2 == ""
str1.downcase.scan(/.{1,2}/).each do |pair|
if str2.downcase.scan(/.{1,2}/).include? pair
return true
else
return false
end
end
end
But it doesn't work if I have:
"Home" and "om" because it truncates the string every two characters. What condition could I add so it works in these cases ? I could add a condition truncating the string each two characters after the fist character but it feels like there could be an easier way?
You could do something like
def substring_test(str1, str2)
str1.each_char.each_cons(2).any? { |pair| str2.include? (pair.join) }
end
Using the any? method will exit the loop as soon as it finds its first match.
Here is my variant:
def split_by_2_chars(string)
(0..string.size - 2).map { |i| string[i..i + 1].downcase }
end
def substring_test(s1, s2)
(split_by_2_chars(s1) & split_by_2_chars(s2)).any?
end
puts substring_test("Home", "om") #=> true
puts substring_test("Abcd", "defg") #=> false
Here is one way to do this. Use of Enumerable#any? helps us to keep the number of loop iterations to minimum as it will exit as soon as match is found.
s1 = "Home"
s2 = "Rome"
s1.downcase.split('').each_cons(2).any? do |c1|
s2.downcase.split('').each_cons(2).any?{|c2| c2 == c1}
end
#=> true
Related
I had a technical test for an entry-level job 2 days ago. It went well apart from the last assessment.
I will go over the assessment with the CTO tomorrow and was hoping I could get help to get my head around this one, so I do not sound clueless.
It was something like this:
Given string as an argument, give us palindrome method that would check if a palindrome of a minimum 3 characters can be created by removing 1 or 2 characters. The string has no special characters, numbers or whitespace basically only letters (eg: str = "abecbea")
If true, print letters removed, if false print "not possible"
"remove 1 or 2 characters" and "print letters removed" is giving me a headache legit
I have tried a lot of different things but for the last 2 days but i am completely stuck!
[EDIT]
Ideas i started with below
def is_palindrome(s)
s == s.reverse && s.length >= 3
end
def possible_palin_by_removing_one_char(s)
array_chars = s.chars
first_char = array_chars[0]
last_char = array_chars[-1]
while first_char < last_char
if first_char != last_char
first_char += 1
last_char -= 1
else
if is_palindrome
puts ????
else
puts "not possible"
end
end
end
end
or
def palindrome?(string)
deleted_chars = []
candidates = 0.upto(string.length-1).map do |index|
array_chars = string.chars
deleted_chars << array_chars.delete_at(index)
array_chars
end
if candidates.any? { |c| c.reverse == c } && string.length >= 3
puts "It is a palindrome with letters '#{deleted_chars.join(",")}' removed !"
# puts deleted_chars
else
if string.length <= 3
puts "String '#{string}' is too short"
else
puts "This is not possible to create palindrome with string '#{string}'"
end
end
end
palindrome?("abcecbae")
I would love someone to help me solve this one
Thanks heaps for your help
Put all chars in an Array (ar = str.chars)
Try all combinations which are 2 less than
the size of the array (so 5 in the example "abcecbae")(ar.combination(5))
Select all combinations which happen to be equal to their reverse
Map the result(s) back from arrays to strings
Similar for 1 removed char.
This task might be a little bit trickier without Ruby's rich standard library support.
Here is how I'd solve this task in the 1st iteration:
Check if the original string is a palindrome, return immediately if it is
Build a list of possible combinations of indices to remove
Iterate over this list, check if removing the chars at given indices makes our input string a palindrome
The trickiest part here is (2), but Enumerable#permutation almost does the job.
So, something like
def palindrome?(s)
s == s.reverse
end
def indices_to_remove(string)
ary = (0..string.size-1).to_a
# Permutations like [0,1] and [1,0] means the same in our context, so we need to remove this duplication.
(ary.map { |x| [x] } + ary.permutation(2).map(&:sort)).uniq
end
def remove_chars(str, *indices)
str.dup.tap do |s|
indices.sort.reverse_each { |i| s[i] = '' }
end
end
def find_palindrome(str)
return if palindrome?(str)
indices_to_remove(str).each do |indices|
new_str = remove_chars(str, *indices)
return "Letters removed: #{indices.inspect}; palindrome: '#{new_str}'" if palindrome?(new_str)
end
end
That's it. Let's see if i works:
pry(main)> find_palindrome("abecbea")
=> "Letters removed: [1, 3]; palindrome: 'aebea'"
It does. Adjust the filtering logic (check palindroms' size as needed) and output if necessary.
Thanks all for your help. With the help of a senior dev, we came up with the below solution
def palindrome_check(str)
length = str.length
# convert to array of chars
chars = str.chars
# check if palindrome by deleting 1 characters
length.times do |i|
char = chars.delete_at(i)
return "Letter '#{char}' has been removed from String '#{str}' to create palindrome" if chars == chars.reverse
# return the array to original condition
chars.insert(i, char)
end
# only do remove 2 characters check if length > 4, as otherwise removing would result in a string less than 3 characters
if length > 4
length.times do |i|
length.times do |j|
# avoid repeating the same checks
next if j <= i
# since j is always greater than i, remove j first to avoid affecting which character is at position i
char_two = chars.delete_at(j)
char_one = chars.delete_at(i)
return "Letters '#{[char_one, char_two].join(' and ')}' has been removed from String '#{str}' to create palindrome" if chars == chars.reverse
# return the array to original condition
chars.insert(i, char_one)
chars.insert(j, char_two)
end
end
end
return "'#{str}' can't be a Palindrome"
end
palindrome_check('string') # 'can't be a Palindrome'
palindrome_check('abcdcfba') # 'f'
palindrome_check('rqubyxyburx') # 'q, x'
# 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
I'm trying to get this to pass spec to verify if an argument is an anagram of another word, but it's just not happening.
I can get the string (starting with just one sting word) into an array, and whether it's one or multiple words,
It then iterates through the array over each word.
Using the If statement to compare if the sorted object is equal to the sorted argument.
Applied .join, since it came out one letter at a time in irb, but it's still not happening, with or without .join.
class String
define_method(:anagrams) do |check_word|
words = self.downcase
check_word = check_word.downcase
words_array = words.split(" ")
words_array.each do |word|
if (word.chars.sort) == (check_word.chars.sort)
true
else
false
end
end
end
end
Any ideas why it's broken?
words_array.each do |word|
if (word.chars.sort) == (check_word.chars.sort)
true
else
false
end
end
I'm assuming you want to return true if any words are anagrams. You're currently not explicitly returning.
Better Ruby syntax would be words_array.any? { |word| word.chars.sort == check_word.chars.sort) }
OR
words_array.each do |word|
return true if (word.chars.sort) == (check_word.chars.sort)
end
Here's another way to see if two words w1 and w2 are anagrams of each other:
def anagrams?(w1, w2)
w1.size == w2.size && w1.chars.difference(w2.chars).empty?
end
where Array#difference is how I defined it in my answer here.
Array#difference is similar to Array#-. The difference is illustrated in the following example:
a = [1,2,3,4,3,2,2,4]
b = [2,3,4,4,4]
a - b #=> [1]
a.difference b #=> [1, 3, 2, 2]
Let's try it:
anagrams?("tops", "stop") #=> true
anagrams?("tops", "stopz") #=> false
anagrams?("tops", "stopz") #=> false
anagrams?("tops", "sto") #=> false
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