Given the string, find the number of possible codes the string can generate using Python

If a = 1, b = 2, c = 3, .... z = 26. I want to find the number of possible codes that a string can generate with Python

For example:

get_combination('12')
Output: 2
// The possible decodings are "AB", "L"

get_combination('121')
Output: 3
// The possible decodings are "ABA", "AU", "LA"

get_combination('1234')
Output: 3
// The possible decodings are "ABCD", "LCD", "AWD"

      

Here is the code for that. but the complexity of the time is greater. Maybe someone has a simpler solution than this

def get_combination(string):
    def get_branchs(string):
        if not string or len(string) == 1:
            return 0
        if string[0:2] <= '26':
            if '0' not in string[1:3]:
                return 1 + get_branchs(string[1:]) + get_branchs(string[2:])
            else:
                return get_branchs(string[2:])
        else:
            return get_branchs(string[1:])
    return 1 + get_branchs(string)

      

+3


source to share


5 answers


This is the simplest form I got:

def get_num_codes(number):
    if not number:
        return 1
    # If you want to allow for "stray zeros" at the end remove this if block
    if number == "0":
        return 0
    count = get_num_codes(number[1:])
    if number[0] != "0" and len(number) > 1 and number[:2] <= "26":
        count += get_num_codes(number[2:])
    return count

      



The performance of both versions is comparable:

%timeit get_combination("123412432414324231123412")
>>> 1000 loops, best of 3: 1.83 ms per loop
%timeit get_num_codes("123412432414324231123412")
>>> 1000 loops, best of 3: 1.87 ms per loop

      

+1


source


You can make the following points:

  • There may be invalid sequences: 0 must always precede either 1 or 2. If there is a violation of this rule, 0 must be returned (or a raised exception)
  • All minimum subsequences in the input that can increase the number of combinations start with 1 or 2. So, for example, a line that starts with 7 will produce as many combinations as the same input as 7 deleted.
  • Subsequential sequences that allow multiple combinations can be isolated from each other, since they also end in can never be matched with the next character being in the input.
  • Let the definition of "matching subsequence" be: any subsequence that returns 2 or more (number of combinations) if treated as a separate input, and that returns a smaller value if one character is made shorter (remove one character from either end) and not return a larger value if you make 1 character longer on each side of the subsequence (i.e. not on any character, but as a subsequence in the input, of course). The regex matching such matching subsequences is

    [12]*?(?:[3-6]|1[3-9]|(?=.0|$))
    
          

  • The number of combinations represented by one matching subsequence is determined by the following recursive form (where n

    is the length of the subsequence):

    C(n) = 1 when n == 1
         = 2 when n == 2
         = C(n-1) + c(n-2) when n > 2
    
          

    This is a Fibonacci series (shifted by one index position as true Fibonacci has Fib(2) = 1

    , but this is just a detail).

  • So, if we first find the longest matching subsequence, we would know how many Fibonacci numbers will generate (and no more!), And we only have to do this generation once. Then we can simply take the product of the Fibonacci numbers corresponding to the lengths of the subsequences.

Here is the commented code that implements these ideas:

import re
from operator import mul
from functools import reduce # For Python 3.x

def get_combination(string):
    def fib_to(n):
        fibs = [1, 1] # Real Fibonacci would need [0, 1], but we want fib[2] to be 2.
        for i in range(2, n+1):
            fibs.append(fibs[-1] + fibs[-2])
        return fibs

    # Detect an invalid sequence, where a 0 occurs without a 1 or 2 preceding it  
    if re.search(r"(?<![12])0", string):
        return 0
    # Find all sub sequences where a variation is possible
    sequences = re.findall(r"[12]*?(?:[3-6]|1[3-9]|(?=.0|$))", string)
    if not sequences:
        return 1
    # Get the sizes of all these sequences; it is all we need to know
    sizes = [len(seq) for seq in sequences]
    # Generate Fibonacci numbers up to the maximum needed:
    fib = fib_to(max(sizes))
    # Take the product of the sizes for each of the sequences
    return reduce(mul, [fib[size] for size in sizes], 1)

tests = [ # inputs combined with expected outputs
    ["", 1],
    ["2", 1],
    ["12", 2],
    ["121", 3],
    ["1234", 3],
    ["2121", 5],
    ["210", 1],
    ["210210", 1],
    ["22102210", 4],
    ["219219", 9],
    ["07", 0],
    ["507", 0]
]

for test in tests:
    res = get_combination(test[0])
    if res != test[1]: 
        print("Error - Input: '{}' Output: {} Expected: {}".format(test[0], res, test[1]))
    else:
        print("OK - Input: '{}' Output: {}".format(test[0], res))

      



See how it works on repl.it

Pay attention to the checked edge cases. Your code fails several of these tests.

At the time of writing, none of the answers give the correct result for entering "07" (which should return 0), and kashif's answer is the only one that only fails in this test.

Here is the test result for each of the answers at the time of writing: repl.it

+1


source


There is a dynamic programming option here: the number of combinations for a given suffix of your string (so the last N characters) is fixed regardless of what came before.

So, you can work from end to beginning of a line and store the number of combinations for each suffix. This should result in a significant reduction in execution time.

def get_combinations(input):
    store = [ 0 for _ in input ]
    store[len(input)-1] = 1
    store.append(1)

    idx = len(input) - 2
    while idx >= 0:
        can_make_double = input[idx:idx+2] <= '26'
        if can_make_double:
            store[idx] = store[idx+2] + store[idx+1]
        else:
            store[idx] = store[idx+1]
        idx = idx - 1
    return store[0]

for input in ['12', '121', '1234']:
    print("%s: %d" % (input, get_combinations(input)))

      

0


source


As Trincot commented, you can save time slicing strings by passing the offset to your function get_branch

, so you don't have to traverse string

or string

slice.

So here's my modified version, just using the offset as a parameter to the inner function get_branch

:

def get_combination(string):
    def get_branchs(offset=0):
        if len(string)-offset <= 1:
            return 0
        if string[offset:2+offset] <= '26':
            if '0' not in string[1+offset:3+offset]:
                return 1 + get_branchs(1+offset) + get_branchs(2+offset)
            else:
                return get_branchs(2+offset)
        else:
            return get_branchs(1+offset)
    return 1 + get_branchs()

      

There are a few remaining cuts to compare with 26 and to check if 0

the substring is present . Testing against indices would be an alternative, but I'm not sure how this would behave when the indices are out of bounds (slicing don't mind, accessing the index does)

Note. I hope that if you answer the question, perhaps codereview will be the best for this kind of question and answer.

0


source


The problem can be split into two parts if we start at the end of the line instead of the beginning:

  • If the last digit! = 0, repeat the procedure for the remaining digits and add the count to the result.
  • If the last two digits are less than 27, repeat the procedure for the remaining digits and add the count to the result.

More information can be found here: Count Possible decoding of a given digital sequence

Below is the version of Python C code using dynamic programming mentioned in the link:

def decodings(digits):
    n_digits =  len(digits)
    count_table = {}
    for i in range(0,n_digits+1):
        count_table[i]=0
    count_table[0] = 1
    count_table[1] = 1

    for i in range(2,n_digits+1):
        #Case 1: If the last digit!=0 , repeat and add the count to the result.
        if digits[i-1] > '0':
            count_table[i] = count_table[i-1]
        #Case 2 : If the last two digits are less than 27, repeat and the add the count to the result.
        if digits[i-2] == '1' or (digits[i-2] == '2' and digits[i-1] < '7'):
             count_table[i] += count_table[i-2]

    return count_table[n_digits]

print decodings('121')

      

Time complexity is O (n) and space complexity is O (n), where 'n' is the number of digits in the input.

0


source







All Articles