[Python-ideas] Another "little" combination, permutation, iterator, yield from, example.

Ron Adam ron3200 at gmail.com
Wed Oct 16 08:21:13 CEST 2013



On 10/15/2013 07:25 PM, Tim Peters wrote:
> [Tim]
>>> [MRAB, posts a beautiful solution]
>>>
>>> I don't really have a use for this, but it was a lovely programming
>>> puzzle, so I'll include an elaborate elaboration of MRAB's algorithm
>>> below.  And that's the end of my interest in this;-)
>
> [Ron Adam]
>> A what the heck...  :-)
>>
>>
>> This is what I came up with which works like MRAB's by removing the first
>> element and using recursion on the rest.  (It's how you do it in lisp or
>> scheme.)
>
> It's solving a different problem, though.  In your "Return all unique
> combinations of elements in seq", by "unique" you really mean "by
> position", not " by value".  For example:

Correct.

And sometimes an items position is part of what makes it unique.  Multiple 
roller combination locks and slot-machines, come to mind.  I'm sure there 
are other things where the label or face value is only one aspect of it's 
identity.


>>>>   for p in unique_sets('aab'):
> ...        print(p)
> ['a']
> ['a']
> ['b']
> ['b', 'a']
> ['a', 'a']
> ['b', 'a']
> ['b', 'a', 'a']
>
> See?  ['a'] is produced twice, and so is ['b', 'a'].  The whole point
> of the algorithms in the thread this spawned from was to avoid
> generating *value*-duplicates to begin with.  Filtering them out later
> is too inefficient.


Well, we can filter them to begin-with instead of later.

for p in unique_sets(list(set('aaaaaaaa'))):
     print(p)

['a']



And skip combination duplicates later as they are produced.

for p in unique_sets('aaaaaaaa'):
     if cached(tuple(p)):
        continue
     print(p)

['a']
['a', 'a']
['a', 'a', 'a']
['a', 'a', 'a', 'a']
['a', 'a', 'a', 'a', 'a']
['a', 'a', 'a', 'a', 'a', 'a']
['a', 'a', 'a', 'a', 'a', 'a', 'a']
['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']


Not generating them to begin with requires some sort of known ordering or 
pattern in the data.

The difference between checking them inside the algorithm as they are 
produced, and checking them externally as they are *yielded* is only the 
yield overhead if we are talking about python code in both places.

In effect the inner loop control is yielded out with the value.  That's a 
very nice thing about "yield from", and it still works that way in the 
recursive example.  That makes things like this a lot more flexible.


> Only one comment on the code:
>
>
>> def unique_sets(seq):
>>      """Return all unique combinations of elements in seq."""
>>      if len(seq) == 0:
>>          return []
>>      first, rest = seq[0], seq[1:]
>
> Python's newer unpacking syntax makes that one prettier:
>
>      first, *rest = seq
>

LOL... yep.  Thats why that part bugged me.  I knew there was something I 
was missing.

The other part that bothers me is the insistence list check.  And not 
accepting sets directly.


 > Much higher-level than crusty old Lisp or Scheme ;-)

HAHA... yep, although they are both new to me and clojure seems to be doing 
well.


A little digression...

I've been thinking that pythons byte code could be a little higher level 
and still be efficient.  So I wrote a little interpreter (in python) to 
test some ideas.

   * Use nested [sequences] in the bytecode rather than jumps.
   * Use tuples to contain the function followed by it's arguments..
        (very easy to parse and evaluate this way).
   * Keep bytecodes and keywords to a minimal set.
   * Use as little punctuation symbols as possible.

I didn't start learning lisp and scheme until after I got that much 
working. But it turned out (and I recently found out) it to be very close 
to a scheme interpreter or lisp interpreter.

This example works, with some syntax alterations from the lisp version. 
And after adding in the needed standard lisp/scheme functions.. car, cdr, 
etc...

(Looks much nicer with syntax highlighting.)

def "power-set set" [
     if (is-empty set) [return (list set)]
     let psets-without-car (power-set (cdr set))
     let psets-with-car
         (mapcar
             (cons
                 (lambda 'subset' [return (cons (car set) subset)])
                 psets-without-car))
     return (join-lists psets-with-car psets-without-car)
]

echo (power-set [[1 2] [3 4] [5 6]])

(power-set [[1 2] [3 4] [5 6]]) == [[[1 2] [3 4] [5 6]] [[1 2] [3 4]] [[1 
2] [5 6]] [[1 2]] [[3 4] [5 6]] [[3 4]] [[5 6]] []]


When I first looked at the lisp version of this, I had no idea it had three 
different return points.  Explicit is better than implicit. ;-)


So how does that anything like byte code?  The structure is just nested 
tokens and symbols.


(This will parse and evaluate fine in this form ... with the comments too.)

     def               # keyword
     "power-set set"   # string-literal
     [                 # block-start
     if                # keyword
     (                 # expression-start
     is-empty          # name
     set               # name
     )                 # expression-stop
     [                 # block-start
     return            # keyword
     (                 # expression-start
     list              # name
     set               # name
     )                 # expression-stop
     ]                 # block-stop
     .... etc.


There's a bit more... python wrappers for dictionaries, and sets, and lower 
level functions.

I was thinking of rewriting it in C and seeing how it performs, but 
Gambit-C may be a better choice.  It has nearly all the low level features 
python needs and compiles to C or Java.
So don't hold your breath.
It will be a while before I can get that far.  :-)

Cheers,
     Ron











More information about the Python-ideas mailing list