How to compare strings for 3/4 matches - ruby

Need to compare numbers in an array to 'winning' number. But I have to see if 3 out of 4 match. eg "1234" is my number and winning=["4356","8312","4820","7623"] . In this case "8312" should alert a win because they have 1 2&3 in common. I have to define the numbers in a unit test, then write a function in a separate file then pass that function back into the unit test. Any help would be greatly appreciated. I already wrote a function and test comparing for an exact match and dont have any idea on what next step to take.
function_file
def match(my_num,arr)
matches = []
arr.each_with_index do |v,i|
if my_num == v
matches << my_num
end
end
matches
end
test_file
require "minitest/autorun"
require_relative "close_but_no_func.rb"
class TestWinningNumbers < Minitest::Test
def test_1_equals_1
assert_equal(10-5, 3+2)
end
def test_winning_num
my_num = "1134"
arr=["6028","2088","3058","3476","8740","1134"]
assert_equal(["1134"], match(my_num, arr))
end
end

So let us divide this problem into two seperate problems.
You want a function that counts the number of matching characters.
Then you want to collect these strings together, if they have enough matching characters.
You could for example write a function that checks how many characters of the both strings match.
def count_matching_chars(str1,str2)
# counts how many characters of str1 appear in str2
matching_chars_count = 0
str1.each_char do |char|
matching_chars_count += 1 if str2.include?(char)
end
matching_chars_count
end
puts count_matching_chars("1234", "1134") => 3
puts count_matching_chars("1111", "1134") => 4
puts count_matching_chars("1234", "1111") => 1
This one here ignores the positioning, it just checks, how many of the characters of str1 match one character of str2.
Now you can easily collect these numbers in an array.
def matches(my_num, arr)
result = []
arr.each do |num|
result << arr if count_matching_chars(my_num,num) >= 3
end
result
end
You can write both functions in a more compact way, by using enumerator functions like count and select:
def count_matching_chars(str1,str2)
str1.each_char.count do |char|
str2.include?(char)
end
end
def matches(my_num, arr)
arr.select do |num|
return true if count_matching_chars(num,my_num) >= 3
end
end
Or you then combine everything into one function
def matches(my_num, arr)
arr.select do |num|
true if my_num.each_char.count { |char| num.include?(char)} >= 3
end
end
Now if you just want to check, if it is a winning number. You just return true as soon as you find a match:
def winning_number?(my_num, arr)
arr.select do |num|
return true if my_num.each_char.count { |char| num.include?(char)} >= 3
end
end

Related

Ruby - Remove 1 or 2 character from a string to make it a palindrome

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'

calling the method 'count' returns wrong number of arguments

I have a method which takes two parameters. An integer for (max_length) and a string for (text). If the amount of characters per word in the text is >= max_length we remove that word from the array. At the end we count the remaining words in the array.
My method works well until text.count where I encounter 'wrong number of arguments, given 0 expected 1+'
I know this is because we haven't passed any arguments to text.count but I don't want to pass any in as I only want to count the remaining number of words left in the array.
However, if I performed a simple example of
x = ["This", "Will", "Work"]
x.count => 3
Why can't I use this example of count inside my block?
What am i doing wrong?
def timed_reading(max_length, text)
text.split.delete_if do |y|
y.length >= max_length
text.count
end
end
I think this is what you were trying to do
def timed_reading(max_length, text)
text.split.delete_if { |y| y.length >= max_length }.count
end
You could just count words with length less than the max anyway
text.split.count { |y| y.length < max_length }
There's no need to delete words if all you return is the count. You can simply use count with a block:
def timed_reading(max_length, text)
text.split.count{|w| w.length < max_length}
end
You are calling count on the string text and not on the array. You need to rearrange your code so that you are calling count on the result of your delete_if call. Something like this:
def timed_reading(max_length, text)
short_words = text.split.delete_if do |y|
y.length >= max_length
end
short_words.count
end

String compressor (Ruby)

Here is my code in ruby for a word compression.
For any given word (e.g. abbbcca) the compressed word/output should be in the format as "letter+repetition" (for above example, output: a1b3c2a1).
Here I'm so close to the completion but my result isn't in the expected format. It's counting the whole letters in string.chars.each thus resulting output as a2b3c2a2.
Any help?
def string_compressor(string)
new_string = []
puts string.squeeze
string.squeeze.chars.each { |s|
count = 0
string.chars.each { |w|
if [s] == [w]
count += 1
end
}
new_string << "#{s}#{count}"
puts "#{new_string}"
}
if new_string.length > string.length
return string
elsif new_string.length < string.length
return new_string
else "Equal"
end
end
string_compressor("abbbcca")
'abbbcca'.chars.chunk{|c| c}.map{|c, a| [c, a.size]}.flatten.join
Adapted from a similar question.
Similar:
'abbbcca'.chars.chunk{|c| c}.map{|c, a| "#{c}#{a.size}"}.join
See chunk documentation
You can use a regular expression for that.
'abbbcca'.gsub(/(.)\1*/) { |m| "%s%d" % [m[0], m.size] }
#=> "a1b3c2a1"
The regular expression reads, "match any character, capturing it in group 1. Then match the contents of capture group 1 zero or more times".
As you said, your code counts every letter in the string, not just the one grouped next to one another.
Here's a modified version :
def display_count(count)
if count == 1
""
else
count.to_s
end
end
def string_compressor(string)
new_string = ''
last_char = nil
count = 0
string.chars.each do |char|
if char == last_char
count += 1
else
new_string << "#{last_char}#{display_count(count)}" if last_char
last_char = char
count = 1
end
end
new_string << "#{last_char}#{display_count(count)}" if last_char
new_string
end
p string_compressor('abbbcca') #=> "ab3c2a"
p string_compressor('aaaabbb') #=> "a4b3"
p string_compressor('aabb') #=> "a2b2"
p string_compressor('abc') #=> "abc"
Note that with display_count removing 1s from the string, new_string can never be longer than string. It also probably isn't a good idea to return Equal as a supposedly compressed string.
To decompress the string :
def string_decompressor(string)
string.gsub(/([a-z])(\d+)/i){$1*$2.to_i}
end
p string_decompressor("a5b11") #=> "aaaaabbbbbbbbbbb"
p string_decompressor("ab3c2a") #=> "abbbcca"

changing integers into words ruby without gems

I am trying to change numbers up to 100 from integers into words, but have run into some trouble, can anyone point out what is missing with my code:
def in_words(integer)
numWords = {
0=>"zero",
1=>"one",
2=>"two",
3=>"three",
4=>"four",
5=>"five",
6=>"six",
7=>"seven",
8=>"eight",
9=>"nine",
10=>"ten",
11=>"eleven",
12=>"twelve",
13=>"thirteen",
14=>"fourteen",
15=>"fifteen",
16=>"sixteen",
17=>"seventeen",
18=>"eighteen",
19=>"nineteen",
20=>"twenty",
30=>"thirty",
40=>"fourty",
50=>"fifty",
60=>"sixty",
70=>"seventy",
80=>"eighty",
90=>"ninety",
100=>"one hundred"
}
array = integer.to_s.split('')
new_array = []
numWords.each do |k,v|
array.each do |x|
if x = k
new_array.push(v)
end
end
end
new_array.join('')
end
Right now when I do:
inwords(0)
I get the following:
=>"zeroonetwothreefourfivesixseveneightnineteneleventwelvethirteenfourteenfiftee nsixteenseventeeneighteennineteentwentythirtyfourtyfiftysixtyseventyeightyninetyone hundred"
Edit
I noticed your code iterates through the array a lot of times and uses the = instead of the == in your if statements.
Your code could be more efficient using the Hash's #[] method in combination with the #map method.., here's a one-line alternative:
integer.to_s.split('').map {|i| numWords[i.to_i]} .join ' '
Also, notice that the integer.to_s.split('') will split the array into one-digit strings, so having numbers up to a hundred isn't relevant for the code I proposed.
To use all the numbers in the Hash, you might want to use a Regexp to identify the numbers you have. One way is to do the following (I write it in one line, but it's easy to break it down using variable names for each step):
integer.to_s.gsub(/(\d0)|([1]?\d)/) {|v| v + " "} .split.map {|i| numWords[i.to_i]} .join ' '
# or:
integer.to_s.gsub(/(#{numWords.keys.reverse.join('|')})/) {|v| v + " "} .split.map {|i| numWords[i.to_i]} .join ' '
# out = integer.to_s
# out = out.gsub(/(#{numWords.keys.reverse.join('|')})/) {|v| v + " "}
# out = out.split
# out = out.map {|i| numWords[i.to_i]}
# out = out.join ' '
Edit 2
Since you now mention that you want the method to accept numbers up to a hundred and return the actual number (23 => twenty three), maybe a different approach should be taken... I would recommend that you update your question as well.
def in_words(integer)
numWords = {
0=>"zero",
1=>"one",
2=>"two",
3=>"three",
4=>"four",
5=>"five",
6=>"six",
7=>"seven",
8=>"eight",
9=>"nine",
10=>"ten",
11=>"eleven",
12=>"twelve",
13=>"thirteen",
14=>"fourteen",
15=>"fifteen",
16=>"sixteen",
17=>"seventeen",
18=>"eighteen",
19=>"nineteen",
20=>"twenty",
30=>"thirty",
40=>"fourty",
50=>"fifty",
60=>"sixty",
70=>"seventy",
80=>"eighty",
90=>"ninety",
100=>"one hundred"
}
raise "cannot accept such large numbers" if integer > 100
raise "cannot accept such small numbers" if integer < 0
return "one hundred" if integer == 100
if integer < 20 || integer %10 == 0
numWords[integer]
else
[numWords[integer / 10 * 10], numWords[integer % 10]].join ' '
end
end
the integer / 10 * 10 makes the number a round number (ten, twenty, etc') because integers don't have fractions (so, 23/10 == 2 and 2 * 10 == 20). The same could be achieved using integer.round(-1), which is probably better.
It seems like all you're trying to do is find a mapping from an implicit hash
module NumWords
INT2STR = {
0=>"zero",
1=>"one",
2=>"two",
3=>"three",
4=>"four",
5=>"five",
6=>"six",
7=>"seven",
8=>"eight",
9=>"nine",
10=>"ten",
11=>"eleven",
12=>"twelve",
13=>"thirteen",
14=>"fourteen",
15=>"fifteen",
16=>"sixteen",
17=>"seventeen",
18=>"eighteen",
19=>"nineteen",
20=>"twenty",
30=>"thirty",
40=>"fourty",
50=>"fifty",
60=>"sixty",
70=>"seventy",
80=>"eighty",
90=>"ninety",
100=>"one hundred"
}
module_function
def in_words(integer)
INT2STR[integer]
end
end
The above code separates the hash definition from the method call so that the hash doesn't get recreated every time you call in_words.
You can also use Hash#fetch instead of Hash#[] as Andrey pointed out.
Your test whether x = k is your first problem (in two ways).
Firstly, if x = k means assign the value of k to x and then execute the if block if that value is true (basically anything other than false or nil).
What you should actually be testing is x == k which will return true if x is equal to k.
The second problem is that you converted your number into an array of string representation so you are comparing, for example, if "0" == 0. This won't return true because they are different types.
If you convert it to if x.to_i == k then your if block will be executed and you'll get:
> in_words(0)
=> "zero"
Then you get to move onto the next problem which is that you're looking at your number digit by digit and some of the values you are testing against need two digits to be recognised:
> in_words(10)
=> "zeroone"
You might be in looking at a different question then - or maybe that is the question you wanted answered all along!
Here's another way you might do it:
ONES_TO_TEXT = { 0=>"zero", 1=>"one", 2=>"two", 3=>"three", 4=>"four",
5=>"five", 6=>"six", 7=>"seven", 8=>"eight", 9=>"nine" }
TEENS_TO_TEXT = { 10=>"ten", 11=>"eleven", 12=>"twelve",
13=>"thirteen", 15=>"fifteen" }
TENS_TO_TEXT = { 2=>"twenty", 3=>"thirty", 5=>"fifty", 8=>"eighty" }
def in_words(n)
raise ArgumentError, "#{n} is out-of_range" unless (0..100).cover?(n)
case n.to_s.size
when 1 then ONES_TO_TEXT[n]
when 3 then "one hundred"
else
case n
when (10..19)
TEENS_TO_TEXT.key?(n) ? TEENS_TO_TEXT[n] : ONES_TO_TEXT[n]+"teen"
else
t,o = n.divmod(10)
(TENS_TO_TEXT.key?(t) ? TENS_TO_TEXT[t] : ONES_TO_TEXT[t]+"ty") +
(o.zero? ? '' : "-#{ONES_TO_TEXT[o]}")
end
end
end
Let's try it:
in_words(5) #=> "five"
in_words(10) #=> "ten"
in_words(15) #=> "fifteen"
in_words(20) #=> "twenty"
in_words(22) #=> "twenty-two"
in_words(30) #=> "thirty"
in_words(40) #=> "fourty"
in_words(45) #=> "fourty-five"
in_words(50) #=> "fifty"
in_words(80) #=> "eighty"
in_words(99) #=> "ninety-nine"
in_words(100) #=> "one hundred"
Here the increased complexity may not be justified, but this approach may in fact simplify the calculations when the maximum permitted value of n is much greater than 100.

Word Count returns an array (of arrays of the form [word, count]) representing the frequency of each word

str = 'put returns between paragraph put returns between paragraph put returns between paragraph'
def word_count(string)
resut= []
return result = string.split.inject(Hash.new(0)) { |h,v| h[v] += 1; h }
end
def parse_word(word)
word.gsub!(/[^a-zA-Z0-9]/, " ")
word.downcase!
#yoo= word
end
result =word_count(str)
print result, "\n\n"
res2 = result.select { |pair| pair[1] > 1 } `#Error coming`
I am getting OutPut
**
OutPut
**
{"put"=>3, "returns"=>3, "between"=>3, "paragraph"=>3}
I need OutPut Like this
**
OutPut
**
{"put"=>3, "returns"=>3, "between"=>3, "paragraph"=>3}
and
put: 3
returns: 3
between: 3
but the main problem is that he gave us the code to do that but i cant able to understand it
I am not getting this what this code will do can anyone help me ...And modify it so it can work
The following processes the first paragraph of put returns ... Note that ss is an array of those words that occur at least twice in this paragraph.
nect = ss.select { |p| p[1] > 1 }
nect .sort.each do |key, count|
puts "#{key}: #{count}"
end
module WordCount
def self.word_count(s)
count_frequency(words_from_string(s))
end
def self.word_count_from_file(filename)
s = File.open(filename) { |file| file.read }
word_count(s)
end
def self.words_from_string(s)
s.downcase.scan(/[\w']+/)
end
def self.count_frequency(words)
counts = Hash.new(0)
for word in words
counts[word] += 1
end
# counts.to_a.sort {|a,b| b[1] <=> a[1]}
# sort by decreasing count, then lexicographically
counts.to_a.sort do |a,b|
[b[1],a[0]] <=> [a[1],b[0]]
end
end
end
def word_count(s)
WordCount.word_count(s)
end

Resources