I am writing a play2.1 application with mongodb, and my model object is a bit extensive. when updating an entry in the DB, i need to compare the temp object coming from the form with what's in the DB, so i can build the update query (and log the changes).
i am looking for a way to generically take 2 instances and get a diff of them. iterating over each data member is long, hard-coded and error prone (if a.firstName.equalsIgnoreCase(b.firstName)) so i am looking for a way to iterate over all data members and compare them horizontally (a map of name -> value will do, or a list i can trust to enumerate the data members in the same order every time).
any ideas?
case class Customer(
id: Option[BSONObjectID] = Some(BSONObjectID.generate),
firstName: String,
middleName: String,
lastName: String,
address: List[Address],
phoneNumbers: List[PhoneNumber],
email: String,
creationTime: Option[DateTime] = Some(DateTime.now()),
lastUpdateTime: Option[DateTime] = Some(DateTime.now())
)
all three solutions below are great, but i still cannot get the field's name, right? that means i can log the change, but not what field it affected...
Maybe productIterator is what you wanted:
scala> case class C(x: Int, y: String, z: Char)
defined class C
scala> val c1 = C(1, "2", 'c')
c1: C = C(1,2,c)
scala> c1.productIterator.toList
res1: List[Any] = List(1, 2, c)
Expanding on #Malte_Schwerhoff's answer, you could potentially create a recursive diff method that not only generated the indexes of differences, but mapped them to the new value at that index - or in the case of nested Product types, a map of the sub-Product differences:
def diff(orig: Product, update: Product): Map[Int, Any] = {
assert(orig != null && update != null, "Both products must be non-null")
assert(orig.getClass == update.getClass, "Both products must be of the same class")
val diffs = for (ix <- 0 until orig.productArity) yield {
(orig.productElement(ix), update.productElement(ix)) match {
case (s1: String, s2: String) if (!s1.equalsIgnoreCase(s2)) => Some((ix -> s2))
case (s1: String, s2: String) => None
case (p1: Product, p2: Product) if (p1 != p2) => Some((ix -> diff(p1, p2)))
case (x, y) if (x != y) => Some((ix -> y))
case _ => None
}
}
diffs.flatten.toMap
}
Expanding on the use cases from that answer:
case class A(x: Int, y: String)
case class B(a: A, b: AnyRef, c: Any)
val a1 = A(4, "four")
val a2 = A(4, "Four")
val a3 = A(4, "quatre")
val a4 = A(5, "five")
val b1 = B(a1, null, 6)
val b2 = B(a1, null, 7)
val b3 = B(a2, a2, a2)
val b4 = B(a4, null, 8)
println(diff(a1, a2)) // Map()
println(diff(a1, a3)) // Map(0 -> 5)
println(diff(a1, a4)) // Map(0 -> 5, 1 -> five)
println(diff(b1, b2)) // Map(2 -> 7)
println(diff(b1, b3)) // Map(1 -> A(4,four), 2 -> A(4,four))
println(diff(b1, b4)) // Map(0 -> Map(0 -> 5, 1 -> five), 2 -> 8l
You can use the product iterator, and match on the elements if you want to use non-standard equality such as String.equalsIgnoreCase.
def compare(p1: Product, p2: Product): List[Int] = {
assert(p1 != null && p2 != null, "Both products must be non-null")
assert(p1.getClass == p2.getClass, "Both products must be of the same class")
var idx = List[Int]()
for (i <- 0 until p1.productArity) {
val equal = (p1.productElement(i), p2.productElement(i)) match {
case (s1: String, s2: String) => s1.equalsIgnoreCase(s2)
case (x, y) => x == y
}
if (!equal) idx ::= i
}
idx.reverse
}
Use cases:
case class A(x: Int, y: String)
case class B(a: A, b: AnyRef, c: Any)
val a1 = A(4, "four")
val a2 = A(4, "Four")
val a3 = A(5, "five")
val b1 = B(a1, null, 6)
val b2 = B(a1, null, 7)
val b3 = B(a2, a2, a2)
println(compare(a1, a2)) // List()
println(compare(a1, a3)) // List(0, 1)
println(compare(b1, b2)) // List(2)
println(compare(b2, b3)) // List(0, 1, 2)
// println(compare(a1, b1)) // assertion failed
If you want to have access to the field name you can use Java's Reflection API. In this case you can access the declared fields by using the getDeclaredFields method and iterate over the fields. If you then want to access the field's value changes to the field you need to set it accessible (setAccessible method) because by default all class parameters are implemented as private fields with public accessors.
val c = C(1, "C", 'c'))
for(field <- c.getClass.getDeclaredFields) {
println(field.getName)
field.get(c)
}
Related
I have two lists with two different types, but both types have the same field to identify/compare.
My Requirement: I want to compare two lists based on some fields of objects, once matched delete element from both list/set.
For example:
case class Type1(name:String, surname: String, address: Int)
case class Type2(name:String, surname: String, address: Int, dummy: String)
So record will be matched if both lists have the same field data for both types.
My List:
val type1List = List(Type1("name1","surname1", 1),
Type1("name2","surname2", 2),
Type1("name3","surname3", 3)
)
val type2List = List(Type2("name1","surname1", 1),
Type2("name2","surname2", 2),
Type2("name4","surname4", 4)
)
Comparing type1List and type2List, removed the matched date from both lists.
type1List should contain only:
val type1List = List(
Type1("name3","surname3", 3)
)
type2List should contain only:
val type2List = List(
Type2("name4","surname4", 4)
)
I tried it with looping/interation , but that seems too complex and performance hit.
Thanks in advance.
Here is a generic approach to the task.
The idea is to find the common elements in both lists according to some custom functions, and then remove both of them.
def removeCommon[A, B, K](as: List[A], bs: List[B])
(asKey: A => K)
(bsKey: B => K): (List[A], List[B]) = {
def removeDuplicates[V](commonKeys: Set[K], map: Map[K, List[V]]): List[V] =
map
.iterator
.collect {
case (key, value) if (!commonKeys.contains(key)) =>
value.head
}.toList
val asByKey = as.groupBy(asKey)
val bsByKey = bs.groupBy(bsKey)
val commonKeys = asByKey.keySet & bsByKey.keySet
val uniqueAs = removeDuplicates(commonKeys, asByKey)
val uniqueBs = removeDuplicates(commonKeys, bsByKey)
(uniqueAs, uniqueBs)
}
Which you can use like following:
final case class Type1(name:String, surname: String, address: Int)
final case class Type2(name:String, surname: String, address: Int, dummy: String)
val type1List = List(
Type1("name1","surname1", 1),
Type1("name2","surname2", 2),
Type1("name3","surname3", 3)
)
val type2List = List(
Type2("name1","surname1", 1, "blah"),
Type2("name2","surname2", 2, "blah"),
Type2("name4","surname4", 4, "blah")
)
val (uniqueType1List, uniqueType2List) =
removeCommon(type1List, type2List) { type1 =>
(type1.name, type1.surname, type1.address)
} { type2 =>
(type2.name, type2.surname, type2.address)
}
// uniqueType1List: List[Type1] = List(Type1("name3", "surname3", 3))
// uniqueType2List: List[Type2] = List(Type2("name4", "surname4", 4, "blah"))
If you can not adjust the 2 Types, here a pragmatic solution:
First find the 'equals'
val equals: Seq[Type1] = type1List.filter {
case Type1(n, sn, a) =>
type2List.exists { case Type2(n2, sn2, a2, _) =>
n == n2 && sn == sn2 && a == a2
}
}
Then filter both Lists:
val filteredType1List = type1List.filterNot(t1 => equals.contains(t1))
val filteredType2List = type2List.filterNot {
case Type2(n2, sn2, a2, _) =>
equals.exists { case Type1(n, sn, a)=>
n == n2 && sn == sn2 && a == a2
}
}
Assuming you know what field you want to use to base your diff on, here is an outline of a solution.
First define a super class for both types:
abstract class Type(val name : String) {
def canEqual(a: Any) = a.isInstanceOf[Type]
override def equals(obj: Any): Boolean = obj match {
case obj : Type => obj.canEqual(this) && this.name == obj.name
case _ => false
}
override def hashCode(): Int = this.name.hashCode
}
Then define your types as sub types of the above class:
case class Type1(override val name: String, surname: String, address: Int) extends Type(name)
case class Type2(override val name: String, surname: String, address: Int, dummy: String) extends Type(name)
Now a simple
type1List.diff(type2List)
will result in:
List(Type1(name3,surname3,3))
and
type2List.diff(type1List)
will give:
List(Type2(name4,surname4,4,dum3))
Still, I would be careful with solutions like this. Because circumventing equals and hashcode opens up the code to all kinds of bugs. So it is better to make sure this is kept limited to the scope you are working in.
I have a ListBuffer of List[String], val tList = ListBuffer[TCount] where TCount is case class TCount(l: List[String], c: Long). I want to find those list l from tList which are not the subset of any other element of tlist and their c value is less than their superset c value. The following program works but I have to use two for loop that makes the code inefficient. Is there any better approach I can use to make the code efficient?
val _arr = tList.toArray
for (i <- 0 to (_arr.length - 1)) {
val il = _arr(i).l.toSet
val ic = _arr(i).c
for (j <- 0 to (_arr.length - 1)) {
val jl = _arr(j).toSet
val jc = _arr(j).c
if (i != j && il.subsetOf(jl) && ic >= jc) {
tList.-=(_arr(i))
}
}
}
Inspired by the set-trie comment:
import scala.collection.SortedMap
class SetTrie[A](val flag: Boolean, val children: SortedMap[A, SetTrie[A]])(implicit val ord: Ordering[A]) {
def insert(xs: List[A]): SetTrie[A] = xs match {
case Nil => new SetTrie(true, children)
case a :: rest => {
val current = children.getOrElse(a, new SetTrie[A](false, SortedMap.empty))
val inserted = current.insert(rest)
new SetTrie(flag, children + (a -> inserted))
}
}
def containsSuperset(xs: List[A], strict: Boolean): Boolean = xs match {
case Nil => !children.isEmpty || (!strict && flag)
case a :: rest => {
children.get(a).map(_.containsSuperset(rest, strict)).getOrElse(false) ||
children.takeWhile(x => ord.lt(x._1, a)).exists(_._2.containsSuperset(xs, false))
}
}
}
def removeSubsets[A : Ordering](xss: List[List[A]]): List[List[A]] = {
val sorted = xss.map(_.sorted)
val setTrie = sorted.foldLeft(new SetTrie[A](false, SortedMap.empty)) { case (st, xs) => st.insert(xs) }
sorted.filterNot(xs => setTrie.containsSuperset(xs, true))
}
Here is a method that relies on a data structure somewhat similar to Set-Trie, but which stores more subsets explicitly. It provides worse compression, but is faster during lookup:
def findMaximal(lists: List[List[String]]): List[List[String]] = {
import collection.mutable.HashMap
class Node(
var isSubset: Boolean = false,
val children: HashMap[String, Node] = HashMap.empty
) {
def insert(xs: List[String], isSubs: Boolean): Unit = if (xs.isEmpty) {
isSubset |= isSubs
} else {
var isSubsSubs = false || isSubs
for (h :: t <- xs.tails) {
children.getOrElseUpdate(h, new Node()).insert(t, isSubsSubs)
isSubsSubs = true
}
}
def isMaximal(xs: List[String]): Boolean = xs match {
case Nil => children.isEmpty && !isSubset
case h :: t => children(h).isMaximal(t)
}
override def toString: String = {
if (children.isEmpty) "#"
else children.flatMap{
case (k,v) => {
if (v.children.isEmpty) List(k)
else (k + ":") :: v.toString.split("\n").map(" " + _).toList
}
}.mkString("\n")
}
}
val listsWithSorted = for (x <- lists) yield (x, x.sorted)
val root = new Node()
for ((x, s) <- listsWithSorted) root.insert(s, false)
// println(root)
for ((x, s) <- listsWithSorted; if root.isMaximal(s)) yield x
}
Note that I'm allowed to do any kind of mutable nonsense inside the body of the method, because the mutable trie data structure never escapes the scope of the method, and can therefore not be inadvertently shared with another thread.
Here is an example with sets of characters (converted to lists of strings):
println(findMaximal(List(
"ab", "abc", "ac", "abd",
"ade", "efd", "adf", "bafd",
"abd", "fda", "dba", "dbe"
).map(_.toList.map(_.toString))))
The output is:
List(
List(a, b, c),
List(a, d, e),
List(e, f, d),
List(b, a, f, d),
List(d, b, e)
)
so indeed, the non-maximal elements ab, ac, abd, adf, fda and dba are eliminated.
And here is what my not-quite-set-trie data structure looks like (child nodes are indented):
e:
f
b:
e
d:
e
f
c
f
d:
e:
f
f
a:
e
b:
d:
f
c
f
d:
e
f
c
f
c
f
Not sure if you can avoid the complexity, but, I guess I'd write like this:
val tList = List(List(1, 2, 3), List(3, 2, 1), List(9, 4, 7), List(3, 5, 6), List(1, 5, 6), List(6, 1, 5))
val tSet = tList.map(_.toSet)
def result = tSet.filterNot { sub => tSet.count(_.subsetOf(sub)) > 1 }
Here's one approach:
Create an indexed Map for identifying the original List elements
Turn Map of List-elements into Map of Sets (with index)
Generate combinations of the Map elements and use a custom filter to capture the elements that are subset of others
Remove those subset elements from the Map of Sets and retrieve remaining elements from the Map of Lists via the index
Sample code:
type TupIntSet = Tuple2[Int, Set[Int]]
def subsetFilter(ls: List[TupIntSet]): List[TupIntSet] =
if ( ls.size != 2 ) List.empty[TupIntSet] else
if ( ls(0)._2 subsetOf ls(1)._2 ) List[TupIntSet]((ls(0)._1, ls(0)._2)) else
if ( ls(1)._2 subsetOf ls(0)._2 ) List[TupIntSet]((ls(1)._1, ls(1)._2)) else
List.empty[TupIntSet]
val tList = List(List(1,2), List(1,2,3), List(3,4,5), List(5,4,3), List(2,3,4), List(6,7))
val listMap = (Stream from 1).zip(tList).toMap
val setMap = listMap.map{ case (i, l) => (i, l.toSet) }
val tSubsets = setMap.toList.combinations(2).toSet.flatMap(subsetFilter)
val resultList = (setMap.toSet -- tSubsets).map(_._1).map(listMap.getOrElse(_, ""))
// resultList: scala.collection.immutable.Set[java.io.Serializable] =
// Set(List(5, 4, 3), List(2, 3, 4), List(6, 7), List(1, 2, 3))
The following Iterable can be o size one, two, or (up to) three.
org.apache.spark.rdd.RDD[Iterable[(String, String, String, String, Long)]] = MappedRDD[17] at map at <console>:75
The second element of each tuple can have any of the following values: A, B, C. Each of these values can appear (at most) once.
What I would like to do is sort them based on the following order (B , A , C), and then create a string by concatenating the elements of the 3rd place. If the corresponding tag is missing then concatenate with a blank space: ``. For example:
this:
CompactBuffer((blah,A,val1,blah,blah), (blah,B,val2,blah,blah), (blah,C,val3,blah,blah))
should result in:
val2,val1,val3
this:
CompactBuffer((blah,A,val1,blah,blah), (blah,C,val3,blah,blah))
should result in:
,val1,val3
this:
CompactBuffer((blah,A,val1,blah,blah), (blah,B,val2,blah,blah))
should result in:
val2,val1,
this:
CompactBuffer((blah,B,val2,blah,blah))
should result in:
val2,,
and so on so forth.
In your case when A, B and C appear at most once, you could add the corresponding values to a temporary map and retrieve the values from the map in the correct order.
If we use getOrElse to get the values from the map, we can specify the empty string as default value. This way we still get the correct result if our Iterable doesn't contain all the tuples with A, B and C.
type YourTuple = (String, String, String, String, Long)
def orderTuples(order: List[String])(iter: Iterable[YourTuple]) = {
val orderMap = iter.map { case (_, key, value, _, _) => key -> value }.toMap
order.map(s => orderMap.getOrElse(s, "")).mkString(",")
}
We can use this function as follows :
val a = ("blah","A","val1","blah",1L)
val b = ("blah","B","val2","blah",2L)
val c = ("blah","C","val3","blah",3L)
val order = List("B", "A", "C")
val bacOrder = orderTuples(order) _
bacOrder(Iterable(a, b, c)) // String = val2,val1,val3
bacOrder(Iterable(a, c)) // String = ,val1,val3
bacOrder(Iterable(a, b)) // String = val2,val1,
bacOrder(Iterable(b)) // String = val2,,
def orderTuples(xs: Iterable[(String, String, String, String, String)],
order: (String, String, String) = ("B", "A", "C")) = {
type T = Iterable[(String, String, String, String, String)]
type KV = Iterable[(String, String)]
val ord = List(order._1, order._2, order._3)
def loop(xs: T, acc: KV, vs: Iterable[String] = ord): KV = xs match {
case Nil if vs.isEmpty => acc
case Nil => vs.map((_, ",")) ++: acc
case x :: xs => loop(xs, List((x._2, x._3)) ++: acc, vs.filterNot(_ == x._2))
}
def comp(x: String) = ord.zipWithIndex.toMap.apply(x)
loop(xs, Nil).toList.sortBy(x => comp(x._1)).map(_._2).mkString(",")
}
Is there a way to define a Map, where Map value depends on its key, like
Map(key -> f(key), key2 -> f(key2), ...).
You're looking at this the wrong way...
A Map[K,V] is also an instance of Partialfunction[K,V]. So change everywhere you're using your Map type (vals, method params, etc.) to be a PartialFunction.
Then you can just work with f directly, or supply a Map[K,V] as the instance wherever you don't have a simple algebraic relationship between keys and values.
e.g.
def methodUsingMapping(x: PartialFunction[Int,Boolean]) = ...
//then
val myMap = Map(1->true, 2->true, 3->false)
methodUsingMapping(myMap)
//or
val isEven = PartialFunction(n: Int => n % 2 == 0)
methodUsingMapping(isEven)
//or
//note: case statements in a block is the smart way
// to define a partial function
// In this version, the result isn't even defined for odd numbers
val isEven: PartialFunction[Int,Boolean] = {
case n: Int if n % 2 == 0 => true
}
methodUsingMapping(isEven)
You also might also want to consider using (K) => Option[V], in which case you can supply an instance of the type via the lift method, which maps inherit from PartialFunction
e.g.
def methodUsingMapping(x: (Int)=>Option[Boolean]) = ...
//then
val myMap = Map(1->true, 2->true, 3->false)
methodUsingMapping(myMap.lift)
//or
def isEven(n: Int) = Some(n % 2 == 0)
methodUsingMapping(isEven)
//or
def isEven(n: Int) = n % 2 == 0
methodUsingMapping(x => Some(isEven(x)))
Lets say you have your keys in a list like this, and you want to convert it map with squares as values.
scala> val keyList = ( 1 to 10 ).toList
keyList: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> val doSquare = ( x: Int ) => x * x
doSquare: Int => Int = <function1>
// Convert it to the list of tuples - ( key, doSquare( key ) )
scala> val tupleList = keyList.map( key => ( key, doSquare( key ) ) )
tuple: List[(Int, Int)] = List((1,1), (2,4), (3,9), (4,16), (5,25), (6,36), (7,49), (8,64), (9,81), (10,100))
val keyMap = tuple.toMap
keyMap: scala.collection.immutable.Map[Int,Int] = Map(5 -> 25, 10 -> 100, 1 -> 1, 6 -> 36, 9 -> 81, 2 -> 4, 7 -> 49, 3 -> 9, 8 -> 64, 4 -> 16)
Or to do it in one line
( 1 to 10 ).toList.map( x => ( x, x * x ) ).toMap
Or... if you have just few keys... then you can write the specific code
Map( 1 -> doSquare( 1 ), 2 -> doSquare( 2 ) )
Because you only need to define 4 methods to make a Map trait implementation, you could just roll your own:
trait MapWithRelationship[K, +V] extends Map[K, V] {
self =>
def pred: (K, Any) => Boolean
def underlying: Map[K, V]
def get(key: K): Option[V] = underlying.get(key)
def iterator: Iterator[(K, V)] = underlying.iterator
def + [V1 >: V](kv: (K, V1)): MapWithRelationship[K, V1] = {
val (k, v) = kv
if (pred(k, v)) {
new MapWithRelationship[K, V1] {
val pred = self.pred
val underlying = self.underlying + kv
}
} else {
throw new Exception(s"Key-value pair $kv failed MapWithRelationship predicate")
}
}
def -(key: K): MapWithRelationship[K, V] =
new MapWithRelationship[K, V] {
val pred = self.pred
val underlying = self.underlying - key
}
}
object MapWithRelationship {
def apply[K, V](rule: (K, Any) => Boolean)(pairs: (K, V)*) = {
val empty = new MapWithRelationship[K, V] {
def pred = rule
def underlying = Map.empty[K, V]
}
pairs.foldLeft(empty)(_ + _)
}
}
Then you can use as such:
scala> val x = MapWithRelationship[Int, Int]((k, v) => v == k * k)()
x: MapWithRelationship[Int,Int] = Map()
scala> val x2 = x + (1 -> 1)
x2: MapWithRelationship[Int,Int] = Map(1 -> 1)
scala> val x3 = x + (5 -> 25)
x3: MapWithRelationship[Int,Int] = Map(5 -> 25)
scala> val x4 = x + (6 -> "foo")
java.lang.Exception: Key-value pair (6,foo) failed MapWithRelationship predicate
at MapWithRelationship$class.$plus(<console>:21)
at MapWithRelationship$$anon$3.$plus(<console>:33)
... 32 elided
You could make an infinite map of squares using:
val mySquareMap = Map.empty[Int, Int].withDefault(d => d * d)
This map will still have +, get, iterator and other methods that won't work as desired, but if you need a read-only map that returns squares, this would work.
Of course, it would be more efficient and probably clearer to just use:
val mySquare = (d:Int) => d * d
as a function. However, the above Map might be useful if you need to use some API that requires that type.
To have a more full-fledged solution for this, you might be better off building your own class that extends Map[Int, Int] that overrides get to return the square of its argument.
I have a List[A], how is a idiomatic way of removing duplicates given an equality function (a:A, b:A) => Boolean? I cannot generally override equalsfor A
The way I can think now is creating a wrapping class AExt with overridden equals, then
list.map(new AExt(_)).distinct
But I wonder if there's a cleaner way.
There is a simple (simpler) way to do this:
list.groupBy(_.key).mapValues(_.head)
If you want you can use the resulting map instantly by replacing _.head by a function block like:
sameElements => { val observedItem = sameElements.head
new A (var1 = observedItem.firstAttr,
var2 = "SomethingElse") }
to return a new A for each distinct element.
There is only one minor problem. The above code (list.groupBy(_.key).mapValues(_.head)) didnt explains very well the intention to remove duplicates. For that reason it would be great to have a function like distinctIn[A](attr: A => B) or distinctBy[A](eq: (A, A) -> Boolean).
Using the Foo and customEquals from misingFaktor's answer:
case class Foo(a: Int, b: Int)
val (a, b, c, d) = (Foo(3, 4), Foo(3, 1), Foo(2, 5), Foo(2, 5))
def customEquals(x: Foo, y: Foo) = x.a == y.a
(Seq(a, b, c, d).foldLeft(Seq[Foo]()) {
(unique, curr) => {
if (!unique.exists(customEquals(curr, _)))
curr +: unique
else
unique
}
}).reverse
If result ordering is important but the duplicate to be removed is not, then foldRight is preferable
Seq(a, b, c, d).foldRight(Seq[Foo]()) {
(curr, unique) => {
if (!unique.exists(customEquals(curr, _)))
curr +: unique
else
unique
}
}
I must say I think I'd go via an intermediate collection which was a Set if you expected that your Lists might be quite long as testing for presence (via exists or find) on a Seq is O(n) of course:
Rather than write a custom equals; decide what property the elements are equal by. So instead of:
def myCustomEqual(a1: A, a2: A) = a1.foo == a2.foo && a1.bar == a2.bar
Make a Key. Like so:
type Key = (Foo, Bar)
def key(a: A) = (a.foo, a.bar)
Then you can add the keys to a Set to see whether you have come across them before.
var keys = Set.empty[Key]
((List.empty[A] /: as) { (l, a) =>
val k = key(a)
if (keys(k)) l else { keys += k; a +: l }
}).reverse
Of course, this solution has worse space complexity and potentially worse performance (as you are creating extra objects - the keys) in the case of very short lists. If you do not like the var in the fold, you might like to look at how you could achieve this using State and Traverse from scalaz 7
scala> case class Foo(a: Int, b: Int)
defined class Foo
scala> val (a, b, c, d) = (Foo(3, 4), Foo(3, 1), Foo(2, 5), Foo(2, 5))
a: Foo = Foo(3,4)
b: Foo = Foo(3,1)
c: Foo = Foo(2,5)
d: Foo = Foo(2,5)
scala> def customEquals(x: Foo, y: Foo) = x.a == y.a
customEquals: (x: Foo, y: Foo)Boolean
scala> Seq(a, b, c, d) filter {
| var seq = Seq.empty[Foo]
| x => {
| if(seq.exists(customEquals(x, _))) {
| false
| } else {
| seq :+= x
| true
| }
| }
res13: Seq[Foo] = List(Foo(3,4), Foo(2,5))
Starting Scala 2.13, we can use the new distinctBy method which returns elements of a sequence ignoring the duplicates as determined by == after applying a transforming function f:
def distinctBy[B](f: (A) => B): List[A]
For instance:
// case class A(a: Int, b: String, c: Double)
// val list = List(A(1, "hello", 3.14), A(2, "world", 3.14), A(1, "hello", 12.3))
list.distinctBy(x => (x.a, x.b)) // List(A(1, "hello", 3.14), A(2, "world", 3.14))
list.distinctBy(_.c) // List(A(1, "hello", 3.14), A(1, "hello", 12.3))
case class Foo (a: Int, b: Int)
val x = List(Foo(3,4), Foo(3,1), Foo(2,5), Foo(2,5))
def customEquals(x : Foo, y: Foo) = (x.a == y.a && x.b == y.b)
x.foldLeft(Nil : List[Foo]) {(list, item) =>
val exists = list.find(x => customEquals(item, x))
if (exists.isEmpty) item :: list
else list
}.reverse
res0: List[Foo] = List(Foo(3,4), Foo(3,1), Foo(2,5))