How to iterate through all partitions of a list with a condition on the subsets lenghts

好久不见. 提交于 2021-01-28 06:23:13

问题


For certain purposes, I need to generate an iterable that lists all the partitions of a list, but with a condition on the subsets lenghts. That is, I want to partition my list in subsets of equal lenght (=3 here), except the last one if the lenght of the list isn't a multiple of 3.

i.e. ['a','b','c','d','e'] should give all partitions with 2 subsets of lenght 3 and 2.

Namely, if I simply use :

[p for p in multiset_partitions(['a','b','c','d','e'],2)]
Out: 
[[['a', 'b', 'c', 'd'], ['e']],
[['a', 'b', 'c', 'e'], ['d']],
[['a', 'b', 'c'], ['d', 'e']],
         .....
[['a', 'd'], ['b', 'c', 'e']],
[['a', 'e'], ['b', 'c', 'd']],
[['a'], ['b', 'c', 'd', 'e']]]

I get them all. So my best try so far has been to filter out the partitions that contain at least one subset of lenght > 3 :

from sympy.utilities.iterables import multiset_partitions    

def partitions(liste):
   compte = 0
   n = len(liste)//3 + 1
   for p in multiset_partitions(liste,n):
      l = len(p)
      oversize = False
      i = 0
      while not(oversize) and i != l:
         if len(p[i])>3:
            oversize=True
         i+=1

      if oversize == False:
         compte += 1

      #do something with p

   return(compte) #I'm just counting out the number of partitions right now

This does the trick, but is clearly not the most effective way to achieve what I want. Especially that the number of partitions becomes huge very quickly when the lenght of the list grows.

(10 for a length of 5, but 9100 for 10, 800800 for 13...)

What should be the most efficient pythonic way ?

Thanks in advance,

Thierry


回答1:


You can always wrap filter around the partitioning function. You can use a lambda function to ensure all of the elements are of length 3 except the last one.

list(filter(lambda x: all(len(z)==3 for z in x[:-1]), multiset_partitions('abcde', 2)))
# returns:
[[['a', 'b', 'c'], ['d', 'e']],
 [['a', 'b', 'd'], ['c', 'e']],
 [['a', 'b', 'e'], ['c', 'd']],
 [['a', 'c', 'd'], ['b', 'e']],
 [['a', 'c', 'e'], ['b', 'd']],
 [['a', 'd', 'e'], ['b', 'c']]]

You will have to be careful when selecting the number of partitions to ensure you are using ceil. I.e for 10 items, you want ceil(10/3) not 10//3.




回答2:


Thanks James, I just adapted your filter to keep the items with lenght <=3, and it gives the expected result.

def partitions(liste):

    n = len(liste)//3 + 1            
    return(list(filter(lambda x: all(len(z)<=3 for z in x), multiset_partitions(liste, n))))

And thus,

partitions([1,2,3,4,5])

[[[1, 2, 3], [4, 5]],
[[1, 2, 4], [3, 5]],
[[1, 2, 5], [3, 4]],
[[1, 2], [3, 4, 5]],
[[1, 3, 4], [2, 5]],
[[1, 3, 5], [2, 4]],
[[1, 3], [2, 4, 5]],
[[1, 4, 5], [2, 3]],
[[1, 4], [2, 3, 5]],
[[1, 5], [2, 3, 4]]]

Gives the expected 10 results.

Thanks !




回答3:


Turn partitions into a generator by yielding a partition that matches your criteria on each iteration.

from math import ceil
from sympy.utilities.iterables import multiset_partition

def partitions(liste, m):
    n = ceil(len(liste)/m)
    for p in multiset_partitions(liste,n):
        if not any(list(map(lambda x: len(x) > m, p))):
            yield p

parts = partitions([1,2,3,4,5], 3)
for part in parts:
    print(part)


来源:https://stackoverflow.com/questions/59303187/how-to-iterate-through-all-partitions-of-a-list-with-a-condition-on-the-subsets

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!