stupid python tricks

6 thoughts
last posted April 25, 2016, 8:24 a.m.
1
get stream as: markdown or atom
1

Norvig's reboot of Strachey's 1966 checkers program (ab)uses default arguments to map CPL's 1960's era use of let-binding. Now, there are other stupid tricks one can play with binding in python, but my current favorite involves misuse[0] of comprehensions.

With a bit of effort, we even have letrec:

print(min( f(6) 
    for Y0  in [lambda y,f:
                lambda *x: f(y(y,f))(*x)]
    for f   in [Y0(Y0,lambda fac:
                lambda n: 1 if n<2 else
                          n*fac(n-1))]))

Exercise 1: add one line to produce the classic Y

Exercise 2*: modify Y0 to support the classic mutual recursion between even and odd

[0] or not exactly misuse? cf Wadler's Comprehending Monads (p19,infra):

[the existence of the map from the identity to the list monad] explains a trick occasionally used...

1

If anyone feels like taking another run at Cleese (a python VM as bare-metal OS*), it might be worth taking advantage of NetBSD rump kernels.

  • in this case, the stupid python trick was loading and running a gcc-compiled C "hello world" program underneath a python supervisor.
1

Dave's Stupid Python Tricks (mark II) Fun: a toy for interpreting GFM Tables per McCarthy 1960:

Fun = lambda s: min( globals().update(eval(com(par(s))))
    for par in [lambda txt: [[f.strip() for f in pline(l.split('|'))]
            for pline in [ lambda xs:
                ((xs[0],'') if len(xs)==1 else
                  xs        if len(xs)==2 else
                  xs[1:3]   if len(xs)==4 else None) ]
            for l in txt.strip().split('\n') ]]
    for com in [lambda xs: compile(xs[0],xs[1:])
            if xs and None not in xs else '[]'
            for nonsep  in [lambda s: any(c not in ' -:' for c in s)]
            for compile in [lambda hdr,bdy:
                '[("{}",(lambda {}:{} None))]'.format(
                    hdr[0], hdr[1], ' '.join(clause(b)
                for b in bdy if nonsep(b[0]+b[1])))
        for clause in [lambda b:
                    '({}) if ({}) else'.format(
                      b[0],   b[1] or 'True')]]])

if __name__ == "__main__":
    Fun('''
        | fact        | n   |
        |------------:|:----|
        | 1           | n<2 |
        | n*fact(n-1) |     |
    ''')
    print(fact(5))

    Fun('''
        gcd        | x,y
        ----------:|:---
        gcd(x-y,y) | y<x
        gcd(x,y-x) | x<y
        x          |
    ''')
    print(gcd(10,2014))
0

Stupid Python Tricks (mark III): "barely literate programming".

expand   = lambda node,d: ''.join(expand(x,d) if i%2 else x
             for i,x    in enumerate(d[node][:-1].split('@')))
findcode = lambda s: { rk[1:-1] : ''.join(rv[1:] for rv in rvs)
             for ls     in [[l+'\n' for l in s.split('\n') if l[:1] in '@>']]
             for js     in [[i for i,l in enumerate(ls) if l[0]=='@']+[None]]
             for i,j    in zip(js[:-1],js[1:]) 
             for rk,rvs in [(ls[i],ls[i+1:j])]}

print(':: :: ::\n'.join(
    expand(node, findcode(test))
        for node in ['k & r','ansi']
        for test in [r"""
this is a test of barely-literate programming.

:: :: ::

First, we try an old-timer program:

@k & r
>main(@formal names@)
>@formal declarations@
>{
>   printf(@greetings@);
>}
>

Not that we use them in this program (it is,
after all, a constant function, cf. the 'K'
combinator) but for nostalgia value, we have

@formal names
>argc, argv

@formal declarations
>int argc;
>char *argv[];

and, of course, we should specify the value
which the function takes at all arguments:

@greetings
>"Hello, world\n"

:: :: ::

Now, let's rewrite this in a more modern style:

@ansi
>@external declarations@
>
>int main(@inline formal declarations@)
>{
>   printf(@greetings@);
>   @more boilerplate...@
>}
>

You'll notice that we have an 'int' in there.
That's because once one has more than a few
K of memory to play with during a compile, it
turns out that silently assuming all params
are machine words isn't the best tradeoff.

Of course, now that we've declared that we're
returning an int, we'd probably better do so:

@more boilerplate...
>return 0;

We must declare the argument types as well
as the return types:
@inline formal declarations
>int argc, char **argv, char **envp

As in Pascal, the types are now specified in
with the argument names.  One might expect
that we'd need some additional bookkeeping
for printf() as well as main(), but luckily
(if one is not the preprocessor) this is a
matter of including the proper header file.

@external declarations
>#include <stdio.h>

:: :: ::

(to get fancier, one might try piping output
through "tcc -run", but it would probably be
better to fill in the code sketched here and
improve its robustness and input format)
"""]))
0

Reservoir sampling, AKA deal. (in APL, at 110 baud, that main expression would have been something likeHAND 5?52)

import random;print(min(
  hand(deal(5,range(52)))
  for vals,suits in [("A23456789TJQK", "CDHS")]
  for hand in [lambda js: " ".join([
                vals[v]+suits[s] for j in js
                for s,v in [divmod(j,13)]])]
  for resv in [lambda rs,xs,k: rs if not xs else
      min([resv([r if i!=j else xs[0]
                  for i,r in enumerate(rs)],
                xs[1:],k+1)
              for j in [random.randint(0,k)]])]
  for deal in [lambda n,xs:resv(xs[:n],xs[n:],n)]
 ))
0
print((lambda d:d+repr(d)+"))")('print((lambda d:d+repr(d)+"))")('))