[Python-ideas] Fwd: Extremely weird itertools.permutations

MRAB python at mrabarnett.plus.com
Sat Oct 12 18:55:31 CEST 2013


On 12/10/2013 03:36, David Mertz wrote:
> Hi MRAB,
>
> I'm confused by your implementation.  In particular, what do these lines do?
>
>      # [...]
>      items = list(iterable)
>      keys = {}
>      for item in items:
>          keys.setdefault(item, len(keys))
>      items.sort(key=keys.get)
>
> I cannot understand how these can possibly have any effect (other than
> the first line that makes a concrete list out of an iterable).
>
> We loop through the list in its natural order.  E.g. say the list is
> '[a, b, c]' (where those names are any types of objects whatsoever).
>   The loop gives us:
>
>      keys == {a:0, b:1, c:2}
>
> When we do a sort on 'key=keys.get()' how can that ever possibly change
> the order of 'items'?
>
You're assuming that no item is equal to any other.

Try this:

keys = {}
for item in [1, 2, 2.0]:
     keys.setdefault(item, len(keys))

You'll get:

keys == {1: 0, 2: 1}

because 2 == 2.0.

> There's also a bit of a flaw in that your implementation blows up if
> anything yielded by iterable isn't hashable:
>
>      >>> list(unique_permutations([ [1,2],[3,4],[5,6] ]))
>      Traceback (most recent call last):
>        File "<stdin>", line 1, in <module>
>      TypeError: unhashable type: 'list'
>
That is true, so here is yet another implementation:

----8<----------------------------------------8<----

def distinct_permutations(iterable, count=None):
     def perm(items, count):
         if count:
             prev_item = object()

             for i, item in enumerate(items):
                 if item != prev_item:
                     for p in perm(items[ : i] + items[i + 1 : ], count 
- 1):
                         yield [item] + p

                     prev_item = item
         else:
             yield []

     hashable_items = {}
     unhashable_items = []

     for item in iterable:
         try:
             hashable_items[item].append(item)
         except KeyError:
             hashable_items[item] = [item]
         except TypeError:
             for key, values in unhashable_items:
                 if key == item:
                     values.append(item)
                     break
             else:
                 unhashable_items.append((item, [item]))

     items = []

     for values in hashable_items.values():
         items.extend(values)

     for key, values in unhashable_items:
         items.extend(values)

     if count is None:
         count = len(items)

     yield from perm(items, count)

----8<----------------------------------------8<----

It uses a dict for speed, with the fallback of a list for unhashable
items.

>
>      >>> list(permutations([[1,2],[3,4],[5,6]]))
>      [([1, 2], [3, 4], [5, 6]), ([1, 2], [5, 6], [3, 4]), ([3, 4], [1,
> 2], [5, 6]),
>      ([3, 4], [5, 6], [1, 2]), ([5, 6], [1, 2], [3, 4]), ([5, 6], [3,
> 4], [1, 2])]
>
> This particular one also succeeds with my nonredundant_permutations:
>
>      >>> list(nonredundant_permutations([[1,2],[3,4],[5,6]]))
>      [([1, 2], [3, 4], [5, 6]), ([1, 2], [5, 6], [3, 4]), ([3, 4], [1,
> 2], [5, 6]),
>      ([3, 4], [5, 6], [1, 2]), ([5, 6], [1, 2], [3, 4]), ([5, 6], [3,
> 4], [1, 2])]
>
My result is:

 >>> list(distinct_permutations([[1,2],[3,4],[5,6]]))
[[[1, 2], [3, 4], [5, 6]], [[1, 2], [5, 6], [3, 4]], [[3, 4], [1, 2], 
[5, 6]], [[3, 4], [5, 6], [1, 2]], [[5, 6], [1, 2], [3, 4]], [[5, 6], 
[3, 4], [1, 2]]]

> However, my version *DOES* fail when things cannot be compared under
> inequality:
>
>      >>> list(nonredundant_permutations([[1,2],3,4]))
>      Traceback (most recent call last):
>        File "<stdin>", line 1, in <module>
>        File "<stdin>", line 3, in nonredundant_permutations
>      TypeError: unorderable types: int() < list()
>
> This also doesn't afflict itertools.permutations.
>
My result is:

 >>> list(distinct_permutations([[1,2],3,4]))
[[3, 4, [1, 2]], [3, [1, 2], 4], [4, 3, [1, 2]], [4, [1, 2], 3], [[1, 
2], 3, 4], [[1, 2], 4, 3]]



More information about the Python-ideas mailing list