Pergunta

Suponha que eu tenho

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

e gostaria de produzir uma seq onde o baz (i) = foo (i) + bar (i). Uma maneira que eu posso pensar de fazer isso é

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

No entanto, este se sente tanto feia e ineficiente - Eu tenho que converter ambos os PDCs para listas (que explode com listas preguiçoso), criar esta lista temporária de tuplas, apenas para mapear sobre ele e deixá-lo ser GCed. Talvez córregos resolver o problema preguiçoso, mas em qualquer caso, este se sente como desnecessariamente feio. Em Lisp, a função de mapa seria mapear ao longo de várias sequências. Eu ia escrever

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

E há listas de temporário se tornasse criado em qualquer lugar. Existe um mapa-over-múltiplas listas de funcionar em Scala, ou seja zip combinada com desestruturação realmente o caminho 'certo' para fazer isso?

Foi útil?

Solução

A função desejada é chamado zipWith, mas não é uma parte da biblioteca padrão. Será em 2.8. (UPDATE: Aparentemente não, ver comentários)

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

este Trac bilhete .

Outras dicas

Em Scala 2.8:

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

E trabalha há mais de dois operandos da mesma forma. Ou seja, Você poderia, então, acompanhar esta questão com:

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

Bem, isso, a falta de zip, é uma deficiência em 2,7 Seq do Scala. Scala 2.8 tem um design coleção bem-pensamento, para substituir o modo ad-hoc as coleções presente em 2,7 veio a ser (note que eles não foram todos criados de uma só vez, com um design unificado).

Agora, quando você quer evitar a criação de coleção temporária, você deve usar "projeção" em Scala 2.7, ou "vista" em Scala 2.8. Isto lhe dará um tipo de coleção para a qual certas instruções, particularmente mapear, flatMap e filtro, não são rigorosos. Em Scala 2,7, a projecção de uma lista é um Stream. Em Scala 2.8, há um SequenceView de uma seqüência, mas há uma zipWith ali mesmo na seqüência, você nem sequer precisa.

Dito isto, como mencionado, JVM é otimizado para lidar com alocações de objetos temporários, e, quando executado no modo servidor, a otimização de tempo de execução pode fazer maravilhas. Então, não otimizar prematuramente. Teste o código nas condições que serão executados - e se você não tem planejado para executá-lo no modo de servidor, em seguida, repensar que, se o código está prevista para ser de longa duração, e optmize quando / onde / se necessário <. / p>

Editar

O que é realmente vai estar disponível no Scala 2.8 é o seguinte:

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

Uma lista preguiçoso não é uma cópia de uma lista - é mais como um único objeto. No caso de uma implementação zip preguiçoso, cada vez que é solicitado para o próximo item, ele agarra um item de cada uma das duas listas de entrada e cria uma tupla a partir deles, e você, em seguida, quebrar o tuple distante com o padrão de correspondência em seu lambda.

Assim, nunca há uma necessidade de criar uma cópia completa da lista de entrada inteira (s) antes de começar a operar sobre eles. Ela resume-se a um padrão de alocação muito semelhante a qualquer aplicativo em execução no JVM -. Lotes de alocações de muito curta duração, mas pequenas, que a JVM é otimizado para lidar com

Update: para ser claro, você precisa estar usando Streams (listas preguiçosos) não Lists. fluxos de Scala tem um fecho de correr que funciona da maneira preguiçosa, e por isso você não deve ser converter as coisas em listas.

O ideal é o seu algoritmo deve ser capaz de trabalhar em dois infinito córregos sem explodir (assumindo que não faz qualquer folding, é claro, mas apenas lê e gera fluxos).

UPDATE: Ele tem sido apontado (nos comentários) que esta "resposta" na verdade não abordar a questão que se coloca. Esta resposta vai mapear sobre cada combinação de foo e bar, produzindo N x M elementos, em vez de o min (M, N) , conforme solicitado . Então, este é errado , mas deixou para a posteridade, já que é uma boa informação.


A melhor maneira de fazer isso é com flatMap combinado com map. Código fala mais alto do que palavras:

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

Isto irá produzir uma única Seq[Double], exatamente como seria de esperar. Esse padrão é tão comum que Scala, na verdade, inclui um pouco de magia sintática que implementa-lo:

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

Ou, alternativamente:

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

A sintaxe for { ... } é realmente a maneira mais idiomática de fazer isso. Você pode continuar a adicionar cláusulas gerador (por exemplo b <- bar) conforme necessário. Assim, se ele de repente se torna três Seqs que você deve mapear longo, você pode facilmente escalar sua sintaxe, juntamente com os seus requisitos (para cunhar uma frase).

Quando enfrentou uma tarefa semelhante, eu adicionei o seguinte cafetão para Iterables:

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
    }
  }
}

Tendo isso, pode fazer algo como:

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

Observe que você deve considerar cuidadosamente tipo de coleção passada como Iterable aninhada, desde tail e head serão recorrentemente chamada nele. Assim, idealmente você deve passar Iterable[List] ou outra coleção com tail rápido e head.

Além disso, este código espera coleções aninhadas do mesmo tamanho. Esse foi o meu caso de uso, mas eu suspeito que isso pode ser melhorada, se necessário.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top