Подтвердить что ты не робот

Объединение набора упорядоченных целых чисел с использованием итераторов Python

Вот, казалось бы, простая проблема: учитывая список итераторов, которые приводят последовательности целых чисел в порядке возрастания, напишите сжатый генератор, который дает только целые числа, которые появляются в каждой последовательности.

Прочитав несколько статей прошлой ночью, я решил взломать полностью минимальный полнотекстовый индекс в Python, как показано здесь (хотя это версия довольно старая).

Моя проблема связана с функцией search(), которая должна проходить через каждый список проводки и отображать только идентификаторы документов, которые отображаются в каждом списке. Как видно из приведенной выше ссылки, моя текущая нерекурсивная "рабочая" попытка ужасна.

Пример:

postings = [[1,   100, 142, 322, 12312],
            [2,   100, 101, 322, 1221],
            [100, 142, 322, 956, 1222]]

Должен выдать:

[100, 322]

Для этого существует хотя бы одно изящное решение для рекурсивных функций, но я хотел бы избежать этого, если это возможно. Тем не менее, решение, включающее вложенные выражения генератора, itertools злоупотребления или любой другой вид гольф-кода, более чем приветствуется.: -)

Должна быть предусмотрена возможность для функции требовать столько шагов, сколько есть элементов в самом маленьком списке, и не всасывать весь набор целых чисел в память. В будущем эти списки могут считываться с диска и больше, чем доступная оперативная память.

За последние 30 минут у меня появилась идея на кончике моего языка, но я не могу понять ее код. Помните, это просто для удовольствия!

4b9b3361

Ответ 1

import heapq, itertools
def intersect(*its):
    for key, values in itertools.groupby(heapq.merge(*its)):
        if len(list(values)) == len(its):
            yield key

>>> list(intersect(*postings))
[100, 322]

Ответ 2

def postings(posts):
    sets = (set(l) for l in posts)
    return sorted(reduce(set.intersection, sets))

... вы можете попробовать и воспользоваться тем фактом, что списки упорядочены, но так как сокращение, выражения генератора и набор все реализованы на C, вам, вероятно, будет трудно работать лучше, чем выше, с логикой реализован в python.

Ответ 3

Это решение вычислит пересечение ваших итераторов. Он работает, продвигая итераторы один шаг за раз и ищет одинаковое значение во всех них. При обнаружении таких значений даются - это делает функцию intersect самой генератором.

import operator

def intersect(sequences):
    """Compute intersection of sequences of increasing integers.

    >>> list(intersect([[1,   100, 142, 322, 12312],
    ...                 [2,   100, 101, 322, 1221],
    ...                 [100, 142, 322, 956, 1222]]))
    [100, 322]
    """
    iterators = [iter(seq) for seq in sequences]
    last = [iterator.next() for iterator in iterators]
    indices = range(len(iterators) - 1)
    while True:
        # The while loop stops when StopIteration is raised. The
        # exception will also stop the iteration by our caller.
        if reduce(operator.and_, [l == last[0] for l in last]):
            # All iterators contain last[0]
            yield last[0]
            last = [iterator.next() for iterator in iterators]

        # Now go over the iterators once and advance them as
        # necessary. To stop as soon as the smallest iterator is
        # exhausted we advance each iterator only once per iteration
        # in the while loop.
        for i in indices:
            if last[i] < last[i+1]:
                last[i] = iterators[i].next()
            if last[i] > last[i+1]:
                last[i+1] = iterators[i+1].next()

Ответ 4

Если это действительно длинные (или даже бесконечные) последовательности, и вы не хотите заранее загружать все в набор, вы можете реализовать это с помощью 1-позиционного окна на каждом итераторе.

EndOfIter = object() # Sentinel value

class PeekableIterator(object):
    def __init__(self, it):
        self.it = it
        self._peek = None
        self.next() # pump iterator to get first value

    def __iter__(self): return self

    def next(self):
        cur = self._peek
        if cur is EndOfIter:
            raise StopIteration()

        try:
            self._peek = self.it.next()
        except StopIteration:
            self._peek = EndOfIter
        return cur

    def peek(self): 
        return self._peek


def contained_in_all(seqs):
   if not seqs: return   # No items
   iterators = [PeekableIterator(iter(seq)) for seq in seqs]
   first, rest = iterators[0], iterators[1:]

   for item in first:
       candidates = list(rest)
       while candidates:
           if any(c.peek() is EndOfIter for c in candidates): return  # Exhausted an iterator
           candidates = [c for c in candidates if c.peek() < item]
           for c in candidates: c.next()

       # Out of loop if first item in remaining iterator are all >= item.
       if all(it.peek() == item for it in rest):
           yield item

Использование:

>>> print list(contained_in_all(postings))
[100, 322]

Ответ 5

Как насчет этого:

import heapq

def inalliters(iterators):
  heap=[(iterator.next(),iterator) for iterator in iterators]
  heapq.heapify(heap)
  maximal = max(heap)[0]
  while True:
    value,iterator = heapq.heappop(heap)
    if maximal==value: yield value
    nextvalue=iterator.next()
    heapq.heappush(heap,(nextvalue,iterator))
    maximal=max(maximal,nextvalue)

postings = [iter([1,   100, 142, 322, 12312]),
            iter([2,   100, 101, 322, 1221]),
            iter([100, 142, 322, 956, 1222])]
print [x for x in inalliters(postings)]

Я не тестировал его очень тщательно (просто использовал ваш пример), но я считаю, что основная идея звучит.

Ответ 6

Я хочу показать, что есть элегантное решение, которое выполняет только итерацию вперед. Извините, я не знаю Python достаточно хорошо, поэтому я использую вымышленные классы. Он читает input, массив итераторов и записывает на output на лету, даже не возвращаясь или используя какую-либо функцию массива!

    def intersect (input, output) 
        do:
            min = input[0]
            bingo = True
            for i in input:
                if (i.cur < min.cur):
                     bingo = False
                     min =  i
            if bingo: 
                output.push(min.cur)
        while (min.step()) 

Ответ 7

Это выполняется в O(n*m), где n - сумма всех длин итератора, а m - количество списков. Его можно сделать O(n*logm), используя кучу в строке 6.

def intersection(its):
  if not its: return
  vs = [next(it) for it in its]
  m = max(vs)
  while True:
    v, i = min((v,i) for i,v in enumerate(vs))
    if v == m:
      yield m
    vs[i] = next(its[i])
    m = max(m, vs[i])