I was wondering if there is a simple way to do every combination of selected character substitutions in ruby in a simple way.
An example:
string
I'd do as follows using String#gsub(pattern, hash) :
string = "this is a test"
subs = {'a'=>'@','i'=>'!','s'=>'$'} # copied from @ArupRakshit
keys = subs.keys
And core code:
1.upto(keys.length).flat_map { |i|
keys.combination(i).flat_map { |c| string.gsub(/[#{c.join}]/, subs) }
}
Output:
=> ["this is @ test",
"th!s !s a test",
"thi$ i$ a te$t",
"th!s !s @ test",
"thi$ i$ @ te$t",
"th!$ !$ a te$t",
"th!$ !$ @ te$t"]
string = "this is a test"
subs = ['a'=>'@','i'=>'!','s'=>'$']
subs = subs.first.map(&:to_a)
1.upto(subs.length).each do |n|
subs.combination(n).each do |a|
p a.each_with_object(string.dup){|pair, s| s.gsub!(*pair)}
end
end
I'd do as below :
string = "this is a test"
subs = {'a'=>'@','i'=>'!','s'=>'$'}
keys = subs.keys
combinations = 1.upto(subs.size).flat_map { |i| keys.combination(i).to_a }
combinations.each do |ary|
new_string = string.dup
ary.each { |c| new_string.gsub!(c,subs) }
puts new_string
end
output
this is @ test
th!s !s a test
thi$ i$ a te$t
th!s !s @ test
thi$ i$ @ te$t
th!$ !$ a te$t
th!$ !$ @ te$t
Another way:
string = "this is a test"
subs = [{"a"=>"@"}, {"i"=>"!"}, {"s"=>"$"}]
subs.repeated_combination(subs.size)
.map {|e| string.gsub(/./) {|c| (g = e.find {|h| h.key?(c)}) ? g[c] : c}}
.uniq
#=> ["this is @ test", "th!s !s @ test", "thi$ i$ @ te$t", "th!$ !$ @ te$t",
# "th!s !s a test", "th!$ !$ a te$t", thi$ i$ a te$t"]
Explanation:
a = subs.repeated_combination(subs.size)
# Enumerator...
a.to_a
# [[{"a"=>"@"},{"a"=>"@"},{"a"=>"@"}], [{"a"=>"@"},{"a"=>"@"},{"i"=>"!"}],
# [{"a"=>"@"},{"a"=>"@"},{"s"=>"$"}], [{"a"=>"@"},{"i"=>"!"},{"i"=>"!"}],
# [{"a"=>"@"},{"i"=>"!"},{"s"=>"$"}], [{"a"=>"@"},{"s"=>"$"},{"s"=>"$"}],
# [{"i"=>"!"},{"i"=>"!"},{"i"=>"!"}], [{"i"=>"!"},{"i"=>"!"},{"s"=>"$"}],
# [{"i"=>"!"},{"s"=>"$"},{"s"=>"$"}], [{"s"=>"$"},{"s"=>"$"},{"s"=>"$"}]]
b = a.map {|e| string.gsub(/./) {|c| (g = e.find {|h| h.key?(c)}) ? g[c] : c}}
#=> ["this is @ test", "th!s !s @ test", "thi$ i$ @ te$t", "th!s !s @ test",
# "th!$ !$ @ te$t", "thi$ i$ @ te$t", "th!s !s a test", "th!$ !$ a te$t",
# "th!$ !$ a te$t", "thi$ i$ a te$t"]
To see how b
is computed, consider the second element of a
that is passed to the block:
e = [{"a"=>"@"},{"a"=>"@"},{"i"=>"!"}]
Because of the regex, /./
, gsub
passes each character c
of string
to the block
{|c| (g = e.find {|h| h.key?(c)}) ? g[c] : c}
A search is made of e
to determine if any of the three hashes has c
as a key. If one is found, namely, g
, the character c
is replaced with g[c]
; else, the character is left unchanged.
Notice that the first two elements of e
are the same. Efficiency could be improved by changing the first line to:
subs.repeated_combination(subs.size).map(&:uniq)
but efficiency is not one of the virtues of this approach.
Returning to the main calculation, the final step is:
b.uniq
#=> ["this is @ test", "th!s !s @ test", "thi$ i$ @ te$t", "th!$ !$ @ te$t",
# "th!s !s a test", "th!$ !$ a te$t", "thi$ i$ a te$t"]
A one-line functional solution
string = "this is a test"
subs = {'a'=>'@','i'=>'!','s'=>'$'}
(1..subs.size).flat_map { |n| subs.keys.combination(n).to_a }.map { |c| string.gsub(/[#{c.join}]/, subs) }
# => ["this is @ test", "th!s !s a test", "thi$ i$ a te$t", "th!s !s @ test", "thi$ i$ @ te$t", "th!$ !$ a te$t", "th!$ !$ @ te$t"]