How do I access the current array when using map/select - ruby

I have the following (working) code I'm trying to convert into a more concise snippet using either #map or #select.
def duplicate_string(string)
s_list = []
string.downcase.chars.each do |char|
if string.index(char) != string.rindex(char) && s_list.include?(char) == false
s_list << char if char != ' '
end
end
s_list
end
puts duplicate_string("I am a duplicate string") == ["i", "a", "t"] #true
This is what I've come up with so far, but I don't know how to access the current array that's been stored by #map or #select and using self isn't working
def duplicate_string_with_map(string)
string.downcase.chars.select { |char| string.index(char) != string.rindex(char) && self.include?(char) == false && char != ' ' }
end

The following code would solve your purpose:
def duplicate_string_with_map(string)
(string.downcase.chars.select { |char| string.index(char) != string.rindex(char) && char != ' ' }).uniq
end
Here you need not check include condition as you are already ensuring string.index(char) != string.rindex(char).
However, for a better ruby approach, I would suggest you to re-open String class and write a method there.
It would look something like this:
class String
def duplicate_characters_array
(downcase.chars.select { |char| index(char) != rindex(char) && char != ' ' }).uniq
end
end
string = "I am a duplicate string"
string.duplicate_characters_array

You don't need to access the array and you don't need to use Array#map.
There are many ways to reach the goal. One of them is to split the string in chars then group the chars (get a hash), reject the groups of space character and the groups smaller than two elements and return the keys of the remaining groups:
"I am a duplicate string"
.downcase
.chars
.group_by{|i| i}
.reject{|k, v| k == ' ' || v.length < 2}
.keys
# ["a", "i", "t"]

Here we can make use of a helper method, Array#difference. The method is explained here. Note that that link contains a link to an SO answer where I cite examples of its use. Though I proposed that the method be added to the Ruby core there appears to be little interest in doing so.
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
Here we can use this helper as follows.
def duplicate_string(str)
a = str.gsub(/\s/,'').downcase.reverse.chars
a.difference(a.uniq).uniq.reverse
end
duplicate_string "I am a duplicate string"
#=> ["a", "i", "t"]
The steps are as follows.
str = "I am a duplicate string"
b = str.gsub(/\s/,'')
#=> "Iamaduplicatestring"
c = b.downcase
#=> "iamaduplicatestring"
d = c.reverse
#=> "gnirtsetacilpudamai"
a = d.chars
#=> ["g", "n", "i", "r", "t", "s", "e", "t", "a", "c", "i", "l", "p",
# "u", "d", "a", "m", "a", "i"]
e = a.uniq
#=> ["g", "n", "i", "r", "t", "s", "e", "a", "c", "l", "p", "u", "d", "m"]
f = a.difference(e)
#=> ["t", "i", "a", "a", "i"]
g = f.uniq
#=> ["t", "i", "a"]
g.reverse
#=> ["a", "i", "t"]
The key step is the calculation of f. For each element c of e, f contains n-1 instances of c, where n is the number of instances of c in a. The method therefore excludes characters other than spaces that appear in the string exactly once.

Related

private method called noMethodError ruby

I've been trying to work the following problem out and ran into the error. The point of the problem is to use a given key sequence to encrypt a string. For example, when given "cat" and [1,2,3] the result should be "dcw"
Any suggestions? the error was the following
def vigenere_cipher(string, key_sequence)
keyIndex=0
string=string.each_char.map do |c|
c=c.shift!(c,keyIndex)
keyIndex+=1
if keyIndex=key_sequence.length
keyIndex=0
end
end
return string
end
def shift!(c,keyIndex)
alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
inititalLetterIndex=alphabet.index(c)
finalLetterIndex=alphabet[inititalLetterIndex+keyIndex]
return alphabet[finalLetterIndex]
end
vigenere_cipher("cat", [1,2,3])
# private method `shift!' called for "c":String (NoMethodError)
You are trying to call shift! on string object that does not define on String Class, instead you defined on main object. You can call it like shift!(c,keyIndex) instead of c.shift!(c,keyIndex)
If you want to call you method shift! on a string, you will have to define it on String class.
class String
def shift!(keyIndex)
# you can access `c` using `self` here
...
end
end
Then you can call it as c.shift!(keyIndex) (Note the arguments are different).
Step 1
cipher.rb:4:in `block in vigenere_cipher': private method `shift!' called for "c":String (NoMethodError)
shift! isn't defined in String class, but at the top level.
So replace c=c.shift!(c,keyIndex) by c=shift!(c,keyIndex)
Step 2
cipher.rb:17:in `[]': no implicit conversion of String into Integer (TypeError)
Line 16 defines :
finalLetterIndex=alphabet[inititalLetterIndex+keyIndex]
alphabet contains letters as Strings, so finalLetterIndex isn't an index (Numeric), but a String.
On line 17, you try to use this String as an index.
Replace line 16 with :
finalLetterIndex=inititalLetterIndex+keyIndex
Step 3
Your script doesn't raise any exception anymore. It also doesn't display anything, so add a puts to the last line :
puts vigenere_cipher("cat", [1,2,3]).inspect
It returns :
[0, 0, 0]
Step 4
keyIndex seems to be stuck at 0. Why?
Look at line 6 :
if keyIndex=key_sequence.length
It doesn't test an equality, it assigns keyIndex to key_sequence.length.
Since any number is truthy in Ruby, it executes the code inside the if statement. Replace with
if keyIndex==key_sequence.length
Step 5
Your code returns [nil, nil, 0]. Why?
string is defined as the result of map. map returns an Array, in which each element is the result of the last executed command inside the block : in this case, the if statement.
if returns nil when the condition isn't satisfied, and returns the last executed command otherwise. In this case 0.
Add c at the last line of your map block.
Step 6
Your code now returns ["c", "b", "v"]. Why?
You only shift by shiftIndex, not by the amount defined in key_sequence Array. Replace
c=shift!(c,keyIndex)
with
c=shift!(c,key_sequence[keyIndex])
Step 7
Your code returns ["d", "c", "w"]. Almost there!
Ruby is a dynamic language. You're free to overwrite the String string with an Array, but it will confuse others and your future self.
Use array or letters instead of string, and return letters.join
Your script now returns "dcw".
It should look like :
def vigenere_cipher(string, key_sequence)
keyIndex=0
letters=string.each_char.map do |c|
c=shift!(c,key_sequence[keyIndex])
keyIndex+=1
if keyIndex==key_sequence.length
keyIndex=0
end
c
end
return letters.join
end
def shift!(c,keyIndex)
alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
inititalLetterIndex=alphabet.index(c)
finalLetterIndex=inititalLetterIndex+keyIndex
return alphabet[finalLetterIndex]
end
Step 8
vigenere_cipher("Hello", [1,2,3])
raises
cipher.rb:17:in 'shift!': undefined method '+' for nil:NilClass (NoMethodError).
Well, 'H' isn't found in your alphabet. Use downcase :
array=string.downcase.each_char.map do |c|
Step 9
vigenere_cipher("Hello World", [1,2,3])
doesn't work either, because of the space. Delete anything that isn't a letter :
array=string.downcase.delete('^a-z').each_char.map do |c|
Step 10
vigenere_cipher("zzz", [1,2,3])
returns an empty String, because there's no letter after z.
Use modulo 26 :
return alphabet[finalLetterIndex%26]
Step 11
Remove typos, don't use camelCase for variables, remove unnecessary return and you get :
def vigenere_cipher(string, key_sequence)
key_index = 0
letters = string.downcase.delete('^a-z').each_char.map do |c|
c = shift(c, key_sequence[key_index])
key_index = (key_index + 1) % key_sequence.length
c
end
letters.join
end
def shift(c, key_index)
alphabet = ('a'..'z').to_a
initial_letter_index = alphabet.index(c)
final_letter_index = initial_letter_index + key_index
alphabet[final_letter_index % 26]
end
Step 12
Using each_char, zip and cycle, I'd rewrite the whole code this way :
class Integer
# 0 => 'a', 1 => 'b', ..., 25 => 'z', 26 => 'a'
def to_letter
('a'.ord + self % 26).chr
end
end
class String
# 'A' => '0', 'a' => 0, ..., 'z' => 25
def to_code
self.downcase.ord - 'a'.ord
end
end
def vigenere_cipher(string, key)
short_string = string.delete('^A-Za-z')
short_string.each_char.zip(key.cycle).map do |char, shift|
(char.to_code + shift).to_letter
end.join
end
Step 13
Wikipedia article uses a String as key :
def vigenere_cipher(string, key)
short_string = string.delete('^A-Za-z')
short_string.each_char.zip(key.each_char.cycle).map do |char, shift|
(char.to_code + shift.to_code).to_letter
end.join
end
vigenere_cipher('Attack at dawn!', 'LEMON').upcase # => "LXFOPVEFRNHR"
Step 14
You should also be able to decrypt the message :
def vigenere_cipher(string, key, decrypt = false)
short_string = string.delete('^A-Za-z')
short_string.each_char.zip(key.each_char.cycle).map do |char, shift|
(char.to_code + shift.to_code * (decrypt ? -1 : 1)).to_letter
end.join
end
vigenere_cipher("LXFOPVEFRNHR", 'LEMON', :decrypt) #=> "attackatdawn"
Well, that was longer than expected! :D

Ruby codes works

I have the ff array:
words = ['demo', 'none', 'tied', 'evil', 'dome', 'mode', 'live',
'fowl', 'veil', 'wolf', 'diet', 'vile', 'edit', 'tide',
'flow', 'neon']
And now I am trying to do an anagram like this:
["demo", "dome", "mode"]
["neon", "none"]
(etc)
So I got this code:
result = {}
words.each do |word|
key = word.split('').sort.join
if result.has_key?(key)
result[key].push(word)
else
result[key] = [word]
end
end
result.each do |k, v|
puts "------"
p v
end
I understand how the word got split and joined but this part here is not clear to me:
if result.has_key?(key)
result[key].push(word)
else
result[key] = [word]
end
On the code above it's pretty obvious that result is an empty hash and now we're asking if it has a key of the sorted/joined key via if result.has_key?(key) How does that work? Why ask an empty hash if it has a key of the selected key via word iteration?
result[key].push(word) also is not clear to me. So is this code putting the key inside the result as its key? or the word itself?
result[key] = [word] this one also. Is it adding the word inside the array with the key?
Sorry I am bit confused.
The results is only empty on the first iteration of the loop. The line
if result.has_key?(key)
is checking if the key created by sorting the letters in the current word exists, and in the case of the first iteration when it's empty, yes, it is obviously not there this time, but it still needs to check every other time too.
Now, when a particular key does not exist yet in results, that key is added to results and a new array containing the current word is added as the value for that key, in the line
result[key] = [word]
When a key already exists in results, that means there is already an array containing at least one word, so the current word is added into that array, in the line
result[key].push(word)
Stepping through what's happening:
words = ['demo', 'neon', 'dome', 'mode', 'none']
// first iteration of the loop
word = 'demo'
key = 'demo' // the letters in "demo" happen to already be sorted
Is 'demo' a key in results?
results is currently {}
No, 'demo' is not a key in {}
Add 'demo' as a key, and add an array with 'demo' inside
results is now { 'demo' => ['demo'] }
// second iteration
word = 'neon'
key = 'enno'
Is 'enno' a key in results?
results is currently { 'demo' => ['demo'] }
No, 'enno' is not a key in { 'demo' => ['demo'] }
Add 'enno' as a key, and add an array with 'neon' inside
results is now { 'demo' => ['demo'], 'enno' => ['neon'] }
// third iteration
word = 'dome'
key = 'demo'
Is 'demo' a key in results?
results is currently { 'demo' => ['demo'], 'enno' => ['neon'] }
Yes, 'demo' is a key in { 'demo' => ['demo'], 'enno' => ['neon'] }
Add 'dome' to the array at key = 'demo'
results is now { 'demo' => ['demo', 'dome'], 'enno' => ['neon'] }
// ... and so on
There are tools that help you figure this stuff out on your own. Here's an example using Seeing Is Believing with vim:
words = ['demo', 'mode']
result = {}
words.each do |word| # => ["demo", "mode"]
key = word # => "demo", "mode"
.split('') # => ["d", "e", "m", "o"], ["m", "o", "d", "e"]
.sort # => ["d", "e", "m", "o"], ["d", "e", "m", "o"]
.join # => "demo", "demo"
result # => {}, {"demo"=>["demo"]}
if result.has_key?(key)
result[key].push(word) # => ["demo", "mode"]
else
result[key] = [word] # => ["demo"]
end
result # => {"demo"=>["demo"]}, {"demo"=>["demo", "mode"]}
end
result.each do |k, v| # => {"demo"=>["demo", "mode"]}
puts "------"
p v
end
# >> ------
# >> ["demo", "mode"]
Other tools I'd use are Irb and Pry.
Considering that you have answers that provide good explanations of your problem, I would like to present some more Ruby-like approaches that could be used. All of these methods create a hash h whose values are arrays of words that are anagrams of each other, which can be extracted from the hash by executing h.values.
Use Enumerable#group_by and Array#sort
This is arguably the most direct approach.
words.group_by { |w| w.each_char.sort }.values
#=> [["demo", "dome", "mode"], ["none", "neon"], ["tied", "diet", "edit", "tide"],
# ["evil", "live", "veil", "vile"], ["fowl", "wolf", "flow"]]
group_by produces
words.group_by { |w| w.each_char.sort }
#=> {["d", "e", "m", "o"]=>["demo", "dome", "mode"],
# ["e", "n", "n", "o"]=>["none", "neon"],
# ["d", "e", "i", "t"]=>["tied", "diet", "edit", "tide"],
# ["e", "i", "l", "v"]=>["evil", "live", "veil", "vile"],
# ["f", "l", "o", "w"]=>["fowl", "wolf", "flow"]}
after which it is a simple matter of extracting the values of this hash.
Build a hash by appending words to arrays that are the values of the hash
words.each_with_object({}) { |w,h| (h[w.each_char.sort] ||= []) << w }.values
#=> [["demo", "dome", "mode"], ["none", "neon"], ["tied", "diet", "edit", "tide"],
# ["evil", "live", "veil", "vile"], ["fowl", "wolf", "flow"]]
When "demo" is passed to the block the hash h is empty, so the block variables are assigned values
w = "demo"
h = {}
and the block calculation is performed:
h[["d", "e", "m", "o"]] ||= []) << w
as
w.each_char.sort
#=> ["d", "e", "m", "o"]
Ruby first expands this to
h[["d", "e", "m", "o"]] = (h[["d", "e", "m", "o"]] ||= []) << "demo"
At this point h has no keys, so h[["d", "e", "m", "o"]] evaluates to nil. The expression therefore becomes
h[["d", "e", "m", "o"]] = (nil ||= []) << "demo"
= [] << "demo"
= ["demo"]
Later, when "dome" is encountered,
w = "dome"
w.each_char.sort
#=> ["d", "e", "m", "o"]
and since h already has this key, the block calculation is as follows.
h[["d", "e", "m", "o"]] = (h[["d", "e", "m", "o"]] ||= []) << "dome"
= (["demo"] ||= []) << "dome"
= ["demo"] << "dome"
= ["demo", "dome"]
We obtain
words.each_with_object({}) { |w,h| (h[w.each_char.sort] ||= []) << w }
#=> {["d", "e", "m", "o"]=>["demo", "dome", "mode"],
# ["e", "n", "n", "o"]=>["none", "neon"],
# ["d", "e", "i", "t"]=>["tied", "diet", "edit", "tide"],
# ["e", "i", "l", "v"]=>["evil", "live", "veil", "vile"],
# ["f", "l", "o", "w"]=>["fowl", "wolf", "flow"]}
after which the values are extracted.
A variant of this is the following.
words.each_with_object(Hash.new { |h,k| h[k] = []}) { |w,h|
h[w.each_char.sort] << w }.values
See the doc for Hash::new for an explanation, in particular the discussion of default values given by a block.
For each word, merge a hash having a single key into an initially-empty hash
words.each_with_object({}) { |w,h|
h.update(w.each_char.sort=>[w]) { |_,o,n| o+n } }.values
The argument w.each_char.sort=>[w] is shorthand for { w.each_char.sort=>[w] }.
This uses the form of Hash#update (aka merge!) that employs a "resolution" block (here { |_,o,n| o+n }) to determine the values of keys that are present in both hashes begin merged. See the doc for a description of that block's three keys (the first block variable, the common key, is not used in this calculation, which is why I used an underscore).

Regex to check alphanumeric string in ruby

I am trying to validate strings in ruby.
Any string which contains spaces,under scores or any special char should fail validation.
The valid string should contain only chars a-zA-Z0-9
My code looks like.
def validate(string)
regex ="/[^a-zA-Z0-9]$/
if(string =~ regex)
return "true"
else
return "false"
end
I am getting error:
TypeError: type mismatch: String given.
Can anyone please let me know what is the correct way of doing this?
If you are validating a line:
def validate(string)
!string.match(/\A[a-zA-Z0-9]*\z/).nil?
end
No need for return on each.
You can just check if a special character is present in the string.
def validate str
chars = ('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a
str.chars.detect {|ch| !chars.include?(ch)}.nil?
end
Result:
irb(main):005:0> validate "hello"
=> true
irb(main):006:0> validate "_90 "
=> false
def alpha_numeric?(char)
if (char =~ /[[:alpha:]]/ || char =~ /[[:digit:]]/)
true
else
false
end
end
OR
def alpha_numeric?(char)
if (char =~ /[[:alnum:]]/)
true
else
false
end
end
We are using regular expressions that match letters & digits:
The above [[:alpha:]] ,[[:digit:]] and [[:alnum:]] are POSIX bracket expressions, and they have the advantage of matching Unicode characters in their category. Hope this helps.
checkout the link below for more options:
Ruby: How to find out if a character is a letter or a digit?
No regex:
def validate(str)
str.count("^a-zA-Z0-9").zero? # ^ means "not"
end
Great answers above but just FYI, your error message is because you started your regex with a double quote ". You'll notice you have an odd number (5) of double quotes in your method.
Additionally, it's likely you want to return true and false as values rather than as quoted strings.
Similar to the very efficient regex-ish approach mentioned already by #steenslag and nearly just as fast:
str.tr("a-zA-Z0-9", "").length.zero?
OR
str.tr("a-zA-Z0-9", "") == 0
One benefit of using tr though is that you could also optionally analyze the results using the same basic formula:
str = "ABCxyz*123$"
rejected_chars = str.tr("a-zA-Z0-9", "")
#=> *$
is_valid = rejected_chars.length.zero?
#=> false
Similar to #rohit89:
VALID_CHARS = [*?a..?z, *?A..?Z, *'0'..'9']
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
# "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
# "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
# "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
# "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
def all_valid_chars?(str)
a = str.chars
a == a & VALID_CHARS
end
all_valid_chars?('a9Z3') #=> true
all_valid_chars?('a9 Z3') #=> false
Use .match? in Ruby 2.4+.
Ruby 2.4 introduced a convenient boolean-returning .match? method.
In your case, I would do something like this:
# Checks for any characters other than letters and numbers.
# Returns true if there are none. Returns false if there are one or more.
#
def valid?( string )
!string.match?( /[^a-zA-Z0-9]/ ) # NOTE: ^ inside [] set turns it into a negated set.
end

Checking if a certain letter comes x spaces after another in a string

I'm solving Coderbyte problems, and came across one called ABCheck, which takes a string and returns true if the letter 'a' and b are separated by exactly three places. I know there's an easier way to do this with regexes, but I'm trying to do it the logical way first, for learning purposes.
Here's the code I have:
def ABCheck(str)
str = str.downcase.split('')
str.each_with_index do |char,index|
if char == 'a' && str[index+4] == 'b'
return "true"
elsif
char == 'b' && str[index+4] == 'a'
return "true"
else
return "false"
end
end
end
ABCheck("Laura sobs")
My code isn't returning the correct answer. It returns false even though the answer should be true.
As #Arkku diagnosed your problem I will confine my comments to an alternative method for the non-regex solution. (In real life you certainly would want to use a regular expression.)
The Ruby way, as I see it, would be to use Enumerable#each_cons rather than indices:
def a3b_match?(str)
str.each_char.each_cons(5).any? { |f,*_,l|
(f=='a' && l=='b') || (f=='b' && l=='a') }
end
a3b_match?('xadogbite') #=> true
a3b_match?('xbdogaite') #=> true
a3b_match?('xbdgaite') #=> false
a3b_match?('xadoggybite') #=> false
If you instead wanted the number of matches, change Enumerable#any? to Enumerable#count.
Here are the steps:
str = 'xadogbite'
enum0 = str.each_char
#=> #<Enumerator: "xadogbite":each_char>
enum1 = enum0.each_cons(5)
#=> #<Enumerator: #<Enumerator: "xadogbite":each_char>:each_cons(5)>
Carefully examine the return values for the calculations of the enumerators enum0 and enum1. You can think of enum1 as a "compound" enumerator.
We can see the (five) values of enum1 that any? will pass into the block by converting that enumerator to an array:
enum1.to_a
#=> [["x", "a", "d", "o", "g"],
# ["a", "d", "o", "g", "b"],
# ["d", "o", "g", "b", "i"],
# ["o", "g", "b", "i", "t"],
# ["g", "b", "i", "t", "e"]]
Let's simulate the passing of the first value of enum1 into the block and assign it to the block variables1:
f,*m,l = enum1.next
f #=> "x"
m #=> ["a", "d", "o"]
l #=> "g"
We then perform the block calculation:
(f=='a' && l=='b') || (f=='b' && l=='a') }
#=> ('x'=='a' && 'g'=='b') || ('x'=='b' && 'g'=='a') }
#=> false || false => false
any? must therefore pass the next element of enum1 into the block:
f,*_,l = enum1.next
#=> ["a", "d", "o", "g", "b"]
f #=> "a"
l #=> "b"
(f=='a' && l=='b') || (f=='b' && l=='a') }
#=> ('a'=='a' && 'b'=='b') => true
Since we have a match on (f=='a' && l=='b'), there is no need for Ruby to evaluate (f=='b' && l=='a') or to perform similar calculations for the rest of the elements of enum1, so she doesn't. any? returns true.
1 I used the local variable m instead of _ because IRB uses the latter for its own purpose. When run from the command line, _ works just fine.
The problem is that you only check the first character – if that first character is not a or b meeting the search condition, you immediately return "false". You need to search through all the possible positions in the string before you know that none of the matched.
(This is a common pattern when searching for a match in some sort of collection; if you find it you can return immediately, but if you don't you must keep searching until the end.)
Also note that you return the string "false", not the boolean false.
Example solution (without regex):
def axb_match?(str, in_between = 3)
distance = in_between + 1 # chars in between + the 'b'
str, i = str.downcase, -1
while i = str.index('a', i + 1)
return true if (str[i + distance] == 'b') || (i >= distance && str[i - distance] == 'b')
end
false # no match was found (finding would return immediately)
end
axb_match? "Laura sobs" # -> true
And of course with regex it's quite simple:
str =~ /(a...b)|(b...a)/i

Ruby search for word in string

Given input = "helloworld"
The output should be output = ["hello", "world"]
Given I have a method called is_in_dict? which returns true if there's a word given
So far i tried:
ar = []
input.split("").each do |f|
ar << f if is_in_dict? f
// here need to check given char
end
How to achieve it in Ruby?
Instead of splitting the input into characters, you have to inspect all combinations, i.e. "h", "he", "hel", ... "helloworld", "e", "el" , "ell", ... "elloworld" and so on.
Something like this should work:
(0..input.size).to_a.combination(2).each do |a, b|
word = input[a...b]
ar << word if is_in_dict?(word)
end
#=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
ar
#=> ["hello", "world"]
Or, using each_with_object, which returns the array:
(0..input.size).to_a.combination(2).each_with_object([]) do |(a, b), array|
word = input[a...b]
array << word if is_in_dict?(word)
end
#=> ["hello", "world"]
Another approach is to build a custom Enumerator:
class String
def each_combination
return to_enum(:each_combination) unless block_given?
(0..size).to_a.combination(2).each do |a, b|
yield self[a...b]
end
end
end
String#each_combination yields all combinations (instead of just the indices):
input.each_combination.to_a
#=> ["h", "he", "hel", "hell", "hello", "hellow", "hellowo", "hellowor", "helloworl", "helloworld", "e", "el", "ell", "ello", "ellow", "ellowo", "ellowor", "elloworl", "elloworld", "l", "ll", "llo", "llow", "llowo", "llowor", "lloworl", "lloworld", "l", "lo", "low", "lowo", "lowor", "loworl", "loworld", "o", "ow", "owo", "owor", "oworl", "oworld", "w", "wo", "wor", "worl", "world", "o", "or", "orl", "orld", "r", "rl", "rld", "l", "ld", "d"]
It can be used with select to easily filter specific words:
input.each_combination.select { |word| is_in_dict?(word) }
#=> ["hello", "world"]
This seems to be a task for recursion. In short you want to take letters one by one until you get a word which is in dictionary. This however will not guarantee that the result is correct, as the remaining letters may not form a words ('hell' + 'oworld'?). This is what I would do:
def split_words(string)
return [[]] if string == ''
chars = string.chars
word = ''
(1..string.length).map do
word += chars.shift
next unless is_in_dict?(word)
other_splits = split_words(chars.join)
next if other_splits.empty?
other_splits.map {|split| [word] + split }
end.compact.inject([], :+)
end
split_words('helloworld') #=> [['hello', 'world']] No hell!
It will also give you all possible splits, so pages with urls like penisland can be avoided
split_words('penisland') #=> [['pen', 'island'], [<the_other_solution>]]

Resources