Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Python's itertools.permutations contain duplicates? (When the original list has duplicates)

It is universally agreed that a list of n distinct symbols has n! permutations. However, when the symbols are not distinct, the most common convention, in mathematics and elsewhere, seems to be to count only distinct permutations. Thus the permutations of the list [1, 1, 2] are usually considered to be
[1, 1, 2], [1, 2, 1], [2, 1, 1]. Indeed, the following C++ code prints precisely those three:

int a[] = {1, 1, 2}; do {     cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<endl; } while(next_permutation(a,a+3)); 

On the other hand, Python's itertools.permutations seems to print something else:

import itertools for a in itertools.permutations([1, 1, 2]):     print a 

This prints

(1, 1, 2) (1, 2, 1) (1, 1, 2) (1, 2, 1) (2, 1, 1) (2, 1, 1) 

As user Artsiom Rudzenka pointed out in an answer, the Python documentation says so:

Elements are treated as unique based on their position, not on their value.

My question: why was this design decision made?

It seems that following the usual convention would give results that are more useful (and indeed it is usually exactly what I want)... or is there some application of Python's behaviour that I'm missing?

[Or is it some implementation issue? The algorithm as in next_permutation — for instance explained on StackOverflow here (by me) and shown here to be O(1) amortised — seems efficient and implementable in Python, but is Python doing something even more efficient since it doesn't guarantee lexicographic order based on value? And if so, was the increase in efficiency considered worth it?]

like image 486
ShreevatsaR Avatar asked Jun 30 '11 12:06

ShreevatsaR


People also ask

What is Itertools permutation in Python?

Itertool is a module provided by Python for creating iterators for efficient looping. It also provides various features or functions that work with iterators to produce complex iterators and help us to solve problems easily and efficiently in terms of time as well as memory.

What is a unique permutation?

A permutation of a set of distinct objects is. an arrangement of the objects in a specific order without repetition. Example 1. If there are three distinct books. A, B, and C, how many different.

What does Permute mean in Python?

Permutations means different orders by which elements can be arranged. The elements might be of a string, or a list, or any other data type. It is the rearrangement of items in different ways. Python has different methods inside a package called itertools, which can help us achieve python permutations.


2 Answers

I can't speak for the designer of itertools.permutations (Raymond Hettinger), but it seems to me that there are a couple of points in favour of the design:

First, if you used a next_permutation-style approach, then you'd be restricted to passing in objects that support a linear ordering. Whereas itertools.permutations provides permutations of any kind of object. Imagine how annoying this would be:

>>> list(itertools.permutations([1+2j, 1-2j, 2+j, 2-j])) Traceback (most recent call last):   File "<stdin>", line 1, in <module> TypeError: no ordering relation is defined for complex numbers 

Second, by not testing for equality on objects, itertools.permutations avoids paying the cost of calling the __eq__ method in the usual case where it's not necessary.

Basically, itertools.permutations solves the common case reliably and cheaply. There's certainly an argument to be made that itertools ought to provide a function that avoids duplicate permutations, but such a function should be in addition to itertools.permutations, not instead of it. Why not write such a function and submit a patch?

like image 76
Gareth Rees Avatar answered Oct 01 '22 23:10

Gareth Rees


I'm accepting the answer of Gareth Rees as the most appealing explanation (short of an answer from the Python library designers), namely, that Python's itertools.permutations doesn't compare the values of the elements. Come to think of it, this is what the question asks about, but I see now how it could be seen as an advantage, depending on what one typically uses itertools.permutations for.

Just for completeness, I compared three methods of generating all distinct permutations. Method 1, which is very inefficient memory-wise and time-wise but requires the least new code, is to wrap Python's itertools.permutations, as in zeekay's answer. Method 2 is a generator-based version of C++'s next_permutation, from this blog post. Method 3 is something I wrote that is even closer to C++'s next_permutation algorithm; it modifies the list in-place (I haven't made it too general).

def next_permutationS(l):     n = len(l)     #Step 1: Find tail     last = n-1 #tail is from `last` to end     while last>0:         if l[last-1] < l[last]: break         last -= 1     #Step 2: Increase the number just before tail     if last>0:         small = l[last-1]         big = n-1         while l[big] <= small: big -= 1         l[last-1], l[big] = l[big], small     #Step 3: Reverse tail     i = last     j = n-1     while i < j:         l[i], l[j] = l[j], l[i]         i += 1         j -= 1     return last>0 

Here are some results. I have even more respect for Python's built-in function now: it's about three to four times as fast as the other methods when the elements are all (or almost all) distinct. Of course, when there are many repeated elements, using it is a terrible idea.

Some results ("us" means microseconds):  l                                       m_itertoolsp  m_nextperm_b  m_nextperm_s [1, 1, 2]                               5.98 us       12.3 us       7.54 us [1, 2, 3, 4, 5, 6]                      0.63 ms       2.69 ms       1.77 ms [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]         6.93 s        13.68 s       8.75 s  [1, 2, 3, 4, 6, 6, 6]                   3.12 ms       3.34 ms       2.19 ms [1, 2, 2, 2, 2, 3, 3, 3, 3, 3]          2400 ms       5.87 ms       3.63 ms [1, 1, 1, 1, 1, 1, 1, 1, 1, 2]          2320000 us    89.9 us       51.5 us [1, 1, 2, 2, 3, 3, 4, 4, 4, 4, 4, 4]    429000 ms     361 ms        228 ms 

The code is here if anyone wants to explore.

like image 22
ShreevatsaR Avatar answered Oct 01 '22 22:10

ShreevatsaR