1

I had a friend of mine show me an interesting problem he had on a coding challange at an interview. So you have a number like n = 3413289830 and a pattern like p = a-bcdefghij, you need to create a function that takes this input and outputs -413289827. Obviously it should work for any combination of numbers and letters for addition and subtraction. I worked out this code but I am pretty sure it can be improved as I think it's a bit inefficient.

pattern = 'a-bcdefghij'       
n = '3413289830'
lst = []
def splitnb(n, pattern):
    save = dict()
    if(len(n) != len(pattern) - 1):
        print('Pattern needs to match number')
    else:
        if( '-' in pattern):
            patlst = pattern.split('-')
        elif('+' in pattern):
            patlst = pattern.split('+')
        for char in n:
            a = list(n)
        for pat in patlst:
            first  = patlst[0].split()
            rest = pat.split()
        for l in first[0]:
            f1 = l
            lst.append(f1)
        for l2 in rest[0]:
            f2 = l2
            lst.append(f2)
        save = dict(zip(lst, a))
        nb = ''
        if( '-' in pattern):
            for i in first[0]:
                numb = int(save.get(i))
            for j in rest[0]:
                nb += save.get(j)
                numb1 = numb - int(nb)
        elif('+' in pattern):
            for i in first[0]:
                numb = int(save.get(i))
            for j in rest[0]:
                nb += save.get(j)
                numb1 = numb + int(nb)
    return numb1

f1 = splitnb(n, pattern)
f2 = splitnb('1234', 'ab+cd')
f3 = splitnb('22', 'a-b')
glennsl
  • 28,186
  • 12
  • 57
  • 75
thouxanstx
  • 11
  • 1
  • If there is a `-`, there cannot be a `+`? – AMC Nov 30 '19 at 02:40
  • @AMC obviously that would be ideal, I thought of it only at the level that it contains one operation only – thouxanstx Feb 23 '20 at 21:28
  • I had forgotten all about this. Quick question: why are you using `dict.get()` everywhere? – AMC Feb 23 '20 at 21:29
  • @AMC , I am iterating over the dictionary and using dict.get(index) to get the item at that index and calculate the number – thouxanstx Feb 23 '20 at 21:34
  • @AMC, I am basically splitting the whole operation in 2 lists, the first and the rest, then zip them up in a dict the iterate over them in order to calculate the result. I know it's not by far the best way, but it was done first try and in abour 35-40 mins so, I could have worked on it more obviously – thouxanstx Feb 23 '20 at 21:38
  • Oh damn I didn’t even realize you were iterating over the dict. First of all, the standard method for indexing dicts is using brackets, like lists. Second, you can iterate over the keys and values of a dict at the same time, using `.items()`. – AMC Feb 23 '20 at 22:24
  • In fact, I’ll have to take a look, but “zip-dict-iterate” sounds like it should just be “zip”. Did this question never get answered, by the way? – AMC Feb 23 '20 at 22:25
  • @AMC, sorry, my bad. was not iterating over the dictionary actually. the dictionary contains all the letters in the pattern as keys and the corresponding index in the pattern as value. I am itterating on the first part of the pattern then on the second one – thouxanstx Feb 24 '20 at 01:57
  • @AMC and there was no question tbh, just wanted to see how I could improve this. – thouxanstx Feb 24 '20 at 01:57

3 Answers3

0

One way to do this is to take the pattern and replace each char with the number that should be there and then eval the result

string.ascii_letters is a string of all ascii characters in alphabetical order starting with lowercase. This can be used to convert the char into the index of the digit that should be extracted from n

>>> [n[string.ascii_letters.index(x)] if x in string.ascii_letters else x for x in pattern]
['3', '-', '4', '1', '3', '2', '8', '9', '8', '3', '0']

We add if x in string.ascii_letters else x so that the operators are not converted. Then you join the resulting list to get the string

>>> ''.join(n[string.ascii_letters.index(x)] if x in string.ascii_letters else x for x in pattern)
'3-413289830'

Removing the brackets turns the list comprehension into a generator which should be slightly more performant. You can then use eval to run this string as if it was python code

>>> eval(''.join(n[string.ascii_letters.index(x)] if x in string.ascii_letters else x for x in pattern))
-413289827

You should only use eval if you trust the input that you are being given as it can execute arbitrary code

Iain Shelvington
  • 31,030
  • 3
  • 31
  • 50
  • @AlexanderCécile there is a question here that deals with this problem although it involves installing a third-party package https://stackoverflow.com/questions/43836866/safely-evaluate-simple-string-equation – Iain Shelvington Nov 30 '19 at 02:51
  • Oh no I meant like without anything `eval()`-style. – AMC Nov 30 '19 at 08:38
0

it can be made bit small and clean like below using regex split and zip_longest

import re
from itertools import zip_longest

pattern = 'a-bcdefghij'       
n = '3413289830'

lst = re.split("[^a-z]", pattern)
oprs = [None] + re.findall("[^a-z]", pattern)

result = 0
for x, opr in zip_longest(lst, oprs):
    op = int(n[:len(x)])
    n = n[len(x):]

    if opr == "-":
        op = -op
    result+=op


result

Dev Khadka
  • 5,142
  • 4
  • 19
  • 33
0

You want to

  • identify the operation that should be applied
  • find the position of the split
  • apply the operation using the parts of the split as operands

One approach could be to loop over the pattern to find the operand and its position in the list, which is also the position of the split, then match the operator to the function in the operator module that performs the required operation. If using the operator module isn't allowed, you can write your own functions.

import operator

operators = { 
    '+': operator.add,
    '-': operator.sub,
    '*': operator.mul,
    '/': operator.truediv,
}
# operator not allowed?  Then '+': def add(a, b):return a + b etc.

def splitnb(n, pattern):
    if len(n) != len(pattern) - 1:
        print("Pattern needs to match number")
        return
    # Find the operator and its position.
    for _i, op in enumerate(pattern):
        if not op.isalpha():
            break
    # Lookup the operator and execute it.
    return operators[op](int(n[:_i]), int(n[_i:]))

If pattern is allowed to not contain an operator, or the operator could be at the beginning or end of the number string then you would need to add some validation for these cases.

snakecharmerb
  • 47,570
  • 11
  • 100
  • 153