I know there is a method to find the largest string in an array
def longest_word(string_of_words)
x = string_of_words.split(" ").max_by(&:length)
end
However, if there are multiple words with the longest length, how do i return the last instance of the word with the longest length? Is there a method and do I use indexing?
Benjamin
What if we took advantage of reverse?
"asd qweewe lol qwerty df qwsazx".split.reverse_each.max_by(&:length)
=> "qwsazx"
Simply reverse your words array before applying max_by.
The first longest word from the reversed array will be the last one in your sentence.
can do this way also:
> "asd qweewe lol qwerty df qwsazx".split.sort_by(&:length).last
#=> "qwsazx"
Note: You can split words and sort by length in ascending(default) order and take the last word
You can use inject which will replace the maximum only if (via <=) it's matched or improved upon. By default inject takes the first element of its receiver.
str.split.inject { |m,s| m.size <= s.size ? s : m }
max_by.with_index{|e, i| [e, i]}
There's no need to convert the string to an array.
def longest_word(str)
str.gsub(/[[:alpha:]]+/).
each_with_object('') {|s,longest| longest.replace(s) if s.size >= longest.size}
end
longest_word "Many dogs love to swim in the sea"
#=> "swim"
Two points.
I've used String#gsub to create an enumerator that will feed words to Enumerable.#each_with_object. The string argument is not modified. This is an usual use of gsub that I've been able to use to advantage in several situations.
Within the block it's necessary to use longest.replace(s) rather than longest = s. That's because each_with_object returns the originally given object (usually modified by the block), but does not update that object on each iteration. longest = s merely returns s (is equivalent to just s) but does not alter the value of the block variable. By contrast, longest.replace(s) modifies the original object.
With regard to the second of these two points, it is interesting to contrast the use of each_with_object with Enumerable#reduce (aka inject).
str.gsub(/[[:alpha:]]+/).
reduce('') {|longest,s| s.size >= longest.size ? s : longest }
#=> "swim"
Related
I'm working on an array containing Twitter handles such as:
array = ["#user1","#User2","#uSer3","#User4"]
I want to know how many handles start with a capital letter.
If the question is "how many in a collection" , then count could very well be the way to go.
The regular expression /[[:upper:]]/ has the benefit that it matches capital letters other than A-Z.
array = ["#user1","#User2","#uSer3","#User4"]
p array.count{|handle| handle[1].match?( /[[:upper:]]/ )} # => 2
Even though there's an accepted answer, I felt like it's worth mentioning that the same can be accomplished using #count instead of #inject:
array = ["#user1","#User2","#uSer3","#User4"]
capital_letters = ('A'..'Z')
array.count {|x| capital_letters.include?(x[1])}
#=> 2
If a block is given to the #count method, it counts the number of elements for which the block returns a true value. The block, in this case, evaluates to true if the second letter of the array x[1] is a capital letter from A to Z. This works under the assumption that every string in the array starts with a "#".
I used a regex to match # followed by a capital letter at the start of the string.
count is the most semantic function (thanks to #crenmo):
array.count {|e| e =~ /^#[A-Z]/}
select is useful if you'd like a list of matched elements:
array.select {|e| e =~ /^#[A-Z]/ }
Use [[:upper:]] instead of [A-Z] if you wish to match non-English uppercase letters (thanks to #CarySwoveland).
For reference, indexing into a string can be done with bracket notation (although I didn't wind up using this): str[0]
Write a function that accepts two parameters, i) a string (containing a list of words) and ii) an integer (n). The function should alphabetize the list based on the nth letter of each word.
I have tried
def sort_it(list_, n)
list_.sort_by {|name| name[n]}
end
but it is saying that sort_by is not recognised.
Is there an elegant way to solve this?
list_ is a string while sort_by is a method of Enumerable. You need to convert your string to a collection of words before sorting. One way to do that is
list_.split
so your code will look like
def sort_it(list_, n)
list_.split.sort_by {|name| name[n]}
end
As a side note, don't use trailing underscore in argument name.
I've solved a problem that asks you to write a method for determining what words in a supplied array are anagrams and group the anagrams into a sub array within your output.
I've solved it using what seems to be the typical way that you would which is by sorting the words and grouping them into a hash based on their sorted characters.
When I originally started looking for a way to do this I noticed that String#sum exists which adds the ordinals of each character together.
I'd like to try and work out some way to determine an anagram based on using sum. For example "cars" and "scar" are anagrams and their sum is 425.
given an input of %w[cars scar for four creams scream racs] the expected output (which I already get using the hash solution) is: [[cars, scar, racs],[for],[four],[creams,scream]].
It seems like doing something like:
input.each_with_object(Hash.new []) do |word, hash|
hash[word.sum] += [word]
end
is the way to go, that gives you a hash where the values of the key "425" is ['cars','racs','scar']. What I think i'm missing is moving that into the expected format of the output.
Unfortunately I don't think String#sum is a robust way to solve this problem.
Consider:
"zaa".sum # => 316
"yab".sum # => 316
Same sum, but not anagrams.
Instead, how about grouping them by the sorted order of their characters?
words = %w[cars scar for four creams scream racs]
anagrams = words.group_by { |word| word.chars.sort }.values
# => [["cars", "scar", "racs"], ["for"], ["four"], ["creams", "scream"]]
words = %w[cars scar for four creams scream racs]
res={}
words.each do |word|
key=word.split('').sort.join
res[key] ||= []
res[key] << word
end
p res.values
[["cars", "scar", "racs"], ["for"], ["four"],["creams", "scream"]]
Actually, I think you could use sums for anagram testing, but not summing the chars' ordinals themselves, but something like this instead:
words = %w[cars scar for four creams scream racs]
# get the length of the longest word:
maxlen = words.map(&:length).max
# => 6
words.group_by{|word|
word.bytes.map{|b|
maxlen ** (b-'a'.ord)
}.inject(:+)
}
# => {118486616113189=>["cars", "scar", "racs"], 17005023616608=>["for"], 3673163463679584=>["four"], 118488792896821=>["creams", "scream"]}
Not sure if this is 100% correct, but I think the logic stands.
The idea is to map every word to a N-based number, every digit position representing a different char. N is the length of the longest word in input set.
To get the desired output format, you just need hash.values. But note that just using the sum of the character codes in a word could fail on some inputs. It is possible for the sums of the character codes in two words to be the same by chance, when they are not anagrams.
If you used a different algorithm to combine the character codes, the chances of incorrectly identifying words as "anagrams" could be made much lower, but still not zero. Basically you need some kind of hash algorithm, but with the property that the order of the values being hashed doesn't matter. Perhaps map each character to a different random bitstring, and take the sum of the bitstrings for each character in the string?
That way, the chances of any two non-anagrams giving you a false positive would be approximately 2 ** bitstring_length.
I have an array of strings:
phrases = ["Have a good Thanksgiving", "Eat lots of food"]
I have another array of single words: words = ["eat", "food"]
I want to return the entries in the first array if the string contains all the words in the second array.
So, it should look something like this:
phrases.select{ |x| x.include_all?(words) }
Should I just create the include_all? function to iterate through each member of the words array and do the comparison, or is there any built-in methods I'm missing?
You're actually very close to the solution.
phrases.select do |phrase|
words.all?{ |word| phrase.include? word }
end
The all? method is on Enumerable, and returns true if the block evaluates to true for each item in the collection.
Depending on exactly what your definition of the phrase "including" the word is, you may want to define your own include_all? method, or a method on String to determine the match. The include? method is case-sensitive and doesn't care about word boundaries. If those aren't your requirements, you can use a Regexp in place of include? or define your own method to wrap that logic up.
match, text, number = *"foobar 123".match(/([A-z]*) ([0-9]*)/)
I know this is doing some kind of regular expression match but what role does the splat play here and is there a way to do this without the splat so it's less confusing?
The splat is decomposing the regex match results (a MatchData with three groups: the whole pattern, the letters, and the numbers) into three variables. So we end up with:
match = "foobar 123"
text = "foobar"
number = "123"
Without the splat, there'd only be the one result (the MatchData) so Ruby wouldn't know how to assign it to the three separate variables.
is there a way to do this without the splat so it's less confusing?
Since a,b = [c,d] is the same as a,b = *[c,d] and splat calls to_a on its operand when it's not an array you could simply call to_a explicitly and not need the splat:
match, text, number = "foobar 123".match(/([A-z]*) ([0-9]*)/).to_a
Don't know whether that's less confusing, but it's splatless.
There's a good explanation in the documentation for MatchData:
Because to_a is called when expanding
*variable, thereās a useful assignment shortcut for extracting matched
fields. This is slightly slower than
accessing the fields directly (as an
intermediate array is generated).
all,f1,f2,f3 = *(/(.)(.)(\d+)(\d)/.match("THX1138."))
all #=> "HX1138"
f1 #=> "H"
f2 #=> "X"
f3 #=> "113"
String.match returns a MatchData object, which contains all the matches of the regular expression. The splat operator splits this object and returns all the matches separately.
If you just run
"foobar 123".match(/([A-z]*) ([0-9]*)/)
in irb, you can see the MatchData object, with the matches collected.
MatchData is a special variable, for all intents and purposes an array (kind of) so you can in fact do this as well:
match, text, number = "foobar 123".match(/([A-z]*) ([0-9]*)/)[0..2]
Learn more about the special variable MatchData