I’m trying to show all possible options for substring replacements. For example, if I have this string:
my_string = “yellow dogs are cooler than brown cats”
and here are the different options that could be replaced:
substitutions = {“yellow” => “black”, “brown” => “grey”}
How would I get the following output?
["black dogs are cooler than brown cats", "black dogs are cooler than grey cats", "yellow dogs are cooler than brown cats", "yellow dogs are cooler than grey cats"]
So far I just have this, and as you can see, it’s not getting all options:
substitutions.each do |key, value|
puts my_string.sub(key, value) if my_string.include? key
end
What you really need is to turn this
substitutions = {“yellow” => “black”, “brown” => “grey”}
into this:
[{"yellow"=>"yellow", "brown"=>"brown"},
{"yellow"=>"yellow", "brown"=>"grey"},
{"yellow"=>"black", "brown"=>"brown"},
{"yellow"=>"black", "brown"=>"grey"}]
Then you can trivially loop over the above and get your desired output (you initially omitted the identity transformation, but it's most straightforward to include it). Here is one way to change substitutions into that:
substitutions.to_a[0].product(substitutions.to_a[1]).map do |ary|
Hash[substitutions.keys.zip ary]
end
Then it's straightforward:
_.each do |h|
p my_string.split.map {|word| h.fetch(word,word) }.join(' ')
end
"yellow dogs are cooler than brown cats"
"yellow dogs are cooler than grey cats"
"black dogs are cooler than brown cats"
"black dogs are cooler than grey cats"
here I opted for split -> sub each word -> rejoin over a global sub operation, since you presumably want to replace "brown" but not "brownie". On the other hand this doesn't handle punctuation very gracefully, so you may opt for doing sub or some more sophisticated regex op. Up to you.
Here's a generic solution that works for any number of substitutions (not just two):
my_string = "yellow dogs are cooler than brown cats"
substitutions = {"yellow"=>"black", "brown"=>"grey", "cooler"=>"sweeter"}
keys = substitutions.keys #=> ["yellow","brown","cooler"]
regx = /\b#{Regexp.union(keys)}\b/ #=> /\b(?:yellow|brown|cooler)\b/
axes = substitutions.to_a #=> [["yellow", "black"], ["brown", "grey"], ["cooler", "sweeter"]]
maps = axes.shift.product(*axes).map{ |*vals| Hash[ keys.zip(*vals) ] }
maps.each{ |map| p map, my_string.gsub(regx){ |match| map[match] } }
#=> {"yellow"=>"yellow", "brown"=>"brown", "cooler"=>"cooler"}
#=> "yellow dogs are cooler than brown cats"
#=> {"yellow"=>"yellow", "brown"=>"brown", "cooler"=>"sweeter"}
#=> "yellow dogs are sweeter than brown cats"
#=> {"yellow"=>"yellow", "brown"=>"grey", "cooler"=>"cooler"}
#=> "yellow dogs are cooler than grey cats"
#=> {"yellow"=>"yellow", "brown"=>"grey", "cooler"=>"sweeter"}
#=> "yellow dogs are sweeter than grey cats"
#=> {"yellow"=>"black", "brown"=>"brown", "cooler"=>"cooler"}
#=> "black dogs are cooler than brown cats"
#=> {"yellow"=>"black", "brown"=>"brown", "cooler"=>"sweeter"}
#=> "black dogs are sweeter than brown cats"
#=> {"yellow"=>"black", "brown"=>"grey", "cooler"=>"cooler"}
#=> "black dogs are cooler than grey cats"
#=> {"yellow"=>"black", "brown"=>"grey", "cooler"=>"sweeter"}
#=> "black dogs are sweeter than grey cats"
The key here is the product call, which turns [[1,2],[3,4],[5,6]] into:
[[1, 3, 5],
[1, 3, 6],
[1, 4, 5],
[1, 4, 6],
[2, 3, 5],
[2, 3, 6],
[2, 4, 5],
[2, 4, 6]]
In other words, all the combinations from the calling array and all arrays passed in.
Using the regular expression with gsub makes it efficient, doing one pass through the string for each full set of replacements. The block form of gsub yields the found text, which is then used to look up the desired replacement (sometimes the same string as the original).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With