Shirley Du
Shirley Du

Reputation: 45

Scala filter by set

Say I have a map that looks like this

val map = Map("Shoes" -> 1, "heels" -> 2, "sneakers" -> 3, "dress" -> 4, "jeans" -> 5, "boyfriend jeans" -> 6)

And also I have a set or collection that looks like this:

val set = Array(Array("Shoes", "heels", "sneakers"), Array("dress", "maxi dress"), Array("jeans", "boyfriend jeans", "destroyed jeans"))

I would like to perform a filter operation on my map so that only one element in each of my set retains. Expected output should be something like this:

map = Map("Shoes" -> 1, "dress" -> 4 ,"jeans" -> 5)

The purpose of doing this is so that if I have multiple sets that indicate different categories of outfits, my output map doesn't "repeat" itself on technically the same objects.

Any help is appreciated, thanks!

Upvotes: 2

Views: 2410

Answers (4)

Ivan
Ivan

Reputation: 462

A slightly simpler version

set.flatMap(_.find(map.contains).map(y => y -> map(y)))

Upvotes: 1

dth
dth

Reputation: 2337

So first get rid of the confusion that your sets are actually arrays. For the rest of the example I will use this definition instead:

val arrays = Array(Array("Shoes", "heels", "sneakers"), Array("dress", "maxi dress"), Array("jeans", "boyfriend jeans", "destroyed jeans"))

So in a sense you have an array of arrays of equivalent objects and want to remove all but one of them?

Well first you have to find which of the elements in an array are actually used as keys in the mep. So we just filter out all elements that are not used as keys:

array.filter(map.keySet)

Now, we have to chose one element. As you said, we just take the first one:

array.filter(map.keySet).head

As your "sets" are actually arrays, this is really the first element in your array that is also used as a key. If you would actually use sets this code would still work as sets actually have a "first element". It is just highly implementations specific and it might not even be deterministic over various executions of the same program. At least for immutable sets it should however be deterministic over several calls to head, i.e., you should always get the same element.

Instead of the first element we are actually interested in all other elements, as we want to remove them from the map:

array.filter(map.keySet).tail

Now, we just have to remove those from the map:

map -- array.filter(map.keySet).tail

And to do it for all arrays:

map -- arrays.flatMap(_.filter(map.keySet).tail)

This works fine as long as the arrays are disjoined. If they are not, we can not take the initial map to filter the array in every step. Instead, we have to use one array to compute a new map, then take the next starting with the result from the last and so on. Luckily, we do not have to do much:

arrays.foldLeft(map){(m,a) => m -- a.filter(m.keySet).tail}

Note: Sets are also functions from elements to Boolean, this is, why this solution works.

Upvotes: 3

Rex Kerr
Rex Kerr

Reputation: 167891

The basic idea is to use groupBy. Something like

map.groupBy{ case (k,v) => g(k) }.
  map{ case (_, kvs) => kvs.head }

This is the general way to group similar things (using some function g). Now the question is just how to make the g that you need. One way is

val g = set.zipWithIndex.
  flatMap{ case (a, i) => a.map(x => x -> i) }.
  toMap

which labels each set with a number, and then forms a map so you can look it up. Maps have an apply function, so you can use it as above.

Upvotes: 2

Hugo Alonso
Hugo Alonso

Reputation: 6824

This code solves the problem:

var newMap = map

set.foreach { list =>
  var remove = false
  list.foreach { _key =>
    if (remove) {
      newMap -= _key
    }
    if (newMap.contains(_key)) {
      remove = true
    }

  }
}

I'm completely new at Scala. I have taken this as my first Scala example, please any hints from Scala's Gurus is welcome.

Upvotes: 2

Related Questions