Decompose words into letters with Ruby - ruby

In my language there are composite or compound letters, which consists of more than one character, eg "ty", "ny" and even "tty" and "nny". I would like to write a Ruby method (spell) which tokenize words into letters, according to this alphabet:
abc=[*%w{tty ccs lly ggy ssz nny dzs zzs sz zs cs gy ny dz ty ly q w r t z p l k j h g f d s x c v b n m y}.map{|z| [z,"c"]},*"eéuioöüóőúűáía".split(//).map{|z| [z,"v"]}].to_h
The resulting hash keys shows the existing letters / composite letters of the alphabet and also shows which letter is a consonant ("c") and which one is a vowel ("v"), becase later I would like to use this hash to decompose words into syllables. Cases of compound words when accidentally composite letters are formed at the words common boundary shoudn't be resolved by the method of course.
Examples:
spell("csobolyó") => [ "cs", "o", "b", "o", "ly", "ó" ]
spell("nyirettyű") => [ "ny", "i", "r", "e", "tty", "ű" ]
spell("dzsesszmuzsikus") => [ "dzs", "e", "ssz", "m", "u", "zs", "i", "k", "u", "s" ]

You might be able to get started looking at String#scan, which appears to be giving decent results for your examples:
"csobolyó".scan(Regexp.union(abc.keys))
# => ["cs", "o", "b", "o", "ly", "ó"]
"nyirettyű".scan(Regexp.union(abc.keys))
# => ["ny", "i", "r", "e", "tty", "ű"]
"dzsesszmuzsikus".scan(Regexp.union(abc.keys))
# => ["dzs", "e", "ssz", "m", "u", "zs", "i", "k", "u", "s"]
The last case doesn't match your expected output, but it matches your statement in the comments
I sorted the letters in the alphabet: if a letter appears earlier, then it should be recognized instead of its simple letters. When a word contains "dzs" it should be considered to "dzs" and not to "d" and "zs"

I didn't use the preference in which you sorted, rather I used higher character word will have higher preference than lower character word.
def spell word
abc=[*%w{tty ccs lly ggy ssz nny dzs zzs sz zs cs gy ny dz ty ly q w r t z p l k j h g f d s x c v b n m y}.map{|z| [z,"c"]},*"eéuioöüóőúűáía".split(//).map{|z| [z,"v"]}].to_h
current_position = 0
maximum_current_position = 2
maximum_possible_position = word.length
split_word = []
while current_position < maximum_possible_position do
current_word = set_current_word word, current_position, maximum_current_position
if abc[current_word] != nil
current_position, maximum_current_position = update_current_position_and_max_current_position current_position, maximum_current_position
split_word.push(current_word)
else
maximum_current_position = update_max_current_position maximum_current_position
current_word = set_current_word word, current_position, maximum_current_position
if abc[current_word] != nil
current_position, maximum_current_position = update_current_position_and_max_current_position current_position, maximum_current_position
split_word.push(current_word)
else
maximum_current_position = update_max_current_position maximum_current_position
current_word = set_current_word word, current_position, maximum_current_position
if abc[current_word] != nil
current_position, maximum_current_position = update_current_position_and_max_current_position current_position, maximum_current_position
split_word.push(current_word)
else
puts 'This word cannot be formed in the current language'
break
end
end
end
end
split_word
end
def update_max_current_position max_current_position
max_current_position = max_current_position - 1
end
def update_current_position_and_max_current_position current_position,max_current_position
current_position = max_current_position + 1
max_current_position = current_position + 2
return current_position, max_current_position
end
def set_current_word word, current_position, max_current_position
word[current_position..max_current_position]
end
puts "csobolyó => #{spell("csobolyó")}"
puts "nyirettyű => #{spell("nyirettyű")}"
puts "dzsesszmuzsikus => #{spell("dzsesszmuzsikus")}"
Output
csobolyó => ["cs", "o", "b", "o", "ly", "ó"]
nyirettyű => ["ny", "i", "r", "e", "tty", "ű"]
dzsesszmuzsikus => ["dzs", "e", "ssz", "m", "u", "zs", "i", "k", "u", "s"]

Meanwhile I managed to write a method which works, but 5x slower than String#scan:
abc=[*%w{tty ccs lly ggy ssz nny dzs zzs sz zs cs gy ny dz ty ly q w r t z p l k j h g f d s x c v b n m y}.map{|z| [z,"c"]},*"eéuioöüóőúűáía".split(//).map{|z| [z,"v"]}].to_h
def spell(w,abc)
s=w.split(//)
p=""
t=[]
for i in 0..s.size-1 do
p << s[i]
if i>=s.size-2 then
if abc[p]!=nil then
t.push p
p=""
elsif abc[p[0..-2]]!=nil then
t.push p[0..-2]
p=p[-1]
elsif abc[p[0]]!=nil then
t.push p[0]
p=p[1..-1]
end
elsif p.size==3 then
if abc[p]!=nil then
t.push p
p=""
elsif abc[p[0..-2]]!=nil then
t.push p[0..-2]
p=p[-1]
elsif abc[p[0]]!=nil then
t.push p[0]
p=p[1..-1]
end
end
end
if p.size>0 then
if abc[p]!=nil then
t.push p
p=""
elsif abc[p[0..-2]]!=nil then
t.push p[0..-2]
p=p[-1]
end
end
if p.size>0 then
t.push p
end
return t
end

Related

Ruby. Shuffle the array so that there are no adjacent elements with the same value

An array of hashes is given (10 elements at least):
arr = [{letter: "a", number: "1"}, {letter: "a", number: "3"}, {letter: "b", number: "4"}, {letter: "b", number: "1"}, ..., {letter: "e", number: "2"} ]
The task is to shuffle the array so that there are no adjacent elements with the same 'letter' value.
So, the result should be like the following:
[{letter: "c", number: "4"}, {letter: "a", number: "1"}, {letter: "e", number: "2"}, {letter: "b", number: "1"}, ..., {letter: "a", number: "3"} ]
What is the simplest way to do that?
=== UPDATE ===
The number of repeated letters in the array is precisely known - it's 20% of the array length.
So, the array looks like the following:
[
{letter: "a", number: "1"}, {letter: "a", number: "3"},
{letter: "b", number: "4"}, {letter: "b", number: "1"},
{letter: "c", number: "7"}, {letter: "c", number: "3"},
{letter: "d", number: "6"}, {letter: "d", number: "4"},
{letter: "e", number: "5"}, {letter: "e", number: "2"}
]
Or, its simplified version:
["a", "a", "b", "b", "c", "c", "d", "d", "e", "e"]
Or, for example, there is a simplified array containing 15 elements:
["a", "a", "a", "b", "b", "b", "c", "c", "c", "d", "d", "d", "e", "e", "e"]
The simplest way (without any random):
# Calculate letter frequency
freq = arr.group_by { |h| h[:letter] }.map { |k, v| [k, v.size] }.to_h
# Then check that the most frequent element occurs less that arr.size / 2
center = (arr.size + 1) / 2
if freq.values.max > center
# Impossible
end
# Sort array by frequency to have most frequent first.
sarr = arr.sort_by { |h| freq[h[:letter]] }.reverse
sarr[0..center-1].zip(sarr[center..-1]).flatten.compact
Your problem is a special case of this question. See my answer for the detailed explanation how this works.
We even don't need to sort by letter frequency. It's for corner cases like "abbcccc". We can solve them in another way:
# Works with correct data: most frequent letter occurs <= center times
def f(arr)
arr = arr.sort
center = (arr.size + 1) / 2
arr = arr[0..center-1].zip(arr[center..-1]).flatten.compact
double = (1..arr.size-1).find { |i| arr[i] == arr[i-1] }
double ? arr.rotate(double) : arr # fix for the corner cases
end
puts f(%w[a a a a b b c].shuffle).join
# ababaca
puts f(%w[a a b b b b c].shuffle).join
# bcbabab
puts f(%w[a b b c c c c].shuffle).join
# cacbcbc
The only non-linear part of the algorithm is arr.sort. But as you can see by the link above, we even don't need the sorting. We need letters counts, which could be found in linear time. Therefore, we can reduce the algorithm to O(n).
The number of repeated letters in the array is precisely known - it's 20% of the array length.
With this update, the algorithm is simplified to (as there are no corner cases):
sarr = arr.sort_by { |h| h[:letter] }
center = (arr.size + 1) / 2
sarr[0..center-1].zip(sarr[center..-1]).flatten.compact
The simple and maybe the less effective way could be the brute force.
So on a simplified version of the array, one can do:
ary = %w(a a c c b a s)
loop do
break if ary.shuffle!.slice_when { |a, b| a == b }.to_a.size == 1
end
Some check should be added to assure that a solution exists, to avoid infinite loop.
Other (better?) way is to shuffle then find the permutation (no infinite loop) which satisfy the condition:
ary.shuffle!
ary.permutation.find { |a| a.slice_when { |a, b| a == b }.to_a.size == 1 }
If a solution does not exist, it returns nil.
Run the the benchmark:
def looping
ary = %w(a a c c b a s)
loop do
break if ary.shuffle!.slice_when { |a, b| a == b }.to_a.size == 1
end
ary
end
def shuffle_permute
ary = %w(a a c c b a s)
ary.shuffle!
ary.permutation.lazy.find { |a| a.slice_when { |a, b| a == b }.to_a.size == 1 }
end
require 'benchmark'
n = 500
Benchmark.bm do |x|
x.report { looping }
x.report { shuffle_permute }
end
Code
def reorder(arr)
groups = arr.group_by { |h| h[:letter] }
return nil if 2 * groups.map { |_,v| v.size }.max > arr.size + 1
max_key = groups.max_by { |_,a| a.size }.first
letters = ([max_key] + (groups.keys - [max_key])).cycle
ordered = []
while ordered.size < arr.size
k = letters.next
ordered << groups[k].pop unless groups[k].empty?
end
ordered
end
nilis returned if it is not possible to rearrange the elements in such a way that g[:letter] != h[:letter] for all pairs of consecutive elements g and h.
Note that this method has near linear computational complexity, O(arr.size), "near" because hash lookups are not quite constant time.
If desired, one could call the method with arr randomized: reorder(arr.shuffle).
Example
arr = [
{ letter: "a" }, { letter: "e" }, { letter: "b" }, { letter: "b" },
{ letter: "e" }, { letter: "a" }, { letter: "a" }, { letter: "f" }
]
reorder(arr)
#=> [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"},
# {:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"a"}]
Proof
The assertion is that if the line
return nil if 2 * groups.map { |_,v| v.size }.max > arr.size + 1
were removed from the method the array returned by the method would have the property that for all pairs of successive elements, g, h, g[:letter] != h[:letter] if and only if
2 * groups.map { |_,v| v.size }.max <= arr.size + 1
The proof has two parts.
The above inequality holds if the method produces a valid array
Compute
max_key = groups.max_by { |_,a| a.size }.first
max_key_freq = groups.map { |_,v| v.size }.max
and assume a valid array is returned. There must be at least one element other than max_key between each successive value of max_key in that array. The number of elements of arr other than max_key must therefore be at least max_key_freq - 1, so that
max_key_freq + max_key_freq - 1 <= arr.size
Hence,
2 * max_key_freq <= arr.size + 1
which is the same as:
2 * groups.map { |_,v| v.size }.max <= arr.size + 1
The above inequality does not hold if the method produces an invalid array
Suppose ordered is returned and it contains successive elements g and h for which both g[:letter] and h[:letter] equal the same letter l.
Because of the way ordered is constructed:
groups[k] must be empty for all keys k in groups for which k != l;
f[:letter] must equal l for all elements of ordered following g (if there are any); and
l must be the first key enumerated by keys, which is a letter that appears with a frequency that is not less than that of any other letter. l has frequency groups.map { |_,v| v.size }.max.
If n = groups.keys.size there must be a non-negative integer k (loosely, the number of rounds of allocations for all keys of groups) such that the number of elements h of arr for which h[:letter] != l equals k*n and the number of elements h of arr for which h[:letter] == l is k*n + 2 + m, where m >= 0. The size of arr is therefore 2*k*n + 2 + m.
In that case,
2 * groups.map { |_,v| v.size }.max > arr.size + 1
-> 2 * (k*n + 2 + m) > (k*n + 2 + m + k*n) + 1
-> 2*k*n + 4 + 2*m > 2*k*n + 3 + m
-> (4-3) + m > 0
-> true
Explanation
For the example,
groups = arr.group_by { |h| h[:letter] }
#=> {"a"=>[{:letter=>"a"}, {:letter=>"a"}, {:letter=>"a"}],
# "e"=>[{:letter=>"e"}, {:letter=>"e"}],
# "b"=>[{:letter=>"b"}, {:letter=>"b"}],
# "f"=>[{:letter=>"f"}]}
The following tells us that a solution exists.
2 * groups.map { |_,v| v.size }.max > arr.size + 1
#=> 2 * [3, 2, 2, 1].max > 8 + 1
#=> 2 * 3 > 9
#=> 6 > 9
#=> false
Next create an enumerator letters.
max_key = groups.max_by { |_,a| a.size }.first
#=> "a"
letters = ([max_key] + (groups.keys - [max_key])).cycle
#=> #<Enumerator: ["a", "e", "b", "f"]:cycle>
The elements of letters are generated as follows.
letters.next #=> "a"
letters.next #=> "e"
letters.next #=> "b"
letters.next #=> "f"
letters.next #=> "a"
letters.next #=> "e"
... ad infinititum
See Array#cycle.
I can best explain the remaining calculations by salting the method with puts statements before running the method. Note that arr.size #=> 8.
def reorder(arr)
groups = arr.group_by { |h| h[:letter] }
puts "groups = #{groups}"
return nil if 2 * groups.map { |_,v| v.size }.max > arr.size + 1
max_key = groups.max_by { |_,a| a.size }.first
letters = ([max_key] + (groups.keys - [max_key])).cycle
ordered = []
while ordered.size < arr.size
puts "\nordered.size = #{ordered.size} < #{arr.size} = #{ordered.size < arr.size}"
k = letters.next
puts "k = #{k}"
puts "groups[#{k}].empty? = #{groups[k].empty?}"
ordered << groups[k].pop unless groups[k].empty?
puts "ordered = #{ordered}"
puts "groups = #{groups}"
end
ordered
end
reorder(arr)
#=> [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"},
# {:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"a"}]
The following is displayed.
groups = {"a"=>[{:letter=>"a"}, {:letter=>"a"}, {:letter=>"a"}],
"e"=>[{:letter=>"e"}, {:letter=>"e"}],
"b"=>[{:letter=>"b"}, {:letter=>"b"}],
"f"=>[{:letter=>"f"}]}
ordered.size = 0 < 8 = true
k = a
groups[a].empty? = false
ordered = [{:letter=>"a"}]
groups = {"a"=>[{:letter=>"a"}, {:letter=>"a"}],
"e"=>[{:letter=>"e"}, {:letter=>"e"}],
"b"=>[{:letter=>"b"}, {:letter=>"b"}],
"f"=>[{:letter=>"f"}]}
ordered.size = 1 < 8 = true
k = e
groups[e].empty? = false
ordered = [{:letter=>"a"}, {:letter=>"e"}]
groups = {"a"=>[{:letter=>"a"}, {:letter=>"a"}],
"e"=>[{:letter=>"e"}],
"b"=>[{:letter=>"b"}, {:letter=>"b"}],
"f"=>[{:letter=>"f"}]}
ordered.size = 2 < 8 = true
k = b
groups[b].empty? = false
ordered = [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}]
groups = {"a"=>[{:letter=>"a"}, {:letter=>"a"}],
"e"=>[{:letter=>"e"}],
"b"=>[{:letter=>"b"}],
"f"=>[{:letter=>"f"}]}
ordered.size = 3 < 8 = true
k = f
groups[f].empty? = false
ordered = [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"}]
groups = {"a"=>[{:letter=>"a"}, {:letter=>"a"}],
"e"=>[{:letter=>"e"}], "b"=>[{:letter=>"b"}],
"f"=>[]}
ordered.size = 4 < 8 = true
k = a
groups[a].empty? = false
ordered = [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"},
{:letter=>"a"}]
groups = {"a"=>[{:letter=>"a"}],
"e"=>[{:letter=>"e"}],
"b"=>[{:letter=>"b"}],
"f"=>[]}
ordered.size = 5 < 8 = true
k = e
groups[e].empty? = false
ordered = [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"},
{:letter=>"a"}, {:letter=>"e"}]
groups = {"a"=>[{:letter=>"a"}],
"e"=>[],
"b"=>[{:letter=>"b"}],
"f"=>[]}
ordered.size = 6 < 8 = true
k = b
groups[b].empty? = false
ordered = [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"},
{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}]
groups = {"a"=>[{:letter=>"a"}], "e"=>[], "b"=>[], "f"=>[]}
ordered.size = 7 < 8 = true
k = f
groups[f].empty? = true
ordered = [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"},
{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}]
groups = {"a"=>[{:letter=>"a"}], "e"=>[], "b"=>[], "f"=>[]}
ordered.size = 7 < 8 = true
k = a
groups[a].empty? = false
ordered = [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"},
{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"a"}]
groups = {"a"=>[], "e"=>[], "b"=>[], "f"=>[]}
Refering to the revised question, if
arr = ["a", "a", "b", "b", "c", "c", "d", "d", "e", "e"]
one could simply write:
arr.each_slice(arr.index { |s| s != arr.first }.to_a.transpose.flatten
#=> ["a", "b", "c", "d", "e", "a", "b", "c", "d", "e"]
or
arr.each_slice(arr.count(arr.first)).to_a.transpose.flatten
This sounds a lot like backtracking.
I would build the "shuffled" array from left to right.
Assume that there are N elements in the array. Say that at some point during the algorithm, you have already the first k elements arranged to fulfil the condition.
Now you pick from the remaining (N-k) elements the first one, which you can append to your result array, without breaking the condition.
If you can find one, you repeat the process recursively, now having an result array of (k+1) elements.
If you can not find one, you return a failure indicator and let the caller (i.e. the previous recursion) try another choice.

Return string value to array from loop (Ruby)

Not sure if that's the term I should use for it but what I'm trying to do is add len amount of characters to an array, then output that to a .txt. I have the generation done to my satisfaction but I'm not sure how to pack the strings into an array. Right now it just spits out all the strings into the console because of the puts statement, just to make sure it works.
#Password list generator by Nightc||ed, ©2015
norm = ["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"]
caps = ["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"]
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
puts "How many passwords would you like to generate?"
num = gets.to_i
system "cls"
puts "Password length (1-x):"
len = gets.to_i
system "cls"
puts """
Which characters would you like to use?
[1] a-z, 0-9
[2] A-Z, 0-9
[3] a-z, A-Z, 0-9
"""
arr = []
type = gets.chomp
if type == "1"
arr = [norm,nums]
elsif type == "2"
arr = [caps,nums]
elsif type == "3"
arr = [norm,caps,nums]
else
exit
end
num.times do |pass|
len.times do |char|
arr2 = arr.to_a.sample
char = arr2.to_a.sample
puts char
end
end
sleep
here your code simplified
#Password list generator by Nightc||ed, ©2015
norm = [*"a".."z"]
caps = [*"A".."Z"]
nums = [*0..9]
num, len, type = [
"How many passwords would you like to generate?",
"Password length (1-x):",
"Which characters would you like to use?
[1] a-z, 0-9
[2] A-Z, 0-9
[3] a-z, A-Z, 0-9"].map do |msg|
puts msg
gets.to_i
end
arr = case type
when 1 then
norm + nums
when 2 then
caps + nums
when 3 then
norm + caps + nums
else
exit
end
passwords = num.times.map { arr.sample(len).join }
puts passwords.inspect
sleep
I think you can simplify your life by replacing the if... and below with the following:
case type
when "1"
arr = [norm,nums].flatten
when "2"
arr = [caps,nums].flatten
when "3"
arr = [norm,caps,nums].flatten
else
exit
end
passwd_set = []
num.times { passwd_set << arr.sample(len).join }
p passwd_set
I find case statements easier to read, and more easily extended. Flattening the arrays makes it so sample can directly produce the desired number of characters/symbols, and those can be joined to produce a string which can be appended to your passwd_set array.
You can add to an array using the << method. For example:
arr = []
3.times do |el|
arr << el
end
arr.inspect #=> [0, 1, 2]
Another option would be the push method:
arr = []
(0..2).each { |el| arr.push(el)}

Palindrome count in a string

So, I explored www.hackerearth.com today and was solving my first problem statement in ruby: http://www.hackerearth.com/problem/algorithm/palindrome-count-1/
Palindrome count Problem:
Given a string S, count the number of non empty sub strings that are palindromes.
A sub string is any continuous sequence of characters in the string.
A string is said to be palindrome, if the reverse of the string is same as itself.
Two sub strings are different if they occur at different positions in S
Input: Input contains only a single line that contains string S.
Output: Print a single number, the number of sub strings that are palindromes.
Constraints
1 <= |S| <= 50
S contains only lower case latin letters, that is characters a to z.
Sample Input (Plaintext Link): dskjkd
Sample Output (Plaintext Link): 7
Explanation -
The 7 sub strings are d, s, k, j, k, d, kjk.
Time limit 3 sec(s)
Memory limit 256 MB
Source limit 1024 KB
Here is what I did:
chars = gets.chomp.gsub(' ', '').split('')
counts = chars.count
(2..chars.count).each do |len|
chars.combination(len).each do |comb|
string = comb.inject(:<<)
counts += 1 if string.reverse == string
end
end
puts counts
However, this approach seems to be inefficient in terms of the time execution and memory usage. Is there any way to optimize this? Or have any other approach to this solution, algorithm is also welcome as solution! Thanks.
Edit
Since, all the answers are correct. I had to choose the one which is efficient. So, I ran benchmark and here is the result: https://gist.github.com/suryart/7577481
Based on the result you can see this answer is much faster. Thank you for the new approaches/ solution guys! :)
This approach -- in pseudo-code -- should work.
input: String s
// each single letter is palindrome itself
palindromsCount = length(s)
// let count all odd-length palindromes first (palindrome of length 1 already counted)
// we will be checking from the very middle of a sub-string, if it is symmetric
for(int i = 1; i < length(s)-1; i++)
for(int j = 1; ; j++)
if (i - j < 0 || i + j >= length(s) || s[i-j] != s[i+j])
break
else
palindromsCount += 1
// let count in similar way all even-length palindromes
for(int i = 0; i < length(s)-1; i++)
for(int j = 0; ; j++)
if (i - j < 0 || i + j + 1 >= length(s) || s[i-j] != s[i+j+1])
break
else
palindromsCount += 1
EDIT Of course both loops can be combined into a single one -- I did not want to do it for better readability.
using the algorithm to get all subsets of the string from What is the best way to split a string to get all the substrings by Ruby?
count = 0
(0..len-1).each do |i|
(i..len-1).each do |j|
temp = s[i..j]
count = count + 1 if temp == temp.reverse
end
end
puts "found #{count} palindromes"
Enumerable#each_cons is handy here:
str = "momanddadpaddledthekayak"
b = str.chars
(1..b.size).reduce(0) {|t,n| t + b.each_cons(n).reduce(0) \
{|r,e| w = e.join; w==w.reverse ? r + 1 : r}} # => 30
If we want to see the palendromes:
b = str.chars
pals = (1..b.size).each_with_object([]) {|n, a| b.each_cons(n).each \
{|e| w = e.join; a << w if w==w.reverse}}
p pals.size # => 30
p pals # => ["m", "o", "m", "a", "n", "d", "d", "a", "d", "p", "a",\
"d", "d", "l", "e", "d", "t", "h", "e", "k", "a", "y",
"a", "k", "dd", "dd", "mom", "dad", "aya", "kayak"]
Edit: #squiguy made the useful observation that we may not want to count duplicates. If that's the case, my first calculation above could not be used and the second would have to be changed as squiguy suggests (e.g., p a.uniq.size) or changed to build a hash rather than an array:
b = str.chars
pals = (1..b.size).each_with_object({}) {|n,h| b.each_cons(n).each \
{|e| w = e.join; h[w] = 0 if w==w.reverse}}.keys
p pals.size # => 17
p pals# => ["m", "o", "a", "n", "d", "p", "l", "e", "t",\
"h", "k", "y", "dd", "mom", "dad", "aya", "kayak"]
[Edit: replaced each with each_with_object. On rereading the question, it appears that dups are to be counted.]

%w in ruby makes code not work?

Please compare the 2 codes (the first returning the correct value of false while the second returns a value of true) The only difference in the codes is the %w. Why does the %w cause this problem?
#1
def ordered_vowel_word?(word)
vowels = ["a", "e", "i", "o", "u"]
letters_arr = word.split("")
vowels_arr = letters_arr.select { |l| vowels.include?(l) }
(0...(vowels_arr.length - 1)).all? do |i|
vowels_arr[i] <= vowels_arr[i + 1]
end
end
ordered_vowel_word?("complicated")
#2
def ordered_vowel_word?(word)
vowels = %w[a, e, i, o, u]
letters_arr = word.split("")
vowels_arr = letters_arr.select { |l| vowels.include?(l) }
(0...(vowels_arr.length - 1)).all? do |i|
vowels_arr[i] <= vowels_arr[i + 1]
end
end
ordered_vowel_word?("complicated")
You should not be using it with commas. That is half the purpose of using this notation. Correctly:
%w[a e i o u]
Use this instead:
vowels = %w[a e i o u]
Commas are not needed.
irb(main):001:0> %w[a, e, i, o, u]
=> ["a,", "e,", "i,", "o,", "u"]
irb(main):002:0> %w[a e i o u]
=> ["a", "e", "i", "o", "u"]
As you can see, %w will treat its contents as a string and split by space.

How to determine named group if regex is input by user

str = "cruel world"
#pattern can be /(?<a>.)(?<b>.)/ OR /(?<b>.)(?<a>.)/
#which was inputted by user, we don't know which one will be picked up by user.
pattern = params[:pattern]
please using str.scan(/#{pattern}/) or other match methods expect output:
p a
# ["c", "u", "l", "w", "r"]
p b
# ["r", "e", " ", "o", "l"]
p c
# [ ]
# there is no named group: `?<c>` in this case. However, we should take it if user inputted.
This is my solution:
str = "cruel world"
#case 1
pattern = /(?<a>.)(?<b>.)/
a = Array.new
b = Array.new
c = Array.new
str.scan(/#{pattern}/) do |x|
a << Regexp.last_match(:a) if $~.names.include? "a"
b << Regexp.last_match(:b) if $~.names.include? "b"
c << Regexp.last_match(:c) if $~.names.include? "c"
end
p a
p b
p c
is there a better way?
Here's my solution
def find_named_matches(str, pattern)
names = pattern.names
return Hash[names.zip [[]] * names.size] unless str =~ pattern
Hash[names.zip str.scan(pattern).transpose]
end
And tests
describe 'find_named_matches' do
example 'no matches' do
find_named_matches('abcabcabc', /(?<a>x.)(?<b>.)/).should == {'a' => [], 'b' => []}
end
example 'simple match' do
find_named_matches('abc', /(?<a>.)/).should == {'a' => %w[a b c]}
end
example 'complex name' do
find_named_matches('abc', /(?<Complex name!>.)/).should == {'Complex name!' => %w[a b c]}
end
example 'two simple variables' do
find_named_matches('cruel world', /(?<a>.)(?<b>.)/).should ==
{'a' => %w[c u l w r], 'b' => %w[r e \ o l]}
end
example 'two simple variables' do
find_named_matches('cruel world', /(?<b>.)(?<a>.)/).should ==
{'b' => %w[c u l w r], 'a' => %w[r e \ o l]}
end
example "three variables and matched chars that aren't captured" do
find_named_matches('afk1bgl2chm3', /(?<a>.)(?<f>.)(?<k>.)./).should ==
{'a' => %w[a b c], 'f' => %w[f g h], 'k' => %w[k l m]}
end
example 'complex regex' do
find_named_matches("the dog's hog is the cat's rat", /(?<nouns>(?:(?<=the |'s ))\w+)/).should ==
{'nouns' => %w[dog hog cat rat]}
end
end

Resources