1

I want to do string substitution. With gsub or tr I can give a single input character and map it to a single output value but I want to create multiple output strings based on multiple mappings:

swap = { 
  'a' => ['$', '%', '^'],
  'b' => ['3'],
  'c' => ['4', '@'],
}

For input string 'abc', I should get the following output strings:

  • '$34'
  • '$3@'
  • '%34'
  • '%3@'
  • '^34'
  • '^3@'

Is there an easy way to do this for an arbitrary number of inputs and mappings? In reality it is likely to be about 10 inputs and at most 3 mappings, usually only one.

sawa
  • 165,429
  • 45
  • 277
  • 381
Robin
  • 43
  • 1
  • 4
  • It's as simple (in this case) as having three nested loops (which enumerate all combinations) and in the innermost loop you call tr/gsub. – Sergio Tulentsev Jul 29 '17 at 21:11
  • Although making a generic implemenation is, at least, non-trivial, I admit. Give it a try, though. – Sergio Tulentsev Jul 29 '17 at 21:15
  • Robin, thanks for choosing my answer, but you really should wait awhile (perhaps 2+ hours) before awarding the greenie. A quick selection can discourage other answers and, imo, is discourteous to others still working on answers. There's no rush. Please consider removing the checkmark and deciding later. – Cary Swoveland Jul 29 '17 at 21:36

1 Answers1

6
def gen_products(swap, str)
  swap_all = Hash.new { |_,k| [k] }.merge(swap) 
  arr = swap_all.values_at(*str.chars)
  arr.shift.product(*arr).map(&:join)
end

See Hash::new (with a block), Hash#values_at and Array#product. If h = Hash.new { |_,k| [k] } and h does not have a key k, h[k] returns [k].

swap = { 'a'=>['$', '%', '^'], 'b'=>['3'], 'c'=>['4', '@'] }

gen_products(swap, "abc")
  #=> ["$34", "$3@", "%34", "%3@", "^34", "^3@"]

Here

swap_all = Hash.new { |_,k| [k] }.merge(swap) 
  #=> {"a"=>["$", "%", "^"], "b"=>["3"], "c"=>["4", "@"]}
vals = swap_all.values_at(*str.chars)
  #=> [["$", "%", "^"], ["3"], ["4", "@"]]

Another example:

gen_products(swap, "bca")
  #=> ["34$", "34%", "34^", "3@$", "3@%", "3@^"]

and one more:

gen_products(swap, "axbycx")
  #=> ["$x3y4x", "$x3y@x", "%x3y4x", "%x3y@x", "^x3y4x", "^x3y@x"]

Here

swap_all = Hash.new { |_,k| [k] }.merge(swap)
  #=> {"a"=>["$", "%", "^"], "b"=>["3"], "c"=>["4", "@"]}
vals = swap_all.values_at(*str.chars)
  #=> [["$", "%", "^"], ["x"], ["3"], ["y"], ["4", "@"], ["x"]]
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • 1
    Oh, that's clever :) Forgot about `.product`. Reimplemented it recursively. Which doesn't look as sexy. – Sergio Tulentsev Jul 29 '17 at 21:29
  • 4
    You can decompose the array(s) during assignment via `first, *others = swap.values_at(*str.chars)` in order to shorten the method's second line to `first.product(*others)` – Stefan Jul 29 '17 at 21:39
  • Good idea, @Stefan. I knew there was something not quite right. I'll change it. Thanks. – Cary Swoveland Jul 29 '17 at 21:51
  • @Stefan,you *missed something*!! I could hardly believe my eyes. Thinking that `product` (like `combination`, `permutation`, etc.) returned an enumerator, I had tacked on `.to_a`. But it then dawned on me that it returns an array, so I just removed `.to_a`. – Cary Swoveland Jul 29 '17 at 22:00
  • 1
    @Stefan, another (less desirable, imo) way is `arr = swap.values_at(*str.chars); arr.shift.product(*arr)`. – Cary Swoveland Jul 29 '17 at 22:05
  • I've had second thought on `arr.shift.product(*arr)` being inferior. It does only require that a single variable be defined. – Cary Swoveland Jul 30 '17 at 18:43
  • 1
    Just realised, this fails if there are characters in str that aren't in the swap array, i.e. you call it like this: `gen_products(swap, "abcxyz")` is that an easy fix? – Robin Aug 11 '17 at 21:13
  • 1
    What I'd like is to leave the xyz on the end of all the swaps. – Robin Aug 11 '17 at 21:19
  • Sorry for slow reply, didn't get any email alerts from SO. That fixed it, thanks very much. – Robin Aug 14 '17 at 08:44