Присоединение к ряду заказанного целого числа, приводящего к итераторам Python

C # против Руби

Являются ли эти два по существу одним и тем же? Они очень похожи на меня.

Они очень разные.

Прежде всего, лямбды в C # делают две очень разные вещи, только одна из которых имеет эквивалент в Ruby. (И этот эквивалент, сюрприз, лямбды, а не блоки.)

В C # литералы лямбда-выражения перегружены. (Интересно, что, насколько мне известно, это только перегруженные литералы ). И они перегружены типом результата . (Опять же, они являются только вещью в C #, которая может быть перегружена на тип результата, методы могут быть перегружены только на их типы аргумента.)

Литералы C # лямбда-выражения могут либо может быть анонимной частью исполняемого кода , либо абстрактным представлением анонимной части исполняемого кода, в зависимости от того, является ли их тип результата Func / Action, или Expression.

Ruby не имеет эквивалента для последней функциональности (ну, есть специфичные для интерпретатора непереносимые нестандартизированные расширения). И эквивалент для прежней функциональности - лямбда, а не блок.

Синтаксис Ruby для лямбды очень похож на C #:

->(x, y) { x + y }           # Ruby
(x, y) => { return x + y; } // C#

В C # вы можете удалить return, точку с запятой и фигурные скобки, если у вас есть только одно выражение в качестве body:

->(x, y) { x + y }  # Ruby
(x, y) => x + y    // C#

Вы можете опустить скобки, если у вас есть только один параметр:

-> x { x }  # Ruby
x => x     // C#

В Ruby вы можете опустить список параметров, если он пуст:

-> { 42 }  # Ruby
() => 42  // C#

Альтернативой использованию литерального лямбда-синтаксиса в Ruby является передача аргумента блока методу Kernel#lambda:

->(x, y) { x + y }
lambda {|x, y| x + y } # same thing

Основное различие между этими двумя заключается в том, что вы не Не знаю, что делает lambda, поскольку он может быть переопределен, переписан, упакован или изменен иным образом, тогда как поведение литералов не может быть изменено в Ruby.

В Ruby 1.8 вы также можете использовать Kernel#proc, хотя вам, вероятно, следует избегать этого, так как этот метод делает что-то другое в 1.9.

Другим отличием Ruby от C # является синтаксис для , который называет лямбда-выражением:

l.()  # Ruby
l()  // C#

Т.е. в C # вы используете тот же синтаксис для вызова лямбды, который вы использовали бы для вызова чего-либо еще, тогда как в Ruby синтаксис для вызова метода отличается от синтаксиса для вызова любого другого типа вызываемого объекта.

Другое отличие состоит в том, что в C # () встроен в язык и доступен только для определенных встроенных типов, таких как методы, делегаты, Action s и Func s, тогда как в Ruby .() просто синтаксический сахар для .call() и, таким образом, может работать с любым объектом , просто реализуя метод call.

procs vs. lambdas

Итак, что же являются лямбдами? Ну, они экземпляры класса Proc. За исключением небольшого усложнения: на самом деле есть два разных типа экземпляров класса Proc, которые немного различаются. (ИМХО, класс Proc следует разделить на два класса для двух разных типов объектов.)

В частности, не все Proc являются лямбдами. Вы можете проверить, является ли Proc лямбда-выражением, вызвав метод Proc#lambda?. (Обычное соглашение заключается в том, чтобы называть лямбда-выражения Proc s лямбда-выражениями, а не лямбда-выражения Proc просто «процессами».) до Kernel#proc. Тем не менее, обратите внимание, что до Ruby 1.9 Kernel#proc создает лямбду , а не proc.

11148 В чем разница? По сути, лямбды ведут себя больше как методы, а проки - как блоки.

Если вы следили за некоторыми обсуждениями списков рассылки Project Lambda для Java 8, вы могли столкнуться с проблемой, заключающейся в том, что не совсем понятно, как нелокальный поток управления должен вести себя с лямбдами. В частности, есть три возможных разумных поведения для return (ну, три возможных , но только два действительно имеют смысл ) в лямбда-выражении:

  • возврат из лямбды
  • возврат из метода лямбда был вызван из
  • return из метода, который лямбда была создана в

Этот последний немного ненадежен, так как в общем случае метод будет иметь уже возвращенный , но два других имеют смысл, и ни один не является более правильным или более очевидным, чем другой. Текущее состояние Project Lambda для Java 8 состоит в том, что они используют два разных ключевых слова (return и yield). Ruby использует два различных типа Proc s:

  • возврата из вызывающего метода (так же, как блоки)
  • лямбда-выражения возвращаются из лямбды (точно так же, как методы)

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

  • вы можете передавать в аргумент больше аргументов, чем параметров, и в этом случае лишние аргументы будут игнорироваться
  • вы можете передать меньше аргументов в процесс, чем есть параметры, и в этом случае избыточные параметры будут связаны с nil
  • , если вы передадите одиночный аргумент , который является Array (или отвечает на to_ary), и процедура имеет несколько параметров, массив будет распакован, а элементы привязаны к параметрам (точно так же, как и в случае назначения деструктурирования)

Блоки : легковесные проки

Блок - это, по сути, легковесный проц. Каждый метод в Ruby имеет ровно один блочный параметр , который фактически не отображается в списке параметров (подробнее об этом позже), то есть неявно. Это означает, что в каждый метод , вызывающий , вы можете передать аргумент блока, независимо от того, ожидает метод метод или нет.

Поскольку блок не отображается в списке параметров, нет имени, которое вы можете использовать для ссылки на него. Итак, как вы используете это? Ну, единственные две вещи, которые вы можете сделать (не совсем, но об этом позже), это вызвать неявно через ключевое слово yield и проверить, был ли блок передан через block_given?. (Поскольку имени нет, вы не можете использовать методы call или nil?. Как бы вы их назвали?)

Большинство реализаций Ruby реализуют блоки очень легковесным способом. В частности, они на самом деле не реализуют их как объекты. Однако, поскольку у них нет имени, вы не можете ссылаться на них, поэтому на самом деле невозможно определить, являются ли они объектами или нет. Вы можете просто думать о них как о процах, что облегчает эту задачу, поскольку есть одна менее важная концепция, о которой следует помнить. Просто учтите тот факт, что они на самом деле не реализованы как блоки, как оптимизацию компилятора.

to_proc и &

В есть способ ссылки на блок: оператор & сигил / модификатор / унарный префиксный оператор. Он может появляться только в списках параметров и списках аргументов.

В списке параметров это означает « заключить неявный блок в процесс и связать его с этим именем». В списке аргументов это означает « развернуть этот Proc в блок».

def foo(&bar)
end

Внутри метода, bar теперь привязан к объекту proc, который представляет блок. Это означает, например, что вы можете сохранить его в переменной экземпляра для последующего использования.

baz(&quux)

В этом случае baz на самом деле является методом, который принимает нулевые аргументы. Но, конечно, он принимает неявный аргумент блока, который принимают все методы Ruby. Мы передаем содержимое переменной quux, но сначала развернем его в блок.

Эта «развертка» на самом деле работает не только для Proc с. & сначала вызывает to_proc объект, чтобы преобразовать его в процесс. Таким образом, любой объект может быть преобразован в блок.

Наиболее широко используемым примером является Symbol#to_proc, который впервые появился где-то в конце 90-х, я считаю. Он стал популярным, когда его добавили в ActiveSupport, откуда он распространился на Facets и другие библиотеки расширений. Наконец, он был добавлен в базовую библиотеку Ruby 1.9 и перенесен в 1.8.7. Это довольно просто:

class Symbol
  def to_proc
    ->(recv, *args) { recv.send self, *args }
  end
end

%w[Hello StackOverflow].map(&:length) # => [5, 13]

Или, если вы интерпретируете классы как функции для создания объектов, вы можете сделать что-то вроде этого:

class Class
  def to_proc
    -> *args { new *args }
  end
end

[1, 2, 3].map(&Array) # => [[nil], [nil, nil], [nil, nil, nil]]

Method с и UnboundMethod с

Другим классом, представляющим фрагмент исполняемого кода, является класс Method. Method объекты являются реципиентами для методов. Вы можете создать объект Method, вызвав Object#method для любого объекта и передав имя метода, который вы хотите изменить:

m = 'Hello'.method(:length)
m.() #=> 5

или используя метод оператор ссылки .: :

m = 'Hello'.:length
m.() #=> 5

Method s отвечает на to_proc, так что вы можете передавать их везде, где можете передать блок:

[1, 2, 3].each(&method(:puts))
# 1
# 2
# 3

An UnboundMethod - это прокси для метода, который еще не был привязан к получателю, то есть метод, для которого self еще не был определен. Вы не можете вызвать UnboundMethod, но вы можете bind сделать это для объекта (который должен быть экземпляром модуля, из которого вы получили метод), который преобразует его в Method.

UnboundMethod объекты создаются путем вызова одного из методов из семейства Module#instance_method, передавая имя метода в качестве аргумента.

u = String.instance_method(:length)

u.()
# NoMethodError: undefined method `call' for #<UnboundMethod: String#length>

u.bind(42)
# TypeError: bind argument must be an instance of String

u.bind('Hello').() # => 5

Обобщенные вызываемые объекты

Как я уже намекал выше: в Proc и Method s нет особенного. Любой объект , который отвечает на call, может быть вызван, и любой объект , который отвечает на to_proc, может быть преобразован в Proc и, таким образом, развернут в блок и передан в метод, который ожидает блок.

История

Лямбда-выражение позаимствовало свою идею у Руби?

Вероятно, нет. Большинство современных языков программирования имеют некоторую форму анонимного буквального блока кода: Lisp (1958), Scheme, Smalltalk (1974), Perl, Python, ECMAScript, Ruby, Scala, Haskell, C ++, D, Objective-C, даже PHP (! ). И, конечно, вся идея восходит к «лямбда-калькуле» Алонзо Черча (1935 и даже ранее).

17
задан 6 revs 22 July 2009 в 00:45
поделиться

6 ответов

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]
16
ответ дан 30 November 2019 в 12:07
поделиться

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

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()
6
ответ дан 30 November 2019 в 12:07
поделиться
def postings(posts):
    sets = (set(l) for l in posts)
    return sorted(reduce(set.intersection, sets))

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

6
ответ дан 30 November 2019 в 12:07
поделиться

Если это действительно длинные (или даже бесконечные) последовательности, и вы не хотите загружать все в набор заранее, вы можете реализовать это с прогнозированием по 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]
3
ответ дан 30 November 2019 в 12:07
поделиться

Что насчет этого:

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)]

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

2
ответ дан 30 November 2019 в 12:07
поделиться

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

    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()) 
1
ответ дан 30 November 2019 в 12:07
поделиться
Другие вопросы по тегам:

Похожие вопросы: