2012-04-13 84 views
2

我试图为一个序列实现一个distinctOn函数,该函数将采用一个函数f并返回一个序列,当f应用于它时,每个项目都有一个不同的结果。 EG:Scala:Seq.distinctOn函数的实现

case class Person(name:String, age:Int) 

val people = Seq(Person("Al", 20), Person("Bob", 21), 
       Person("Bob", 24)).distinctOn(_.name) 

//people should be: 

Seq(Person("Al", 20), Person("Bob", 21)) 

其中第一个副本(Al)的返回和订单被保留。我当前的实现包含一个var,而我使用Sets和GroupBy的其他尝试并未保持顺序。有没有更好的方式来实现这个没有var?为了记录我目前的尝试是:

def distinctOn[A](f: T => A):Seq[T]={ 
    var seen = Set[A]() 

    seq.foldLeft(Seq[T]()) { (res, curr) => { 
     if(!seen.contains(f(curr))){ 
     seen = seen ++ Set[A](f(curr)) 
     res ++ Seq(curr) 
     }else{ 
     res 
     } 
    }} 
    } 
+0

为什么不尝试使用'groupBy'方式类似: 'people.groupBy(_名).MAP(_._ 2(0))' – RyuuGan 2012-04-13 08:55:35

+1

@RyuuGan,我认为这将不保留命令。 – 2012-04-13 09:18:12

+0

@RyuuGan,Paul是正确的,groupBy不保存顺序。 – ChucK 2012-04-16 07:30:16

回答

6

这里是一个implemen在适用的情况下保留订单,并且也适用于其他Traversable s比Seq s。它基于distinct的实施并使用在其他收集方法中使用的建筑工厂(又名:CanBuildFrom)。

class TraversableOnceExt[A, CC[A] <: TraversableOnce[A]](coll: CC[A]) { 
    import collection.generic.CanBuildFrom 
    def distinctBy[B, That](f: A => B)(implicit cbf: CanBuildFrom[CC[A], A, That]): That = { 
    val b = cbf(coll) 
    val seen = collection.mutable.HashSet[B]() 
    for (x <- coll) { 
     val v = f(x) 
     if (!seen(v)) { 
     b += x 
     seen += v 
     } 
    } 
    b.result 
    } 
} 

implicit def commomExtendTraversable[A, C[A] <: TraversableOnce[A]](coll: C[A]): TraversableOnceExt[A, C] = 
    new TraversableOnceExt[A, C](coll) 
2

下面是把seen成倍的提高,一般清理东西(如不建设一个集只是一个元素添加到现有的集合):

class EnrichedSeq[T](seq: Seq[T]) { 
    def distinctOn[A](f: T => A): Seq[T] = { 
    seq.foldLeft((Set[A](), Seq[T]())) { 
     case ((seen, res), curr) => 
     val y = f(curr) 
     if (!seen(y)) 
      (seen + y, res :+ curr) 
     else 
      (seen, res) 
    }._2 
    } 
} 
implicit def enrichSeq[T](self: Seq[T]) = new EnrichedSeq(self) 

此外,你可能会因为这更符合由库(例如,maxBysortBy等)使用的命名约定称之为distinctBy