2009-07-21 3 views
30

Предположим, что у меняzipWith (отображение на нескольких Seq) в Scala

val foo : Seq[Double] = ... 
val bar : Seq[Double] = ... 

и я желаю, чтобы произвести, где SEQ Баз (я) = Foo (I) + бар (I). Один из способов я могу думать, чтобы сделать это

val baz : Seq[Double] = (foo.toList zip bar.toList) map ((f: Double, b : Double) => f+b) 

Однако, это чувствует себя как уродливое и неэффективно - я должен преобразовать оба seqs в списки (которые взрываются с ленивыми списками), создать этот временный список кортежей, только для отображения над ним и пусть он будет GCed. Может быть, потоки решают ленивую проблему, но в любом случае это кажется излишне уродливым. В lisp функция отображения отображает несколько последовательностей. Я бы написал

(mapcar (lambda (f b) (+ f b)) foo bar) 

И никакие временные списки не создавались бы нигде. Есть ли функция масштабирования по нескольким спискам в Scala или zip в сочетании с деструкцией действительно «правильного» способа сделать это?

ответ

15

Функция, которую вы хотите, называется zipWith, но она не является частью стандартной библиотеки. Это будет в 2.8 (ОБНОВЛЕНИЕ: По-видимому, нет, см. Комментарии).

foo zipWith((f: Double, b : Double) => f+b) bar 

См. this Trac ticket.

+2

Извините, нет zipWith на Scala 2.8. –

+3

Просто, чтобы быть ясным (и я уверен, что Даниэль согласился бы), Скале нечего извиняться за это - то, что вы получаете с Scala, еще лучше. См. Ответ Мартина ниже и Даниил. Было бы неплохо, если бы кто-нибудь смог утвердить ответ Мартина на этот вопрос ... – AmigoNico

3

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

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

Обновление: Чтобы быть четким, вам необходимо использовать потоки (ленивые списки), а не списки. В потоках Scala есть zip, который работает ленивым способом, и поэтому вы не должны преобразовывать вещи в списки.

В идеале алгоритм должен быть способен работать на двух бесконечных потоков, не взорвав (если он не делает никакого folding, конечно, а просто читает и генерирует потоки).

+0

Я знаю, что такое ленивый список, но я не очень хорошо знаком с Scala. foo.toList не ленив, не так ли? В любом случае, исходя из фона CL, очень странно, что нет функции mapMultiple, поэтому моя причина задавать этот вопрос - это просто выяснить, каков правильный способ Scala. Производительность на самом деле очень важна; это в моем внутреннем цикле, и, хотя я могу попытаться оптимизировать позже, я хотел бы сначала его закодировать разумным способом. – bsdfish

+0

Я говорю вам, что вы были правы, когда сказали: «Может быть, решает проблему» - используйте поточную версию zip. Если вы считаете, что небольшие ассигнования оказывают давление на GC, напишите императивный эквивалент на языке JVM по вашему выбору и настало время посмотреть, правда ли это (я часто был поражен бриллиантами виртуальных машин, занимающихся партиями небольших коротких ассигнований). –

9

Ну, это, отсутствие zip, является недостатком 2.7 Seag Scala. Scala 2.8 имеет продуманный дизайн коллекции, чтобы заменить специальный способ, которым стали коллекции, представленные в версии 2.7 (обратите внимание, что они не все были созданы сразу, с единым дизайном).

Теперь, когда вы хотите избежать создания временной коллекции, вы должны использовать «проекцию» на Scala 2.7 или «view» на Scala 2.8. Это даст вам тип коллекции, для которого некоторые инструкции, в частности карта, flatMap и фильтр, являются нестрогими. На Scala 2.7 проекция списка представляет собой поток. На Scala 2.8 есть SequenceView последовательности, но в Sequence есть zipWith, вам даже не понадобится.

Сказав, что, как уже упоминалось, JVM оптимизирован для обработки временных распределений объектов, а при запуске в режиме сервера оптимизация времени выполнения может творить чудеса. Поэтому преждевременно не оптимизируйте.Проверьте код в тех условиях, в которых он будет запущен, и если вы еще не планировали его запускать в режиме сервера, переосмыслите это, если код, как ожидается, будет длительным, и optmize, когда/где/при необходимости.

EDIT

Что на самом деле будет доступна на Scala 2.8 заключается в следующем:

(foo,bar).zipped.map(_+_) 
0

UPDATE: Было отмечено, (в комментариях), что этот "ответ" Безразлично Фактически я обращаюсь к задаваемому вопросу. Этот ответ будет отображать над каждым комбинацию изfoo и bar, производя N х M элементов, вместо мин (M, N) в соответствии с просьбой. Итак, это неправильный, но ушел для потомства, так как это хорошая информация.


Лучший способ сделать это с flatMap в сочетании с map. Код говорит громче, чем слова:

foo flatMap { f => bar map { b => f + b } } 

Это произведет один Seq[Double], точно так, как и следовало ожидать. Эта модель является настолько распространенным, что Scala на самом деле включает в себя некоторую синтаксическую магию, которая реализует его:

for { 
    f <- foo 
    b <- bar 
} yield f + b 

Или, наоборот:

for (f <- foo; b <- bar) yield f + b 

for { ... } синтаксис действительно самый идиоматических способ сделать это. Вы можете продолжать добавлять предложения генератора (например, b <- bar) по мере необходимости. Таким образом, если вдруг становится Seq s, с которым вы должны переместиться, вы можете легко масштабировать свой синтаксис вместе с вашими требованиями (монетой фразы).

+4

Я пока не буду голосовать по этому вопросу, но это совершенно неправильно. Это приведет к элементам NxN, и что вопрос задал результаты только по N элементам. Вы добавляете каждую комбинацию элементов из foo и bar, но запрашивается foo (i) + bar (i). –

+1

Хорошая точка. Было немного рано утром, так что, по-видимому, мой мозг не работал должным образом. Я собираюсь удалить этот ответ, так как он действительно не дает то, что спрашивал создатель. –

+1

На самом деле, я просто обновлю ответ. Это хорошая информация, просто не применимая к этому вопросу. –

74

В Scala 2.8:

val baz = (foo, bar).zipped map (_ + _) 

И она работает более двух операндов таким же образом. То есть вы могли бы следить за этим с:

(foo, bar, baz).zipped map (_ * _ * _) 
+0

Однако он не работает с более чем тремя операндами. Это верно? – Debilski

+14

Правильно, 'zipped' определяется только на' Tuple2' и 'Tuple3'. Абстрагирование по arity является одним из последних границ в Scala (и большинство других статически типизированных langauges). HLists предлагают одну возможность ... – retronym

+7

@retronym есть также '<*>'/'<$>' подход, который мы берем с ZipLists в Haskell, где вам фактически не нужно абстрагироваться от arity из-за однородности карриных функций. Поэтому, если бы я хотел сделать zipWith с 5-параметром 'f', я мог бы сделать больше или меньше' f <$> xs <*> ys <*> zs <*> ps <*> qs'. К сожалению, в Scala гораздо более болезненны функции в карри. (Возможно, некоторые идеи могут быть перенесены, поскольку этот подход кажется значительно более элегантным, чем «HList». –

1

Когда сталкивался с подобной задачей, я добавил следующее сутенера к Iterable с:

implicit class IterableOfIterablePimps[T](collOfColls: Iterable[Iterable[T]]) { 
    def mapZipped[V](f: Iterable[T] => V): Iterable[V] = new Iterable[V] { 
    override def iterator: Iterator[V] = new Iterator[V] { 
     override def next(): V = { 
     val v = f(itemsLeft.map(_.head)) 
     itemsLeft = itemsLeft.map(_.tail) 
     v 
     } 

     override def hasNext: Boolean = itemsLeft.exists(_.nonEmpty) 

     private var itemsLeft = collOfColls 
    } 
    } 
} 

Имея это, можно сделать что-то вроде:

val collOfColls = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) 
collOfColls.mapZipped { group => 
    group // List(1, 4, 7), then List(2, 5, 8), then List(3, 6, 9) 
} 

Обратите внимание, что вам следует внимательно рассмотреть тип коллекции, переданный как вложенный Iterable, так как tail и head будут периодически вызываться на i т. Итак, в идеале вы должны пройти Iterable[List] или other с быстрым tail и head.

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

Смежные вопросы