itertools.intersect?
Arnaud Delobelle
arnodel at googlemail.com
Mon Jun 15 22:10:04 CEST 2009
"Andrew Henshaw" <andrew.henshaw at gtri.gatech.edu> writes:
> "Raymond Hettinger" <python at rcn.com> wrote in message
> news:fb1feeeb-c430-4ca7-9e76-fea02ea3ef6f at v23g2000pro.googlegroups.com...
>> [David Wilson]
>>> The problem is simple: given one or more ordered sequences, return
>>> only the objects that appear in each sequence, without reading the
>>> whole set into memory. This is basically an SQL many-many join.
>>
>> FWIW, this is equivalent to the Welfare Crook problem in David Gries
>> book, The Science of Programming, http://tinyurl.com/mzoqk4 .
>>
>>
>>> I thought it could be accomplished through recursively embedded
>>> generators, but that approach failed in the end.
>>
>> Translated into Python, David Gries' solution looks like this:
>>
>> def intersect(f, g, h):
>> i = j = k = 0
>> try:
>> while True:
>> if f[i] < g[j]:
>> i += 1
>> elif g[j] < h[k]:
>> j += 1
>> elif h[k] < f[i]:
>> k += 1
>> else:
>> print(f[i])
>> i += 1
>> except IndexError:
>> pass
>>
>> streams = [sorted(sample(range(50), 30)) for i in range(3)]
>> for s in streams:
>> print(s)
>> intersect(*streams)
>>
>>
>> Raymond
>
> Here's my translation of your code to support variable number of streams:
>
> def intersect(*s):
> num_streams = len(s)
> indices = [0]*num_streams
> try:
> while True:
> for i in range(num_streams):
> j = (i + 1) % num_streams
> if s[i][indices[i]] < s[j][indices[j]]:
> indices[i] += 1
> break
> else:
> print(s[0][indices[0]])
> indices[0] += 1
> except IndexError:
> pass
I posted this solution earlier on:
def intersect(iterables):
nexts = [iter(iterable).next for iterable in iterables]
v = [next() for next in nexts]
while True:
for i in xrange(1, len(v)):
while v[0] > v[i]:
v[i] = nexts[i]()
if v[0] < v[i]: break
else:
yield v[0]
v[0] = nexts[0]()
It's quite similar but not as clever as the solution proposed by
R. Hettinger insofar as it doesn't exploit the fact that if a, b, c are
members of a totally ordered set, then:
if a >= b >= c >= a then a = b = c.
However it can be easily modified to do so:
def intersect(iterables):
nexts = [iter(iterable).next for iterable in iterables]
v = [next() for next in nexts]
while True:
for i in xrange(-1, len(v)-1):
if v[i] < v[i+1]:
v[i] = nexts[i]()
break
else:
yield v[0]
v[0] = nexts[0]()
I haven't really thought about it too much, but there may be cases where
the original version terminates faster (I guess when it is expected that
the intersection is empty).
--
Arnaud
More information about the Python-list
mailing list