2012-04-13 82 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