I've got an array of strings which contains 2 elements: first is a sequence of characters and the second will be a long string of comma-separated words, in alphabetical order that represents dictionary of some arbitrary length. Now I want to check if from the list of words of the second element I can create the word of the first element in the array.
Below example will better illustrate what I mean:
string_array = ["baseball", "a,all,b,ball,bas,base,cat,code,d,e,quit,z"]
Output: base,ball
so What I did is simple method:
def word_split(string_array)
splitted_arr = string_array[1].split(',')
strArr.include?(splitted_arr)
end
but it gives me only false result. How to compare those string in array?
I made a method that takes two arguments: a word and an array of words. You can easily make a wrapper function that operates on the data structure you proposed, if you want.
def find_sequence(target, words)
return [] if target.empty?
words.each do |word|
if target.start_with?(word)
# We found a good candidate for the first word, so let's recurse
# and see if we can find the rest of the words.
remainder = target.sub(word, '') # remove word from start of target
seq = find_sequence(remainder, words)
return [word] + seq if seq
end
end
nil
end
# Example 1: your example
s = ["baseball", "a,all,b,ball,bas,base,cat,code,d,e,quit,z"]
p find_sequence(s[0], s[1].split(",")) # ["bas", "e", "b", "all"]
# Example 2: no solution
p find_sequence("foobar", ["foo", "cat"]) # nil
# Example 3: backtracking
p find_sequence("abcde", ["abcd","abc","ab","a","d","bcde"]) # ["a", "bcde"]
By the way, this is an example of a depth first search.
Related
print str[i].upcase is not working and i have to capitalize specific letters determined using an index. Can someone help me with this?
def mumble_letters
str = nil
print "Please write a string : "
str = gets.to_str
# puts str.length
while str.length == 1
print "Please write a string : "
str = gets.to_str
end
for i in 0..str.length
print str[i].upcase!
i.times{ print str[i].capitalize}
if i != str.length - 1
print"-"
end
end
end
mumble_letters
the error I get is : undefined method `upcase' for nil:NilClass (NoMethodError)
Did you mean? case
Problem
str[i].upcase! mutates the single character in the Array value into an uppercase character. However, at least on Ruby 2.7.1, it won't actually change the contents of your original String object until you reassign the element back to the String index you want modified. For example:
str[i] = str[i].upcase
However, the approach above won't work with frozen strings, which are fairly common in certain core methods, libraries, and frameworks. As a result, you may encounter the FrozenError exception with the index-assignment approach.
Solution
There's more than one way to solve this, but one way is to:
split your String object into an Array of characters,
modify the letter at the desired indexes,
rejoin the characters into a single String, and then
re-assign the modified String to your original variable.
For example, showing some intermediate steps:
# convert String to Array of characters
str = "foobar"
chars = str.chars
# contents of your chars Array
chars
#=> ["f", "o", "o", "b", "a", "r"]
# - convert char in place at given index in Array
# - don't rely on the return value of the bang method
# to be a letter
# - safe navigation handles various nil-related errors
chars[3]&.upcase!
#=> "B"
# re-join Array of chars into String
chars.join
#=> "fooBar"
# re-assign to original variable
str = chars.join
str
#=> "fooBar"
If you want, you can perform the same operation on multiple indexes of your chars Array before re-joining the elements. That should yield the results you're looking for.
More concisely:
str = "foobar"
chars = str.chars
chars[3]&.upcase!
p str = chars.join
#=> "fooBar"
Personally, I find operating on an Array of characters more intuitive and easier to troubleshoot than making in-place changes through repeated assignments to indexes within the original String. Furthermore, it avoids exceptions raised when trying to modify indexes within a frozen String. However, your design choices may vary.
str[i].upcase returns the upcased letter, but does not modify it in place. Assign it back to the string for it to work.
str = 'abcd'
str[2] = str[2].upcase #=> "C"
str #=> "abCd"
I can see two problems with your code...
First, an empty string has a length of 0 so what you wanted to write is
while str.length == 0
Secondly, when you do...
for i in 0..str.length
You are iterating up to the string length INCLUDING the string length. If the string has five characters, it actually only has valid indexes 0 through 4 but you are iterating 0 through 5. And str[5] doesn't exist so returns nil and you cannot do upcase! on a nil.
To handle that common situation, Ruby has the tripe dot operator
for i in 0...str.length
...which will stop at the integer before the length, which is what you want.
It's also more ruby-eque to do
(0...str.length).each do |i|
To convert array to string I used Array#join and got space between the
beginning of the string, first quote mark, and the first word. I do not understand why this is happening.
I resolved with String#strip but I would like to understand
def order(words)
arr_new = []
arr = words.split(" ")
nums = ["1","2","3","4","5","6","7","8","9"]
arr.each do |word|
nums.each do |num|
if word.include? num
arr_new[num.to_i] = word
end
end
end
arr_new.join(" ").strip
end
order("is2 Thi1s T4est 3a")
Without .strip the output is:
" Thi1s is2 3a T4est"
After .strip:
"Thi1s is2 3a T4est"
The reason you're seeing the extra space is because arrays in ruby are 0 indexed, so you have an nil array element because your first insert is a index 1
x = []
x[1] = "test"
This creates an array as such:
[
nil,
"test"
]
If you created an empty array named x and assigned x[10] = "test" you'd have 10 nil values, and the word "test" in your array.
So, your array, before joining, is actually:
[nil, "Thi1s", "is2", "3a", "T4est"]
You have a couple options:
Change your strings to start with zero
Change your assignment to adjust the offset (subtract one)
Use compact before you join (this will remove nils)
Use strip as you noted
I'd suggest compact because it would address a few edge cases (such as "gaps" in your numbers.
More info in the array docs
#Jay's explanation is indeed correct.
I'll simply suggest a cleaner version of your code that doesn't have the same problem.
This assumes that the 1-9 order isn't dynamic. Aka wouldn't work if you wanted to sort by random characters for example.
def order(words)
words.split.sort_by { |word| word[/\d/].to_i }.join ' '
end
I want to split str in half and assign each half to first and second
Like this pseudo code example:
first,second = str.split( middle )
class String
def halves
chars.each_slice(size / 2).map(&:join)
end
end
Will work, but you will need to adjust to how you want to handle odd-sized strings.
Or in-line:
first, second = str.chars.each_slice(str.length / 2).map(&:join)
first,second = str.partition(/.{#{str.size/2}}/)[1,2]
Explanation
You can use partition. Using a regex pattern to look for X amount of characters (in this case str.size / 2).
Partition returns three elements; head, match, and tail. Because we are matching on any character, the head will always be a blank string. So we only care about the match and tail hence [1,2]
Here are two ways to do that
rgx = /
(?<= # begin a positive lookbehind
\A # match the beginning of the string
.{#{str.size/2}} # match any character #{str.size/2} times
) # end positive lookbehind
/x # invoke free-spacing regex definition mode
def halves(str)
str.split(rgx)
end
first, second = halves('abcdef')
#=> ["abc", "def"]
first, second = halves('abcde')
#=> ["ab", "cde"]
The regular expression is conventionally written
/(?<=\A.{#{str.size/2}})/
Note that the regular expression matches a location between two successive characters.
def halves(str)
[str[0, str.size/2], str[str.size/2..-1]]
end
first, second = halves('abcdef')
#=> ["abc", "def"]
first, second = halves('abcde')
#=> ["ab", "cde"]
Note: This only works with even length strings.
Along the line of your pseudocode,
first, second = string[0...string.length/2], string[string.length/2...string.length]
If string is the original string.
I want my output to search and count the frequency of the words "candy" and "gram", but also the combinations of "candy gram" and "gram candy," in a given text (whole_file.)
I am currently using the following code to display the occurrences of "candy" and "gram," but when I aggregate the combinations within the %w, only the word and frequencies of "candy" and "gram" display. Should I try a different way? thanks so much.
myArray = whole_file.split
stop_words= %w{ candy gram 'candy gram' 'gram candy' }
nonstop_words = myArray - stop_words
key_words = myArray - nonstop_words
frequency = Hash.new (0)
key_words.each { |word| frequency[word] +=1 }
key_words = frequency.sort_by {|x,y| x }
key_words.each { |word, frequency| puts word + ' ' + frequency.to_s }
It sounds like you're after n-grams. You could break the text into combinations of consecutive words in the first place, and then count the occurrences in the resulting array of word groupings. Here's an example:
whole_file = "The big fat man loves a candy gram but each gram of candy isn't necessarily gram candy"
[["candy"], ["gram"], ["candy", "gram"], ["gram", "candy"]].each do |term|
terms = whole_file.split(/\s+/).each_cons(term.length).to_a
puts "#{term.join(" ")} #{terms.count(term)}"
end
EDIT: As was pointed out in the comments below, I wasn't paying close enough attention and was splitting the file on each loop which is obviously not a good idea, especially if it's large. I also hadn't accounted for the fact that the original question may've need to sort by the count, although that wasn't explicitly asked.
whole_file = "The big fat man loves a candy gram but each gram of candy isn't necessarily gram candy"
# This is simplistic. You would need to address punctuation and other characters before
# or at this step.
split_file = whole_file.split(/\s+/)
terms_to_count = [["candy"], ["gram"], ["candy", "gram"], ["gram", "candy"]]
counts = []
terms_to_count.each do |term|
terms = split_file.each_cons(term.length).to_a
counts << [term.join(" "), terms.count(term)]
end
# Seemed like you may need to do sorting too, so here that is:
sorted = counts.sort { |a, b| b[1] <=> a[1] }
sorted.each do |count|
puts "#{count[0]} #{count[1]}"
end
Strip punctuation and convert to lower-case
The first thing you probably want to do is remove all punctuation from the string holding the contents of the file and then convert what's left to lower case, the latter so you don't have worry about counting 'Cat' and 'cat' as the same word. Those two operations can be done in either order.
Changing upper-case letters to lower-case is easy:
text = whole_file.downcase
To remove the punctuation it is probably easier to decide what to keep rather than what to discard. If we only want to keep lower-case letters, you can do this:
text = whole_file.downcase.gsub(/[^a-z]/, '')
That is, substitute an empty string for all characters other than (^) lowercase letters.1
Determine frequency of individual words
If you want to count the number of times text contains the word 'candy', you can use the method String#scan on the string text and then determine the size of the array that is returned:
text.scan(/\bcandy\b/).size
scan returns an array with every occurrence of the string 'candy'; .size returns the size of that array. Here \b ensures 'candy gram' has a word "boundary" at each end, which could be whitespace or the beginning or end of a line or the file. That's to prevent `candycane' from being counted.
A second way is to convert the string text to an array of words, as you have done2:
myArray = text.split
If you don't mind, I'd like to call this:
words = text.split
as I find that more expressive.3
The most direct way to determine the number of times 'candy' appears is to use the method Enumberable#count, like this:
words.count('candy')
You can also use the array difference method, Array#-, as you noted:
words.size - (words - ['candy']).size
If you wish to know the number of times either 'candy' or 'gram' appears, you could of course do the above for each and sum the two counts. Some other ways are:
words.size - (myArray - ['candy', 'gram']).size
words.count { |word| word == 'candy' || word = 'gram' }
words.count { |word| ['candy', 'gram'].include?(word) }
Determine the frequency of all words that appear in the text
Your use of a hash with a default value of zero was a good choice:
def frequency_of_all_words(words)
frequency = Hash.new(0)
words.each { |word| frequency[word] +=1 }
frequency
end
I wrote this as a method to emphasize that words.each... does not return frequency. Often you would see this written more compactly using the method Enumerable#each_with_object, which returns the hash ("object"):
def frequency_of_all_words(words)
words.each_with_object(Hash.new(0)) { |word, h| h[word] +=1 }
end
Once you have the hash frequency you can sort it as you did:
frequency.sort_by {|word, freq| freq }
or
frequency.sort_by(&:last)
which you could write:
frequency.sort_by {|_, freq| freq }
since you aren't using the first block variable. If you wanted the most frequent words first:
frequency.sort_by(&:last).reverse
or
frequency.sort_by {|_, freq| -freq }
All of these will give you an array. If you want to convert it back to a hash (with the largest values first, say):
Hash[frequency.sort_by(&:last).reverse]
or in Ruby 2.0+,
frequency.sort_by(&:last).reverse.to_h
Count the number of times a substring appears
Now let's count the number of times the string 'candy gram' appears. You might think we could use String#scan on the string holding the entire file, as we did earlier4:
text.scan(/\bcandy gram\b/).size
The first problem is that this won't catch 'candy\ngram'; i.e., when the words are separated by a newline character. We could fix that by changing the regex to /\bcandy\sgram\b/. A second problem is that 'candy gram' might have been 'candy. Gram' in the file, in which case you might not want to count it.
A better way is to use the method Enumerable#each_cons on the array words. The easiest way to show you how that works is by example:
words = %w{ check for candy gram here candy gram again }
#=> ["check", "for", "candy", "gram", "here", "candy", "gram", "again"]
enum = words.each_cons(2)
#=> #<Enumerator: ["check", "for", "candy", "gram", "here", "candy",
# "gram", "again"]:each_cons(2)>
enum.to_a
#=> [["check", "for"], ["for", "candy"], ["candy", "gram"],
# ["gram", "here"], ["here", "candy"], ["candy", "gram"],
# ["gram", "again"]]
each_cons(2) returns an enumerator; I've converted it to an array to display its contents.
So we can write
words.each_cons(2).map { |word_pair| word_pair.join(' ') }
#=> ["check for", "for candy", "candy gram", "gram here",
# "here candy", "candy gram", "gram again"]
and lastly:
words.each_cons(2).map { |word_pair|
word_pair.join(' ') }.count { |s| s == 'candy gram' }
#=> 2
1 If you also wanted to keep dashes, for hyphenated words, change the regex to /[^-a-z]/ or /[^a-z-]/.
2 Note from String#split that .split is the same as both .split(' ') and .split(/\s+/)).
3 Also, Ruby's naming convention is to use lower-case letters and underscores ("snake-case") for variables and methods, such as my_array.
I have an array:
array = ["abhor", "rage", "mad"]
and I want to check if a string includes any word in that array - But only that word (not a substring).
string = 'I made a cake.'
count = 0
array.each do |word|
if string.include? word
count += 1
end
end
However, the above does increment count by 1 because it's picking up the word mad from made in my string. How can I search for only mad and ensure made doesn't get counted?
The array intersection operator & is useful here.
Here's two options, depending on how you define "word":
1) If a word is any sequence of non-whitespace characters, then you can do:
array & string.split
In your example, this results in the intersection of array and words in string, which is empty.
2) If a word is any sequence of alphanumeric characters including _, then you can do:
array & string.scan(/\w+/)
For example if array = ["abhor", "rage", "mad", "cake"] then #1 above will be empty (because you have cake. with a period in the string) but will return ['cake'] for method #2.
I would do it like this:
array = ["abhor", "rage", "mad"]
string = 'I made a cake.'
string.split.count{|word| array.include?(word)}
The problem with doing a simple split is that it won't take into account punctuation. What you need is a regex, which is a little more complicated.
array.each do |word|
count += 1 if string.match(/\W#{word}\W/)
end
If you're willing to go down the regex road, \b represent a word boundary. This example, which includes all of the words in your sentence and a few which are fragments, correctly returns 4.
array = ["abhor", "rage", "mad", "I", "made", "a", "cake", "cak"]
string = 'I made a cake.'
count = 0
array.each do |word|
if string =~ /\b#{word}\b/
count += 1
end
end
Try splitting up the words first.
words = string.split
count = 0
words.each do |word|
count += 1 if array.include? word
end