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
 
@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.

Saturday, September 23, 2017

Unit Conversions.

A little background

Stephan Fitzpatrick had a blog post entitled Recursion, what is it good for? this week where a friend introduced him to a problem and he decided to solve it in a tutorial style using recursion.
I liked the question and gave an iterative solution on reddit.
I had some time, and couldn't help myself from riffing on the question and expanded on the theme.
Later on, I started to get a niggle, and started to search Rosetta Code for that question, and sure enough, the task appears, from June 2015, where I had indeed posted two Python solutions!

The Original question

On RC it begins:
Write a function or program which:
  • takes a positive integer representing a duration in seconds as input (e.g., 100), and
  • returns a string which shows the same duration decomposed into weeks, days, hours, minutes, and seconds as detailed below (e.g., "1 min, 40 sec"). ...
Not knowing I had already solved it I posted this solution on reddit

Reddit solution

In [ ]:
def say_secs(seconds):
    names = 'sec min hour day week'.split()
    modulos = [60, 60, 24, 7]
    units, remainder = [], seconds
    for mod in modulos:
        remainder, unit = divmod(remainder, mod)
        units.append(unit)
    units.append(remainder)
    plurals = [('' if count == 1 else 's') for count in units]
    out = ' '.join(f'{count} {name}{p}'
                   for count, name, p 
                     in (zip(units[::-1], names[::-1], plurals[::-1]))
                   if count)
    return out or '0 secs'


if __name__ == '__main__':
    for s in [0, 100, 66400, 172801, 987987]:
        print(f'{s:6} seconds is: {say_secs(s)}')

What's it good for?

I looked at my reddit solution and thought "I could make that work for other units by modifying the modulos name appropriately".
A quick look on Wikipedia gave me info on Imperial units of measurement and I decided to add length in inches.

Refactorings to add units of length

I was going to need to change the names and modulos variables for another system of units, that was obvious. Looking at the new names I had though made me realise that plurals can't always be made by simply adding an "s". Inch becomes inches; foot becomes feet, so I had to pull namesmodulos, and pluralsout of the function above.
This stage of the code, supporting two units of measurement became:
In [ ]:
def say_secs(seconds):
    names = 'sec min hour day week'.split()
    plurals = [f'{n}s' for n in names]
    modulos = [60, 60, 24, 7]  # next unit is x times this  for unit in names
    return say_units(seconds, names, plurals, modulos)

def say_inches(inches, progression=False):
    names = 'inch foot yard chain furlong mile'.split()
    plurals = 'inches feet yards chains furlongs miles'.split()
    modulos = [12, 3, 22, 10, 8]
    return say_units(inches,  names, plurals, modulos)

def say_units(amount, names, plurals, modulos):
    units, remainder = [], amount
    for mod in modulos:
        remainder, unit = divmod(remainder, mod)
        units.append(unit)
    units.append(remainder)
    named = [(singular if count == 1 else plural) for 
             count, singular, plural in zip(units, names, plurals)]
    out = ' '.join(f'{count} {name}'
                   for count, name
                     in (zip(units[::-1], named[::-1]))
                   if count)
    return out or f'0 {plurals[0]}'


if __name__ == '__main__':
    print('\n###')
    for s in [0, 100, 66400, 172801, 987987]:
        print(f'{s:6} seconds is: {say_secs(s)}')
    print('\n###')
    say_inches(0, progression=True)
    for i in [0, 100, 66400, 172801, 987987]:
        print(f'{i:6} inches are: {say_inches(i)}')

Progressions

Too aid me I found I added the comment on the first assignment to modulos of
# next unit is x times this  for unit in names
The above was to remind me of how each measure relates to the next. Thinking about it, I thought it would make a nice printout too so added it requirements.

Adding Volumes

I wanted to add imperial units of volume, pints and what not. I found some reference material here.
Hmm, one, two, many - that's my mantra for checking if I need to maybe create a function and loop over something if I am repeating myself.
I now have three units of measurements which would have three pretty similar sections of code in the if __name__ == ... block.
With the additional requirement of wanting to print the progressions" of the units I decided to Use a class. One instantiaon per unit of measurement; make it callable for it's main action.

Result supporting three units of measurement and using classes

In [3]:
class Say_unit():
    def __init__(self, names, plurals, modulos):
        self.names = names
        self.plurals = plurals
        self.modulos = modulos
    
    def __call__(self, amount):
        return self.say_units(amount)
    
    def say_units(self, amount):
        names, plurals, modulos = self.names, self.plurals, self.modulos
        units, remainder = [], amount
        for mod in modulos:
            remainder, unit = divmod(remainder, mod)
            units.append(unit)
        units.append(remainder)
        named = [(singular if count == 1 else plural) for 
                 count, singular, plural in zip(units, names, plurals)]
        out = ' '.join(f'{count} {name}'
                       for count, name
                         in (zip(units[::-1], named[::-1]))
                       if count)
        return out or f'0 {plurals[0]}'

    def print_progression(self):
        for i, mod in enumerate(self.modulos):
            print('  '* i + f'{mod} {self.plurals[i]} in 1 {self.names[i+1]}')
        print()

def sample_unit_conversion(title, names, plurals, modulos):
    print(f'\n### {title}')
    unit_convertor = Say_unit(names, plurals, modulos)
    unit_convertor.print_progression()
    for units in [0, 100, 66400, 172801, 987987]:
        print(f'{units:6} {plurals[0]} is: {unit_convertor(units)}')
         
    

if __name__ == '__main__':
    title = 'Seconds of time'
    names = 'sec min hour day week'.split()
    plurals = [f'{n}s' for n in names]
    modulos = [60, 60, 24, 7]
    sample_unit_conversion(title, names, plurals, modulos)
 
    title = 'Inches of length'
    names = 'inch foot yard chain furlong mile'.split()
    plurals = 'inches feet yards chains furlongs miles'.split()
    modulos = [12, 3, 22, 10, 8]
    sample_unit_conversion(title, names, plurals, modulos)

    title = 'Fluid ounces of volume'
    names = 'fluid ounce, gill, cup, pint, quart, gallon, peck'.split(', ')
    plurals = [f'{n}s' for n in names]
    modulos = [5, 2, 2, 2, 4, 2]
    sample_unit_conversion(title, names, plurals, modulos)
    
    
### Seconds of time
60 secs in 1 min
  60 mins in 1 hour
    24 hours in 1 day
      7 days in 1 week

     0 secs is: 0 secs
   100 secs is: 1 min 40 secs
 66400 secs is: 18 hours 26 mins 40 secs
172801 secs is: 2 days 1 sec
987987 secs is: 1 week 4 days 10 hours 26 mins 27 secs

### Inches of length
12 inches in 1 foot
  3 feet in 1 yard
    22 yards in 1 chain
      10 chains in 1 furlong
        8 furlongs in 1 mile

     0 inches is: 0 inches
   100 inches is: 2 yards 2 feet 4 inches
 66400 inches is: 1 mile 3 chains 18 yards 1 foot 4 inches
172801 inches is: 2 miles 5 furlongs 8 chains 4 yards 1 inch
987987 inches is: 15 miles 4 furlongs 7 chains 10 yards 3 inches

### Fluid ounces of volume
5 fluid ounces in 1 gill
  2 gills in 1 cup
    2 cups in 1 pint
      2 pints in 1 quart
        4 quarts in 1 gallon
          2 gallons in 1 peck

     0 fluid ounces is: 0 fluid ounces
   100 fluid ounces is: 2 quarts 1 pint
 66400 fluid ounces is: 207 pecks 1 gallon
172801 fluid ounces is: 540 pecks 1 fluid ounce
987987 fluid ounces is: 3087 pecks 3 quarts 1 pint 1 gill 2 fluid ounces
We have a unit of volume that is two words fluid ounce so I had to make a slight change of its name string initial value and splitting. Say_unit.print_progression is new, and function sample_unit_conversion cuts down on the cut-n-paste coding.
END.
In [ ]:
 

Followers

Subscribe Now: google

Add to Google Reader or Homepage

Go deh too!

whos.amung.us

Blog Archive