Go deh!

Mainly Tech projects on Python and Electronic Design Automation.

Sunday, October 15, 2017

From Julia to Python

I read a post about the creation about the programming language Julia. It was designed to have the speed of compiled languages together with the higher level traits of Python and ruby. It wasn't the first time I had seen the language mentioned and so decided to look it up on Rosetta Code.

Julia on Rosetta Code

Julia is well represented on the site, with many examples to peruse.

I decided to look for a not too trivial task, and probably something I initially started as I could get into the task more easily. I chose the 24 Game Solver task that I had written both task and Python solution for in 2009.

24 Game

The 24 game is where you are given four numbers from one to nine and can use plus, minus, times, divide and brackets to form an expression with those numbers that evaluates to 24.

24 Game Solver

This is where the computer must form the expression evaluating to 24 from the four digits

Julia example

From the Rosetta Code section, the code is mostly this function:

```function solve24(nums)
length(nums) != 4 && error("Input must be a 4-element Array")
syms = [+,-,*,/]
for x in syms, y in syms, z in syms
for i = 1:24
a,b,c,d = nthperm(nums,i)
if round(x(y(a,b),z(c,d)),5) == 24
return "(\$a\$y\$b)\$x(\$c\$z\$d)"
elseif round(x(a,y(b,z(c,d))),5) == 24
return "\$a\$x(\$b\$y(\$c\$z\$d))"
elseif round(x(y(z(c,d),b),a),5) == 24
return "((\$c\$z\$d)\$y\$b)\$x\$a"
elseif round(x(y(b,z(c,d)),a),5) == 24
return "(\$b\$y(\$c\$z\$d))\$x\$a"
end
end
end
return "0"
end```

Very succinct, and very readable to this Pythonista.

I especially liked the use of the syms array definition - I wonder how they allow the use of the bare symbols like that, tres chic.

Original Python example

Well it is still there, but left me deflated in comparison! I do remember that the program was also written to play with my kids at the time, as well as to start the task with; but really, I did not like my solution and so I have now decided to work on a more direct translation of the Julia code.

Python translation of the Julia code.

There were some changes that I would like to make note of:

1. The first time I ran a version of this I got a zero division exception. A bit of thought lead me to think that the Julia language must be returning, probably, infinity on a division by zero.
I solved it by using function mydiv. With the ranges of numbers in use, seven nines is infinity enough.
2. Pythons, (our,  - as I am a Pythonista), operator literals cannot be just added to a list, hence the difference in the declaration of name sym.
3. New name op maps operator functions to their normal character representation for printing the generated expression.
4. There is no need for the variable i of the Julia example as a,b,c,d can be assigned to successive members of the permutation without indexing in Python.
5.  Julias \$-interpretation within strings mapped directly to f-strings in Python. It was so straightforward I could use a little vim wizardry to convert the Julia if/elsif block to the Python shown.
6. The use of rounding to five digits in this algorithm seems set to make the divisions necessary for an answer to digits [3,3,8,8] achievable.

```# -*- coding: utf-8 -*-
"""
Created on Sat Oct 14 22:30:21 2017

"""

import operator
from itertools import product, permutations

def mydiv(n, d):
return n / d if d != 0 else 9999999

syms = [operator.add, operator.sub, operator.mul, mydiv]
op = {sym: ch for sym, ch in zip(syms, '+-*/')}

def solve24(nums):
for x, y, z in product(syms, repeat=3):
for a, b, c, d in permutations(nums):
if round(x(y(a,b),z(c,d)),5) == 24:
return f"({a} {op[y]} {b}) {op[x]} ({c} {op[z]} {d})"
elif round(x(a,y(b,z(c,d))),5) == 24:
return f"{a} {op[x]} ({b} {op[y]} ({c} {op[z]} {d}))"
elif round(x(y(z(c,d),b),a),5) == 24:
return f"(({c} {op[z]} {d}) {op[y]} {b}) {op[x]} {a}"
elif round(x(y(b,z(c,d)),a),5) == 24:
return f"({b} {op[y]} ({c} {op[z]} {d})) {op[x]} {a}"

if __name__ == '__main__':
#nums = eval(input('Four integers in the range 1:9 inclusive, separated by commas: '))
for nums in [
[9,4,4,5],
[1,7,2,7],
[5,7,5,4],
[1,4,6,6],
[2,3,7,3],
[8,7,9,7],
[1,6,2,6],
[7,9,4,1],
[6,4,2,2],
[5,7,9,7],
[3,3,8,8],  # Difficult case requiring precise division
]:
print(f"solve24({nums}) -> {solve24(nums)}")```
Output:

```solve24([9, 4, 4, 5]) -> --Not Found--
solve24([1, 7, 2, 7]) -> ((7 * 7) - 1) / 2
solve24([5, 7, 5, 4]) -> 4 * (7 - (5 / 5))
solve24([1, 4, 6, 6]) -> 6 + (6 * (4 - 1))
solve24([2, 3, 7, 3]) -> ((2 + 7) * 3) - 3
solve24([1, 6, 2, 6]) -> 6 + (6 * (1 + 2))
solve24([7, 9, 4, 1]) -> (7 - 4) * (9 - 1)
solve24([6, 4, 2, 2]) -> (2 - 2) + (6 * 4)
solve24([5, 7, 9, 7]) -> (5 + 7) * (9 - 7)
solve24([3, 3, 8, 8]) -> 8 / (3 - (8 / 3))```

Wrap

Julia seems interesting, and very Python like, although I did not run any Julia Code, just read it. Julia was very readable, and had no real jarring surprises in this example.

I did not test one of its main features, Julias' speed, but The Python version is fast, so this is not the example for that.

End.