Go deh!

Mainly Tech projects on Python and Electronic Design Automation.

Monday, May 04, 2015

Five cells to freedom logic puzzle


I found the following logic puzzle and decided to solve it using Python rather than logical deduction in my head:

I got it from here

You have been incarcerated in a high-security prison for several nefarious
crimes which we need not go into here. The warden, being a kindly logician,
offers you a single chance to escape. You are blindfolded and given a choice of
five doors, one of which leads to freedom and the other four to prison cells.
You know that the doors are red, blue, green, yellow, and white; they are
evenly spaced, each 2 paces from the next; and you are standing in front of the
middle one. There is a guard standing by each door, and all five guards tell
the truth (weren't expecting that, were you?). They make the following
statements:
  1. The red door is somewhere to the left of the door leading to freedom.
  2. The blue door is not at either end.
  3. The green door is 3 doors away from the door leading to freedom (2 doors between them).
  4. The yellow door is adjacent to the door leading to freedom.
  5. The white door is the middle one.

To find out how many paces you need to take to which side in order to be
standing in front of the door to freedom, you need to answer the following two
questions:
  • What is the order of the coloured doors, left to right?
  • Which colour of door leads to freedom?
You may assume that all statements made about "left" and "right" refer to your

left and right.


Strategy

Well it's only five doors which means a limited number of permutations to test. I will need the doors and individual colours (this should probably have been an enumeration for the individual colours). I use a namedtuple as my main data-structure as it is succinct and I think I am going to need access to the assumed position and colour of the freedom cell out of any particular permutation of door positions and cell-to-freedom under test at any time.
from itertools import permutations
from collections import namedtuple
 
FreeDoor = namedtuple('FreeDoor', 'doors, free_colour, free_position')
 
doors = 'red, blue, green, yellow, white'.split(', ')
R, B, G, Y, W = doors

I would construct a list of constraint functions then apply them to a permutation and select those permutations that fit all constraints.

Constraints #1


Extracting the constraints, I knew, was going to be the hardest part of this.  I initially thought that I could solve for cell order then solve again for which one led to freedom so started with the following list of lambda functions.

Note how I have extracted the five statements as comments to guide me in extracting the constraints.
position_constraints = [
    #The red door is somewhere to the left of the door leading to freedom.
    lambda fd: fd.doors.index(R) < 4, # can't be at far right
    #The blue door is not at either end.
    lambda fd: 0 < fd.doors.index(B) < 4,
    #The green door is 3 doors away from the door leading to freedom (2 doors between them).
    lambda fdd: fd.doors.index(G) != 2,
    #The yellow door is adjacent to the door leading to freedom.
    #lambda d: d.index(Y)
    #The white door is the middle one.
    lambda fd: fd.doors.index(W) == 2,
    ]

I knew that I was having problems when there was nothing about the position of the yellow door that could be stated from statement 4.

Constraints #2

I then resolved to write constraints for both freedom cell position and cell order at the same time and came up with the following constraints. You have to be able to extract this from the text and like most "wordy" maths problems extracting the maths from the text is usually harder than solving the resultant maths.
all_constraints = [
    #The red door is somewhere to the left of the door leading to freedom.
    lambda fd: (fd.free_position > 0 and fd.free_colour != R 
                and fd.doors.index(R) < fd.free_position),
    #The blue door is not at either end.
    lambda fd: 0 < fd.doors.index(B) < 4,
    #The green door is 3 doors away from the door leading to freedom (2 doors between them).
    lambda fd: (fd.free_colour != G and fd.doors.index(G) != 2 
                and (fd.doors.index(G) + 3 == fd.free_position 
                     or fd.doors.index(G) - 3 == fd.free_position)),
    #The yellow door is adjacent to the door leading to freedom.
    lambda fd: (fd.free_colour != Y and (fd.doors.index(Y) + 1 == fd.free_position 
                                      or fd.doors.index(Y) - 1 == fd.free_position)),
    #The white door is the middle one.
    lambda fd: fd.doors.index(W) == 2,
    ]


The Solver

I decided to gather all solutions as I was not sure if there would be more than one.  To get the permutations I need the permutations of the door colours permuted with possible door to freedom positions within that accomplished with the nested for loops then I stuff a new FreeDoor instance with the correct values and check it against all the constraints with the all() function.
def solve():
    solutions = []
    for order in permutations(doors):
        for freedom in doors:
            freedomdoor = FreeDoor(order, freedom, order.index(freedom))
            if all(constraint(freedomdoor) for constraint in all_constraints):
                solutions.append(freedomdoor)
    return solutions
 
ans = solve()
print(ans)

The answer

[FreeDoor(doors=('green', 'red', 'white', 'blue', 'yellow'), free_colour='blue', free_position=3)]

(The position given above is zero-indexed from the LHS).

END.


Friday, May 01, 2015

Solving the Monkey and coconuts problem

Numberphile had another great video which introduced me to the Monkey and coconut problem


The problem statement paraphrased from the video is:

Five sailors are shipwrecked on an island and collect a large pile of coconuts during the day. That night the first sailor wakes up and decides to take his first share early so tries to divide the pile of coconuts equally into five piles but finds that there is one coconut left over so tosses it to a monkey, he then hides "his" one of the five equally sized piles of coconuts and pushes the other four piles together to form a single visible pile of coconuts again and goes to bed.
To cut a long story short, each of the sailors gets up singly, once during the night and performs the same actions of dividing the coconut pile into five, finding that one coconut is left over and giving that single remainder coconut to the monkey.
In the morning (after the surreptitious and separate action of each of the five sailors during the night), The pile of coconuts are divided into five equal piles for each of the sailors and whereupon it is found that the pile of coconuts divides equally amongst the sailors with no remainder. (Nothing for the monkey in the morning).

The task is to calculate the minimum size of the initial pile of coconuts collected during the first day.

I choose to find a solution using a Python program.

Initial thoughts.

Thinking about how I could solve it I thought that I would write something that had a lot of cut-n-pasted code for the night time activities of each sailor followed by a slightly different copy where there is no remainder which models the next days final distribution.

That would lead to me creating another version using a loop for the night time activities and possibly folding in the last days division

Finally I could look at creating a recursive version.

The progression looked like a good idea and I set out to do just that.

First cut-n-paste code

I have removed the print statements that allowed me to debug this initial version but a quick google showed that this was giving the right answer and looked correct.

def monkey_coconuts_5sailors():
    "Hard coded and copy-pasted for only five sailors"
    nuts = sailors = 5
    while True:
        n0, wakes = nuts, []
        portion, remainder = divmod(n0, sailors)
        wakes.append((n0, portion, remainder))
        if portion <= 0 or remainder !=1:
            nuts += 1
            continue
        n0 = n0 - portion - remainder
        portion, remainder = divmod(n0, sailors)
        wakes.append((n0, portion, remainder))
        if portion <= 0 or remainder !=1:
            nuts += 1
            continue
        n0 = n0 - portion - remainder
        portion, remainder = divmod(n0, sailors)
        wakes.append((n0, portion, remainder))
        if portion <= 0 or remainder !=1:
            nuts += 1
            continue
        n0 = n0 - portion - remainder
        portion, remainder = divmod(n0, sailors)
        wakes.append((n0, portion, remainder))
        if portion <= 0 or remainder !=1:
            nuts += 1
            continue
        n0 = n0 - portion - remainder
        portion, remainder = divmod(n0, sailors)
        wakes.append((n0, portion, remainder))
        if portion <= 0 or remainder !=1:
            nuts += 1
            continue
        #
        n0 = n0 - portion - remainder
        portion, remainder = divmod(n0, sailors)
        wakes.append((n0, portion, remainder))
        if portion <= 0 or remainder !=0:
            nuts += 1
            continue
        else:
            break
    return nuts, wakes
 
nuts, wake_stats = monkey_coconuts_5sailors()
print('##', monkey_coconuts_5sailors.__doc__)
print("Initial nut count =", nuts)
print("On each waking, the nuts, portion taken, and monkeys share are:\n ", 
      wake_stats)

The output:
## Hard coded and copy-pasted for only five sailors
Initial nut count = 3121
On each waking, the nuts, portion taken, and monkeys share are:
  [(3121, 624, 1), (2496, 499, 1), (1996, 399, 1), (1596, 319, 1), (1276, 255, 1), (1020, 204, 0)]

Turn the copy-pasted code into a loop

Note that their is slightly different logic in the last section of code above and that is reflected in this second version

def monkey_coconuts1(sailors=5):
    "Parameterised the number of sailors using an inner loop"
    nuts = sailors
    while True:
        n0, wakes = nuts, []
        for sailor in range(sailors):
            portion, remainder = divmod(n0, sailors)
            wakes.append((n0, portion, remainder))
            if portion <= 0 or remainder !=1:
                nuts += 1
                break
            n0 = n0 - portion - remainder
        else:
            portion, remainder = divmod(n0, sailors)
            wakes.append((n0, portion, remainder))
            if portion <= 0 or remainder !=0:
                nuts += 1
                continue
            break
    return nuts, wakes
 
nuts, wake_stats = monkey_coconuts1(sailors=5)
print('\n##', monkey_coconuts1.__doc__)
print("Initial nut count =", nuts)
print("On each waking, the nuts, portion taken, and monkeys share are:\n ", 
      wake_stats)

Output:

## Parameterised the number of sailors using an inner loop
Initial nut count = 3121
On each waking, the nuts, portion taken, and monkeys share are:
  [(3121, 624, 1), (2496, 499, 1), (1996, 399, 1), (1596, 319, 1), (1276, 255, 1), (1020, 204, 0)]

Fold in the logic for the morning division

The code for the morning is similar to the code for each sailors nightly excursion except that there is no remainder for the monkey. This last procedural code version folds this last case into the loop with extra logic around the remainder checking.

I also take the opportunity to solve the case of there being six sailors not five.

def monkey_coconuts2(sailors=5):
    "Parameterised the number of sailors using an inner loop including the last mornings case"    
    nuts = sailors
    while True:
        n0, wakes = nuts, []
        for sailor in range(sailors + 1):
            portion, remainder = divmod(n0, sailors)
            wakes.append((n0, portion, remainder))
            if portion <= 0 or remainder != (1 if sailor != sailors else 0):
                nuts += 1
                break
            n0 = n0 - portion - remainder
        else:
            break
    return nuts, wakes
 
if __name__ == "__main__":
    for sailors in [5, 6]:
        nuts, wake_stats = monkey_coconuts2(sailors)
        print('\n##', monkey_coconuts2.__doc__)
        print("For %i sailors the initial nut count is %i" % (sailors, nuts))
        print("On each waking, the nut count, portion taken, and monkeys share are:\n ", 
              ',\n  '.join(repr(ws) for ws in wake_stats))

Output:
## Parameterised the number of sailors using an inner loop including the last mornings case
For 5 sailors the initial nut count is 3121
On each waking, the nut count, portion taken, and monkeys share are:
  (3121, 624, 1),
  (2496, 499, 1),
  (1996, 399, 1),
  (1596, 319, 1),
  (1276, 255, 1),
  (1020, 204, 0)

## Parameterised the number of sailors using an inner loop including the last mornings case
For 6 sailors the initial nut count is 233275
On each waking, the nut count, portion taken, and monkeys share are:
  (233275, 38879, 1),
  (194395, 32399, 1),
  (161995, 26999, 1),
  (134995, 22499, 1),
  (112495, 18749, 1),
  (93745, 15624, 1),
  (78120, 13020, 0)

Using Recursion

Function monkey_coconuts3 sets up for the recursive calling of wake_and_split which has a depth parameter to control the maximum depth of recursion and returns either None or the last integer division of nuts to sailors (in the morning) on success.

def wake_and_split(n0, sailors, depth=None):
    if depth is None:
        depth = sailors
    portion, remainder = divmod(n0, sailors)
    #print((n0, portion, remainder, depth))
    if portion <= 0 or remainder != (1 if depth else 0):
        return None
    else:
        return n0 if not depth else wake_and_split(n0 - portion - remainder, sailors, depth - 1)
 
 
def monkey_coconuts3(sailors=5):
    "Parameterised the number of sailors using recursion including the last mornings case"    
    nuts = sailors
    while True:
        if wake_and_split(n0=nuts, sailors=sailors) is None:
            nuts += 1
        else:
            break
    return nuts
 
if __name__ == "__main__":
    for sailors in [5, 6]:
        nuts = monkey_coconuts3(sailors)
        print('\n##', monkey_coconuts3.__doc__)
        print("For %i sailors the initial nut count is %i" % (sailors, nuts))

Output:
# Parameterised the number of sailors using recursion including the last mornings case
For 5 sailors the initial nut count is 3121

## Parameterised the number of sailors using recursion including the last mornings case
For 6 sailors the initial nut count is 233275


Rosetta Code Task

Oh yes, I also created a new task:   Sailors, coconuts and a monkey problem  from the last two versions of Python code.

END.

Tuesday, April 14, 2015

Pythonic matrix manipulation?

I was scanning Rosetta Code and came across a Python-2 example of Cholesky decomposition, a 2-D matrix manipulation that I thought could do with converting to Python 3.

On further investigation I noted that it was using "for i in range(len(some_list))" which is a code smell for me which prompts me to see if I can iterate over the elements rather than using an index.

Here is the original Python-2 code with minimal changes just to make it run under Python-3 but without changing any of the indexing and looping:

from pprint import pprint
from math import sqrt
 
def cholesky(A):
    L = [[0.0] * len(A) for _ in range(len(A))]
    for i in range(len(A)):
        for j in range(i+1):
            s = sum(L[i][k] * L[j][k] for k in range(j))
            L[i][j] = sqrt(A[i][i] - s) if (i == j) else \
                      (1.0 / L[j][j] * (A[i][j] - s))
    return L

The i and j loop indices are used in non-simple ways in the inner loops but I noticed that I might be able to factor out accesses to A[i], L[i], and L[j] by creating Ai, Li and Lj respectively, but would still need indices.

I came up with the following where I use enumerate to provide the indices, but I also iterate over the matrices A and L directly:

def cholesky2(A):
    L = [[0.0] * len(A) for _ in range(len(A))]
    for i, (Ai, Li) in enumerate(zip(A, L)):
        for j, Lj in enumerate(L[:i+1]):
            s = sum(Li[k] * Lj[k] for k in range(j))
            Li[j] = sqrt(Ai[i] - s) if (i == j) else \
                      (1.0 / Lj[j] * (Ai[j] - s))
    return L


Now cholesky2 is slightly faster and gives the same results, but I don't have any larger examples to test it on than are given on the RC page. If we say their execution speeds are comparable I would then ask myself which is more pythonic and which is easier to read?

Now for this matrix manipulation task I think that although the second uses more Python idioms I would probably think of the first as being more pythonic as it is likely to more closely follow any pseudo-code you might find for the algorithm i.e. having C/Fortran style indexing, and so be more maintainable.

How say you?


P.S. Although there is probably a numpy or whatever function for this but I want to consider Python code solutions only.

Addition at 14:04:2015 21:00: Further changes

A conversation with commenter Florian Philipp on Google plus lead to suggestions for additional changes  including the removal of more uses of range and removal of the conditional assignment by recognising that its if cause can be done after the inner loop and the inner loop changed to only need the else clause. The resultant is the following code:
from math import fsum
 
def cholesky3(A):
    L = [[0.0] * len(A) for _ in range(len(A))]
    for i, (Ai, Li) in enumerate(zip(A, L)):
        for j, Lj in enumerate(L[:i]):
            s = fsum(Lik * Ljk for Lik, Ljk in zip(Li, Lj[:j]))
            Li[j] = (1.0 / Lj[j] * (Ai[j] - s))
        s = fsum(Lik * Lik for Lik in Li[:i])
        Li[i] = sqrt(Ai[i] - s)
    return L

This third version might be the fastest, but by only a very small margin when calculating m3 so, again, you might want to assume that run time differences between the three are negligible and think of which you, as a seasoned Python programmer, think is more Pythonic.

Tuesday, April 07, 2015

Wholistic options processing.

I have installed, configured and used many large software packages over time and have come to appreciate ideas on options handling and configuration from a mix of vendors that I think should become more widespread and supported by language libraries.

The best packages make their options settable in multiple ways and have a defined hierarchy and visibility of options - by visibility I mean whether an option is editable in some way.


  1. When installing the package some checks are done/questions answered that become options and values in an options file in the install area. Ideally this should also have package default values.
    I have only ever seen these types of files best done in windows .ini format, but I would think that YAML could also be used as it too is human readable/editable and supports comments (unlike JSON).
    Comments should precede every entry and section with enough detail to be understood by someone who has been on the packages introductory course and read the documentation or marked for advance use only.

    (Options should be of  a single case although their expression as upper/lower case may depend on context)
  2. The first time a program from the package is run it creates a $HOME/.package file of all user specific and user changeable options with their site specific defaults (copied over at that time and with comments). This will be a subset of the site options.
  3. When an interactive program from the package is run from within a run directory a subset of options will be editable , set and reset within the program. On good program exit a run directory .package file should be created with both the normal program options used but augmented with aspects of the programs state so that on re-running the program from the same directory things like GUI window positions or database connections that were filled in on the previous invocation might start from those previous values.
    (Warn when a program is run from the $HOME directory and ask before overwriting the $HOME/.package file).
  4. Some options will be able to be read from environment variables. Environment variable names should consist of a one-word prefix of the packages nick-name or name followed by the option name separated by a comma.
    When a package contains a large sub-package then that sub-package nick-name may form the prefix instead.
    Comments in the $HOME/.package file should state the environment variable name to use if an option is settable in the environment
  5. When invoking a program from the package the programs help should give help on all the options that are settable on the command line. In addition, those options settable on the command line should also appear in the $HOME/.package file and run directory .package file with the posix long format argument mentioned in the help text.
    One should allow for  a program argument short-form (single letter argument), that needs to appear in the programs help, but not necessarily in any .package file comments allowing for other package programs to reuse single character short options in what for them may be more idiomatic fashion.
    Default values need to reflect values already set in the options processing mentioned in 6.1 to 6.3.
  6. There should be a clear hierarchy of options processing:
    1. Process the site .package file
    2. Process the run directory .package file if it exists, else if it does not exist then process the home directory .package file.
    3. Process environment variable options.
    4. Process command line options.
    5. Process options changes within a program.
  7. Options processing should also check to ensure that given options are allowed to be changed/viewed where stated.
  8. Options processing should impose a hierarchy whereby an option set later overrides values set earlier in the 6.1 to 6.5 stages mentioned.
I don't know of open source options handling libraries that are this complete as most seem to handle command line processing and a few might integrate handling a single .package file.


Sunday, March 01, 2015

Random number generator PCG32 translated to Python.

There's a new family of pseudo-random number generators called PCG going around with a great Video introducing the problem that it solves:


The site has a pcg32 baby implementation in C that I chose to convert to plain python to learn more about the algorithm.

The pcg32 C code is written to be quite lean-n-mean but Python doesn't do fast integer bit operations - its a higher level language, integers automatically extend to however bits are necessary for computing a result without over/underflow. I therefore needed to use 32 and 64 bit bit-masks to constrain results to the uint32 and uint64 types of the C code.

The C struct pcg_state_setseq_64 is mutable and passed between functions. I chose to model this with a two element Python list - the closest I could think of to a lightweight mutable namedtuple (as there is no such thing in the standard library).

The code is commented with the C-code it is derived from:

# -*- coding: utf-8 -*-
"""
Created on Thu Feb 26 22:58:33 2015
 
@author: Paddy3118
 
 
PCG32 translated from the C code:
  http://www.pcg-random.org/download.html#minimal-c-implementation
  Version pcg-c-basic-0.9
 
"""
 
from sys import getsizeof
 
 
_MASK32 = 2**32-1   # uint32_t
_MASK64 = 2**64-1   # uint64_t
 
 
def state_setseq_64(state=0, inc=0):
    """
    struct pcg_state_setseq_64 {    // Internals are *Private*.
        uint64_t state;             // RNG state.  All values are possible.
        uint64_t inc;               // Controls which RNG sequence (stream) is
                                    // selected. Must *always* be odd.
    };
    """
    return [state & _MASK64, inc & _MASK64]
# typedef struct pcg_state_setseq_64 pcg32_random_t;
random_t = state_setseq_64
 
# #define PCG32_INITIALIZER   { 0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL }
# static pcg32_random_t pcg32_global = PCG32_INITIALIZER;
_GLOBAL = random_t(0x853c49e6748fea9b, 0xda3e39cb94b95bdb)
 
 
def srandom_r(rng, initstate, initseq):
    """
    void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq)
    {
        rng->state = 0U;
        rng->inc = (initseq << 1u) | 1u;
        pcg32_random_r(rng);
        rng->state += initstate;
        pcg32_random_r(rng);
    }
    """
    rng[::] = [0, (initseq << 1) & _MASK64 | 1]
    random_r(rng)
    rng[0] += initstate
    rng[0] &= _MASK64
    random_r(rng)
 
 
def srandom(seed, seq):
    """
    void pcg32_srandom(uint64_t seed, uint64_t seq)
    {
        pcg32_srandom_r(&pcg32_global, seed, seq);
    }
    """
    srandom_r(_GLOBAL, seed & _MASK64, seq & _MASK64)
 
 
def random_r(rng):
    """
    uint32_t pcg32_random_r(pcg32_random_t* rng)
    {
        uint64_t oldstate = rng->state;
        rng->state = oldstate * 6364136223846793005ULL + rng->inc;
        uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
        uint32_t rot = oldstate >> 59u;
        return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
    }
    """
    oldstate, inc = rng
    rng[0] = (oldstate * 6364136223846793005 + inc) & _MASK64
    xorshifted = (((oldstate >> 18) ^ oldstate) >> 27) & _MASK32
    rot = (oldstate >> 59) & _MASK32
    return ((xorshifted >> rot) | (xorshifted << ((-rot) & 31))) & _MASK32
 
def random():
    """
    uint32_t pcg32_random()
    {
        return pcg32_random_r(&pcg32_global);
    }
    """
    return random_r(_GLOBAL)
 
 
def boundedrand_r(rng, bound):
    """
    uint32_t pcg32_boundedrand_r(pcg32_random_t* rng, uint32_t bound)
    {
        // To avoid bias, we need to make the range of the RNG a multiple of
        // bound, which we do by dropping output less than a threshold.
        // A naive scheme to calculate the threshold would be to do
        //
        //     uint32_t threshold = 0x100000000ull % bound;
        //
        // but 64-bit div/mod is slower than 32-bit div/mod (especially on
        // 32-bit platforms).  In essence, we do
        //
        //     uint32_t threshold = (0x100000000ull-bound) % bound;
        //
        // because this version will calculate the same modulus, but the LHS
        // value is less than 2^32.
 
        uint32_t threshold = -bound % bound;
 
        // Uniformity guarantees that this loop will terminate.  In practice, it
        // should usually terminate quickly; on average (assuming all bounds are
        // equally likely), 82.25% of the time, we can expect it to require just
        // one iteration.  In the worst case, someone passes a bound of 2^31 + 1
        // (i.e., 2147483649), which invalidates almost 50% of the range.  In
        // practice, bounds are typically small and only a tiny amount of the range
        // is eliminated.
        for (;;) {
            uint32_t r = pcg32_random_r(rng);
            if (r >= threshold)
                return r % bound;
        }
    }
    """
    bound &= _MASK32
    threshold = ((-bound) & _MASK32) % bound
    while True:
        r = random_r(rng)
        if r >= threshold:
            return r % bound
 
def boundedrand(bound):
    """
    uint32_t pcg32_boundedrand(uint32_t bound)
    {
        return pcg32_boundedrand_r(&pcg32_global, bound);
    }
    """
    return boundedrand_r(_GLOBAL, bound)
 
 
if __name__ == '__main__':
    # // In this version of the code, we'll use a local rng, rather than the
    # // global one.
    # pcg32_random_t rng;
    rng = random_t()
    # // Seed with a fixed constant
    # pcg32_srandom_r(&rng, 42u, 54u);
    srandom_r(rng, 42, 54)
 
    print("""pcg32_random_r:
      -  result:      32-bit unsigned int (uint32_t)
      -  period:      2^64   (* 2^63 streams)
      -  state type:  pcg32_random_t (%i bytes)
      -  output func: XSH-RR\n""" % getsizeof(rng))
 
    #print("  Make some 32-bit numbers:")
    print('  32bit: ' + ' '.join('0x%08x' % random_r(rng) for _ in range(6)))
    #print("  Toss some coins:")
    print('  Coins: ' + ''.join('TH'[boundedrand_r(rng, 2)] for _ in range(65)))
    #print("  Roll a die:")
    print('  Rolls: ' + ' '.join('%i' % (1 + boundedrand_r(rng, 6)) for _ in range(33)))
 

Outputs match between the C and Python versions (well enough to show they are generating the same sequences - The C goes on to generate shuffled cards from a deck).

Python PCG in the Standard Lib.?

From the description of its properties I wouldn't be surprised if PCG, (the full-fat 64 bit version that passes the larger test-suite, unlike our current  Mersenne Twister algorithm), makes it into the Standard library, although I cannot tell if it would displace MT as the default generator for the random module any-time soon.

Followers

Subscribe Now: google

Add to Google Reader or Homepage

Go deh too!

whos.amung.us

Blog Archive