views:

168

answers:

6

I want to zip even and odd elements in a list to make a list of pairs, like that:

["A", "B", "C", "D", "E", "F"] -> [("A", "B"), ("C", "D"), ("E", "F")]

What is the most concise expression to do this in elegant in functional way?

+4  A: 

Untested:

def ziptwo(l: List[String]): List[(String, String)] = l match {
    case Nil => Nil
    case a :: b :: rest =>
         Pair(a,b) :: ziptwo(rest)
}
legoscia
This won't work for long lists as it is not tail recursive.
Thomas Jung
+4  A: 

in Scala 2.8 you can do:

def pairs[T](xs: List[T]) =
  xs.grouped(2)
    .map{case List(a, b) => (a,b)}
    .toList
Seth Tisue
Shucks, beat me by 34 seconds! :) You do want `partialMap` / `collect`, though, or it will throw an exception on odd-length lists.
Rex Kerr
You could call that a bug, or a feature :-)
Seth Tisue
+3  A: 

In 2.8, you'd probably use methods:

scala> val a = "ABCDEF".toList.map(_.toString) 
a: List[java.lang.String] = List(A, B, C, D, E, F)

scala> a.grouped(2).partialMap{ case List(a,b) => (a,b) }.toList
res0: List[(java.lang.String, java.lang.String)] = List((A,B), (C,D), (E,F))

(This is 2.8.0 Beta1; the latest trunk has collect in place of partialMap.)

In 2.7--and not a bad runner-up in 2.8--you could create a recursive method as legoscia did:

def zipPairs[A](la : List[A]): List[(A,A)] = la match {
  case a :: b :: rest => (a,b) :: zipPairs(rest)
  case _ => Nil
}

scala> zipPairs(a)
res1: List[(java.lang.String, java.lang.String)] = List((A,B), (C,D), (E,F))

Edit: here's another briefer approach that works on 2.7 also:

scala> (a zip a.drop(1)).zipWithIndex.filter(_._2 % 2 == 0).map(_._1)
res2: List[(java.lang.String, java.lang.String)] = List((A,B), (C,D), (E,F))

(Note the use of drop(1) instead of tail so it works with empty lists.)

Rex Kerr
+1  A: 
def pairify[T](list: List[T]): List[(T, T)] = list match {
  case Nil => Nil
  case x :: y :: xs => (x, y) :: pairify(xs)
  case _ => error("odd length list!")
}
missingfaktor
+3  A: 

The only advantage of having everyone come up with the most obvious ways of doing it is that I have to think harder about alternate solutions. So here is one that works on Scala 2.8. On Scala 2.7, replace view with projection.

def everyNofM[T](l: List[T], n: Int, m: Int) = 
  l.view.zipWithIndex.filter(_._2 % m == n).map(_._1)
def evens[T](l: List[T]) = everyNofM(l, 0, 2)
def odds[T](l: List[T]) = everyNofM(l, 1, 2) 
def zip[T](l: List[T]) = evens(l) zip odds(l) toList

Strictly speaking, view/projection is unnecessary, but it avoids unnecessary creation of intermediate results.

Other fun ways of doing it:

def zip[T](l: List[T]) = l.indices.partition(_ % 2 == 0).zipped.map(
  (x: Int, y: Int) => (l(x), l(y))
).toList

def zip[T](l: List[T]) = l.zipWithIndex.partition(_._2 % 2 == 0).zipped.map(
  (x, y) => (x._1, y._1)
)

PS: Bonus point to whoever gets the pun. ;-)

Daniel
+1, Strict? [15-char filler]
missingfaktor
A: 

This allows incomplete pairs:

def pairwise [T] (xs: List[T]) : List [(Option[T], Option[T])] = xs match {
     case (x :: y :: xsr) => (Some (x), Some (y)) :: pairwise (xsr)             
     case (x :: Nil) => List ((Some (x), None))                                 
     case (_) => Nil }   
user unknown
I think a `Pair` of `Option` is a bad choice. How about a `List[Either[Pair[T, T], Tuple1[T]]]` instead?
Daniel
Why is a Pair of Option a bad choice? Because you may get a Pair of Nones? Yes, that's not pretty, but you keep the information where an element comes from: [code]List ((Alicia, New Mexico), (Stefan, Berlin), (Paris))// Paris Hilton or Paris/France? List ((Alicia, New Mexico), (Stefan, Berlin), (None, Paris))List ((Alicia, New Mexico), (Stefan, Berlin), (Paris, None))[/code]In the second example no information is lost.
user unknown