开发者

python: how to make a product of iterables without repeating the items?

开发者 https://www.devze.com 2023-03-10 16:13 出处:网络
I need a function that functions in a similar manner as itertools.product, but without repeating items.

I need a function that functions in a similar manner as itertools.product, but without repeating items.

For example:

no_repeat_product((1,2,3), (5,6))
= ((1,5), (None,6), (2,5), (None,6), ...(None,6))
no_repeat_product((1,2,3), (5,6), (7,8))
= ((1,5,7), (None,None,8), (None,6,7), (None,None,8), ...(None,None,8))

Any ideas?

Edit: my wording was not correct. I meant without repeating the numbers that are same in successive output values.

For example,

itertools.product((1,2,3), (4,5), (6,7) is
(1,4,6)
(1,4,7), etc  

Here 1,4 appears twice in the output. So, I want to skip writing the numbers when they are the same as the item before. So, the output I want is:

(1,4,6)  
(None,None,7)  

When it is None, it is understood that it is same as its previous item in the result.

Further Edit:

My explanation was still lacking in clarity. Let us assume that I have list of books, chapter numbers, and page numbers. Assume that each book has same number of chapters and each chapter has same number of pages. So, the lists are (book1, book2, book3), (chap1, chap2), (page1, page2, page3).

Now, suppose I want to collect descriptions for each page:

itertools.product will give me:

(book1, chap1, page1), (book1, chap1, page2)..... (book3, chap2, page3)

If I have arranged these pages successively, I do not need to have descriptions repeating. So, if the book and the chapter are the same, in the second page, I don't need to have book and chapter names So, the 开发者_StackOverflow社区output should be:

(book1, chap1, page1), (None, None, page2), ..   
(when the pages of first chapter are over..) (None, chap2, page1), (None, None, page2)......  
(when the chapters of the first book are over..)(book2, chap1, page1)..............  
(None, None, page3)  


Based on your comment stating "because (None,None,8) does not occur successively", I'm assuming you only want to None-ify elements that appear in the output immediately before.

def no_repeat_product(*seq):
    previous = (None,)*len(seq)
    for vals in itertools.product(*seq):
        out = list(vals)
        for i,x in enumerate(out):
            if previous[i] == x:
                out[i] = None
        previous = vals
        yield(tuple(out))   

Or, if your prefer a more compact and efficient (but less readable) version:

def no_repeat_product(*seq):
    previous = (None,)*len(seq)
    for vals in itertools.product(*seq):
        out = tuple((y,None)[x==y] for x,y in itertools.izip(previous, vals))
        previous = vals
        yield(out)       

They both do the same thing, and produces the following results:

for x in no_repeat_product((1,2,3), (5,6), (7,8)): 
    print x 

Output:

(1, 5, 7)
(None, None, 8)
(None, 6, 7)
(None, None, 8)
(2, 5, 7)
(None, None, 8)
(None, 6, 7)
(None, None, 8)
(3, 5, 7)
(None, None, 8)
(None, 6, 7)
(None, None, 8)

For an example in the context of your updated question:

books = ("Book 1", "Book 2")
chapters = ("Chapter 1", "Chapter 2")
pages = ("Page 1", "Page 2", "Page 3")

s1 = max(map(len, books)) + 2  # size of col 1
s2 = max(map(len, chapters)) + 2  # size of col 2
x = lambda s, L: (s, "")[s == None].ljust(L)  # Left justify, handle None

for book, chapter, page in no_repeat_product(books, chapters, pages):
    print x(book, s1), x(chapter, s2), page

This gives you:

Book 1   Chapter 1   Page 1
                     Page 2
                     Page 3
         Chapter 2   Page 1
                     Page 2
                     Page 3
Book 2   Chapter 1   Page 1
                     Page 2
                     Page 3
         Chapter 2   Page 1
                     Page 2
                     Page 3


def no_repeat_product(*seq):
    def no_repeat(x, known):
        if x in known:
            return None
        else:
            known.add(x)
            return x

    known = set()
    for vals in itertools.product(*seq):
        yield tuple(no_repeat(x, known) for x in vals)

This doesn't return any value that has already been seen before. Is this what you want?

If you only want to limit repetition of a value that appeared in the previous set of results, it can be done this way:

def no_repeat_product(*seq):
    prev = None
    for vals in itertools.product(*seq):
        if prev is None:
            yield vals
        else:
            yield tuple((x if x != y else None) for x, y in zip(vals, prev))
        prev = vals


Sort of a functional version of @ShawnChin's answer, using a tee'ed iterator:

from itertools import product,tee,izip
def product_without_repeats(*seq):
    previter,curriter = tee(product(*seq))
    try:
        yield next(curriter)
    except StopIteration:
        pass
    else:
        for prev,curr in izip(previter,curriter):
            yield tuple(y if x!=y else None for x,y in izip(prev,curr))
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号