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
 
@author: Paddy3118
"""
 
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}"
    return '--Not Found--'
 
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([8, 7, 9, 7]) -> --Not Found--
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.


Tuesday, October 03, 2017

Anti Monty Hall Problem

The Anti Monty Hall problem

As explained to Matt Parker by Rob Eastaway

The Game

There are five, identical, overturned, buckets. One conceals a treat from you, so that from your vantage point, all buckets look the same.

You are asked to choose three of the buckets.
Rob, (who plays the character of Monty Hall), then arranges your three to the left, and the other two to the right like so:

"Monty Hall", who always knows where the prize is, then proceeds to up-turn two of your buckets, and one of the others, that he knows does not conceal the prize, leaving just one of your choices, and one of the others to conceal the prize.

Monty Hall should then give you the choice of whether to stick with the one remaining down-turned bucket of your original choice, or switch to the other bucket.
  • In general, should you switch?

The Video

From the excellent numberphile channel.




The Python simulation

We need to work out, on average, whether to stick with the original choice or switch. So we need to to simulate over many trials.
In [18]:
from random import randrange, sample, shuffle


def anti_monty_hall(switch):
    # Upturn the buckets
    bucket = [False] * 5
    # Secretly hide the treat
    bucket[randrange(5)] = True
    # Choose three buckets, by index
    yours = sample(range(5), 3)
    #
    others = list(set(range(5)) - set(yours))
    shuffle(others)
    # Keep any index if it is to the treat, otherwise the random first
    your_indx = [indx for indx in yours if bucket[indx]] or yours[:1]
    other_indx = [indx for indx in others if bucket[indx]] or others[:1]
    your_bucket, other_bucket = [bucket[i[0]] for i in (your_indx, other_indx)]
    # Your answer to Monty hall
    return other_bucket if switch else your_bucket
    
    
In [21]:
anti_monty_hall(switch=True)
Out[21]:
False
In [25]:
iterations = 100000
print(f"Anti Monty Hall problem simulation for {iterations} iterations in each case:")
print(f"  Not switching allows you to win {sum(anti_monty_hall(switch=False) for _ in range(iterations))} times out of {iterations}")
print(f"  Switching allows you to win {sum(anti_monty_hall(switch=True) for _ in range(iterations))} times out of {iterations}")
Anti Monty Hall problem simulation for 100000 iterations in each case:
  Not switching allows you to win 60426 times out of 100000
  Switching allows you to win 39910 times out of 100000

Roundup

The Simulation clearly shows that it is better to keep to your initial choice.
The Video explains that the odds are 60:40 which is neary the figure arrived at by the simulation.
END.