Generate a number by using a given list of numbers and arithmetic operators

11

2

You are given a list of numbers L = [17, 5, 9, 17, 59, 14], a bag of operators O = {+:7, -:3, *:5, /:1} and a number N = 569.

Task

Output an equation that uses all numbers in L on the left-hand side and only the number N on the right-hand side. If this is not possible, output False. Example solution:

59*(17-5)-9*17+14 = 569

Limitations and Clarification

  • You may not concatenate numbers ([13,37] may not be used as 1337)
  • Only natural numbers and zero will appear in L.
  • The order in L doesn't matter.
  • You must use all numbers in L.
  • Only the operators +,-,*,/ will appear in O.
  • O can have more operators than you need, but at least |L|-1 operators
  • You may use each operator any number of times up to the value in O.
  • All four operations in O are the standard mathematical operations; in particular, / is normal division with exact fractions.

Points

  • The less points, the better
  • Every character of your code gives you one point

You have to provide an un-golfed version that is easy to read.

Background

A similar question was asked on Stack Overflow. I thought it might be an interesting code-golf challenge.

Computational Complexity

As Peter Taylor said in the comments, you can solve subset sum with this:

  1. You have an instance of subset sum (hence a set S of integers and a number x)
  2. L := S + [0, ..., 0] (|S| times a zero), N := x, O := {+:|S|-1, *: |S| - 1, /:0, -: 0}
  3. Now solve this instance of my problem
  4. The solution for subset sum is the numbers of S that don't get multiplied with zero.

If you find an Algorithm that is better than O(2^n), you prove that P=NP. As P vs NP is a Millennium Prize Problem and hence worth 1,000,000 US-Dollar, it is very unlikely that somebody finds a solution for this. So I removed this part of the ranking.

Test cases

The following are not the only valid answers, other solutions exist, and are permitted:

  • ([17,5,9,17,59,14], {+:7, -:3, *:5, /:1}, 569)
    => 59 * (17-5)- 9 * 17 + 14 = 569
  • ([2,2], {'+':3, '-':3, '*':3, '/':3}, 1)
    => 2/2 = 1
  • ([2,3,5,7,10,0,0,0,0,0,0,0], {'+':20, '-':20, '*':20, '/':20}, 16)
    => 5+10-2*3+7+0+0+0+0+0+0+0 = 16
  • ([2,3,5,7,10,0,0,0,0,0,0,0], {'+':20, '-':20, '*':20, '/':20}, 15)
    => 5+10+0*(2+3+7)+0+0+0+0+0+0 = 15

Martin Thoma

Posted 2013-02-26T08:37:44.387

Reputation: 669

Is m = |L|? If yes, how can you expect the runtime to not depend on the size of that list? For example, [2,2],[+,+,...,+,/],1. In fact, since n is O(m), you might just write it all in terms of m. – boothby – 2013-02-26T09:31:13.500

3What kind of arithmetic is this to use – exact fractionals, integer (/div), just floating-point and hope-for-no-rounding-errors, ...? – ceased to turn counterclockwis – 2013-02-26T12:02:17.140

4Why the complicated scoring rules for computational complexity? There's an easy reduction from subset-sum, so anything better than O(2^n) is worth a million USD. – Peter Taylor – 2013-02-26T13:14:48.127

@moose, I mistyped -- I meant "Is m = |O|?". As you affirm, my example (m-1 useless operators followed by one useful operator) shows that the problem is at least O(m): you can fix n and still make the runtime go off to infinity. – boothby – 2013-02-26T17:10:17.457

@PeterTaylor: Thanks, I didn't see it. I've removed this part of the ranking and added an explanation. – Martin Thoma – 2013-02-26T18:29:52.110

Though I'd imagine this could actually be done in subexponential time with extra constraints to prevent "degenerate" cases like || times a zero. But that would probably be really complicated, so... – ceased to turn counterclockwis – 2013-02-26T20:26:30.087

@belisarius Why don't you adapt your prior solution to the challenge at hand? At the very least it will provide an instructive look at how to avoid parentheses (through reverse Polish notation). – DavidC – 2013-02-27T15:58:34.403

@DavidCarraher I'll try. Need some spare time to do it :( – Dr. belisarius – 2013-02-27T17:03:58.763

13rd test case is not False... 5+10+2*3+7*0+0... – Shmiddty – 2013-02-27T18:36:33.990

It looks like the combination of 0,2,3,5,7,10 (with no practical restriction on operators) can actually generate quite a few numbers (pretty sure every whole number below 30, though I've only been doing the math in my head) – Shmiddty – 2013-02-27T20:03:21.387

@DavidCarraher I found that any short enough answer is too inefficient to be used with the OP's testcases. – Dr. belisarius – 2013-03-03T07:23:19.187

Answers

3

Python 2.7 / 478 chars

L=[17,5,9,17,59,14]
O={'+':7,'-':3,'*':5,'/':1}
N=569
P=eval("{'+l+y,'-l-y,'*l*y,'/l/y}".replace('l',"':lambda x,y:x"))
def S(R,T):
 if len(T)>1:
  c,d=y=T.pop();a,b=x=T.pop()
  for o in O:
   if O[o]>0 and(o!='/'or y[0]):
    T+=[(P[o](a, c),'('+b+o+d+')')];O[o]-=1
    if S(R,T):return 1
    O[o]+=1;T.pop()
  T+=[x,y]
 elif not R:
  v,r=T[0]
  if v==N:print r
  return v==N
 for x in R[:]:
  R.remove(x);T+=[x]
  if S(R,T):return 1
  T.pop();R+=[x]
S([(x,`x`)for x in L],[])

The main idea is to use postfix form of an expression to search. For example, 2*(3+4) in postfix form will be 234+*. So the problem become finding a partly permutation of L+O that evalates to N.

The following version is the ungolfed version. The stack stk looks like [(5, '5'), (2, '5-3', (10, ((4+2)+(2*(4/2))))].

L = [17, 5, 9, 17, 59, 14]
O = {'+':7, '-':3, '*':5, '/':1} 
N = 569

P = {'+':lambda x,y:x+y,
     '-':lambda x,y:x-y,
     '*':lambda x,y:x*y,
     '/':lambda x,y:x/y}

def postfix_search(rest, stk):
    if len(stk) >= 2:
        y = (v2, r2) = stk.pop()
        x = (v1, r1) = stk.pop()
        for opr in O:
            if O[opr] > 0 and not (opr == '/' and v2 == 0):
                stk += [(P[opr](v1, v2), '('+r1+opr+r2+')')]
                O[opr] -= 1
                if postfix_search(rest, stk): return 1
                O[opr] += 1
                stk.pop()
        stk += [x, y]
    elif not rest:
        v, r = stk[0]
        if v == N: print(r)
        return v == N
    for x in list(rest):
        rest.remove(x)
        stk += [x]
        if postfix_search(rest, stk):
            return True
        stk.pop()
        rest += [x]
postfix_search(list(zip(L, map(str, L))), [])

Ray

Posted 2013-02-26T08:37:44.387

Reputation: 1 946

1Wow, that's shorter than I've expected. I have scribbled an algorithm which included a conversion postfix<=>infix, but my scribble wasn't much shorter than your implementation. Impressing. And thanks for the construction P[opr](v1, v2). I never thought of combining lambdas and dictionaries like this, although it seems obvious now. – Martin Thoma – 2013-02-27T08:33:22.667

I've tried to test your solution with my 4rd testcase. After 2h, I stopped the execution. – Martin Thoma – 2013-02-27T10:52:08.453

@moose I'll try to add some heuristic to make it faster. But after that the code length may double. – Ray – 2013-02-27T13:46:22.780

Using Fraction like I did here fixes a problem in your answer. Just try it for the given instance on the link I've provided. Your current code doesn't find an answer, but when you use fraction it does.

– Martin Thoma – 2013-06-13T06:38:50.737