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))
精彩评论