1

In Scala, I'm trying to filter a map based on a unique property with the Map values.

case class Product(
  item: Item,
)
productModels: Map[Int, Product]

How can I create a new Map (or filter productModels) to only contain values where Product.Item.someproperty is unique within the Map?

I've been trying foldLeft on productModels, but can't seem to get it. I'll keep trying but want to check with you all as well.

Thanks

Todd M
  • 1,012
  • 1
  • 15
  • 25
  • To make sure that I understand. As an example, replacing `Product` with `String` for simplicity, you'd expect `f ( Map(1 -> "foo", 2 -> "bar", 3 -> "foo", 4 -> "bippy")` to output `Map(2 -> "bar", 4 -> "bippy") )` since `"foo"` shows up as a value for keys `1` and `3`? – Kevin Meredith Apr 13 '16 at 18:24
  • Thanks for question. I'd expect `Map(1 -> "foo", 2 -> "bar", 4 -> "bippy")` because these are the unique values – Todd M Apr 13 '16 at 19:50
  • Please, can you rephrase it once again, do you need to filter your map and leave only entires that **had** unique product's field in original map, or you want to calculate map where there is strictly one entry for each distinct product's field value from original map (discard duplicates)? – Aivean Apr 14 '16 at 01:30

4 Answers4

3

You can do it the following way:

productModels
  .groupBy(_._1)            // produces Map[Product, Map[Int, Product]]
  .filter {case (k,v) => v.size == 1} // filters unique values
  .flatMap {case (_,v) => v}  
Nyavro
  • 8,806
  • 2
  • 26
  • 33
1

The easiest way to do that is to transform your map into another map, where keys are desired fields of Item:

case class Product(item:String)

val productModels =
  Map(
    1 -> Product("a"),
    2 -> Product("b"),
    3 -> Product("c"),
    4 -> Product("a")
  )

// here I'm calculating distinct by Product.item for simplicity
productModels.map { case e@(_, v) => v.item -> e }.values.toMap

Result:

Map(4 -> Product(a), 2 -> Product(b), 3 -> Product(c))

Note, that the order of the elements is not guaranteed, as generic Map doesn't have particular order of keys. If you use Map that has item order, such as ListMap and want to preserve order of elements, here is the necessary adjustment:

productModels.toList.reverse.map { case e@(_, v) => v.item -> e }.toMap.values.toMap

Result:

res1: scala.collection.immutable.Map[Int,Product] = Map(1 -> Product(a), 3 -> Product(c), 2 -> Product(b))
Aivean
  • 10,692
  • 25
  • 39
  • Not clear whether OP wants to preserve only the entries that had unique values in original map or just remove entries with duplicate values, leaving exactly one entry per unique value. – Aivean Apr 14 '16 at 01:32
  • I'd like one entry per unique value. If it helps, I'm trying to replicate a Ruby function uniq. So, in this case, something like `nv = productModels.uniq | |pm| pm.item[:distinct_property] }` – Todd M Apr 14 '16 at 12:13
  • This `productModels.map { case e@(_, v) => v.item -> e }.values.toMap` seems to be working so far. Wow. what does the `e@` do? – Todd M Apr 14 '16 at 12:45
  • @ToddM it's the way to create alias for your result (or partial result) in pattern matching. For example, `case (_, v) =>` just matched the tuple, `case e@(_, v) =>` additionally makes the tuple available as `e`. – Aivean Apr 14 '16 at 16:23
0
case class Item(property:String)
case class Product(item:Item)
val xs = Map[Int, Product]() // your example has this data structure

// just filter the map based on the item property value
xs filter { case (k,v) => v.item.property == "some property value" }
andyczerwonka
  • 4,230
  • 5
  • 34
  • 57
0

Here is implementation with foldLeft:

productModels.foldLeft(Map.empty[Int, Product]){ 
  (acc, el) => 
    if (acc.exists(_._2.item.someproperty == el._2.item.someproperty)) acc 
    else acc + el
}