overstack
overstack

Reputation: 3

String replacement combinations with fixed parts

Let's say I have the following string abcixigea and I want to replace the first 'i', 'e' and the second 'a' with '1', '3' and '4', getting all the combinations with those "progressive" replacement.

So, I need to get:
abc1xigea
abcixig3a
abcixig34
abc1xige4
...and so on.

I tried with itertools.product following this question python string replacement, all possible combinations #2 but the result I get is not exactly what I need and I can see why.
However I'm stuck on trying with combinations and keeping parts of the string fixed (changing just some chars as explained above).

Upvotes: 0

Views: 133

Answers (1)

Hugh Bothwell
Hugh Bothwell

Reputation: 56674

from itertools import product

s = "abc{}xig{}{}"

for combo in product(("i", 1), ("e", 3), ("a", 4)):
    print(s.format(*combo))

produces

abcixigea
abcixige4
abcixig3a
abcixig34
abc1xigea
abc1xige4
abc1xig3a
abc1xig34

Edit: in a more general way, you want something like:

from itertools import product

def find_nth(s, char, n):
    """
    Return the offset of the nth occurrence of char in s,
      or -1 on failure
    """
    assert len(char) == 1
    offs = -1
    for _ in range(n):
        offs = s.find(char, offs + 1)
        if offs == -1:
            break
    return offs

def gen_replacements(base_string, *replacement_values):
    """
    Generate all string combinations from base_string
      by replacing some characters according to replacement_values

    Each replacement_value is a tuple of
      (original_char, occurrence, replacement_char)
    """
    assert len(replacement_values) > 0
    # find location of each character to be replaced
    replacement_offsets = [
        (find_nth(base_string, orig, occ), orig, occ, (orig, repl))
        for orig,occ,repl in replacement_values
    ]
    # put them in ascending order
    replacement_offsets.sort()
    # make sure all replacements are actually possible
    if replacement_offsets[0][0] == -1:
        raise ValueError("'{}' occurs less than {} times".format(replacement_offsets[0][1], replacement_offsets[0][2]))
    # create format string and argument list
    args = []
    for i, (offs, _, _, arg) in enumerate(replacement_offsets):
        # we are replacing one char with two, so we have to
        # increase the offset of each replacement by
        # the number of replacements already made
        base_string = base_string[:offs + i] + "{}" + base_string[offs + i + 1:]
        args.append(arg)
    # ... and we feed that into the original code from above:
    for combo in product(*args):
        yield base_string.format(*combo)

def main():
    s = "abcixigea"

    for result in gen_replacements(s, ("i", 1, "1"), ("e", 1, "3"), ("a", 2, "4")):
        print(result)

if __name__ == "__main__":
    main()

which produces exactly the same output as above.

Upvotes: 1

Related Questions