Filter Map by key set

前端 未结 3 682
感情败类
感情败类 2020-12-25 13:19

Is there a shortcut to filter a Map keeping only the entries where the key is contained in a given Set?

Here is some example code

scala> val map =         


        
相关标签:
3条回答
  • 2020-12-25 13:27

    Sorry, not a direct answer to your question, but if you know which keys you want to remove (instead of which ones you want to keep), you could do this:

    map -- Set("3")
    
    0 讨论(0)
  • 2020-12-25 13:34

    A tangential tip, in case you are going to follow the PredicateW idea in @oxbow_lakes' answer:

    In functional programming, instead of defining ad hoc functions, we aim for more generalized and composable abstractions. For this particular case, Applicative fits the bill.

    Set themselves are functions, and the Applicative instance for [B]Function1[A, B] lets us lift functions to context. In other words, you can lift functions of type (Boolean, Boolean) => Boolean (such as ||, && etc.) to (A => Boolean, A => Boolean) => (A => Boolean). (Here you can find a great explanation on this concept of lifting.)

    However the data structure Set itself has an Applicative instance available, which will be favored over [B]Applicative[A => B] instance. To prevent that, we will have to explicitly tell the compiler to treat the given set as a function. We define a following enrichment for that:

    scala> implicit def setAsFunction[A](set: Set[A]) = new {
         |   def f: A => Boolean = set
         | }
    setAsFunction: [A](set: Set[A])java.lang.Object{def f: A => Boolean}
    
    scala> Set(3, 4, 2).f
    res144: Int => Boolean = Set(3, 4, 2)
    

    And now put this Applicative goodness into use.

    scala> val map = Map("1" -> 1, "2" -> 2, "3" -> 3)
    map: scala.collection.immutable.Map[java.lang.String,Int] = Map(1 -> 1, 2 -> 2, 3 -> 3)
    
    scala> map filterKeys ((Set("1", "2").f |@| Set("2", "3").f)(_ && _))
    res150: scala.collection.immutable.Map[java.lang.String,Int] = Map(2 -> 2)
    
    scala> map filterKeys ((Set("1", "2").f |@| Set("2", "3").f)(_ || _))
    res151: scala.collection.immutable.Map[java.lang.String,Int] = Map(1 -> 1, 2 -> 2, 3 -> 3)
    
    scala> map filterKeys (Set("2", "3").f map (!_))
    res152: scala.collection.immutable.Map[java.lang.String,Int] = Map(1 -> 1)
    

    Note: All of the above requires Scalaz.

    0 讨论(0)
  • 2020-12-25 13:37

    Answering the Question

    You can take advantage of the fact that a Set[A] is a predicate; i.e. A => Boolean

    map filterKeys set
    

    Here it is at work:

    scala> val map = Map("1" -> 1, "2" -> 2, "3" -> 3)
    map: scala.collection.immutable.Map[java.lang.String,Int] = Map(1 -> 1, 2 -> 2, 3 -> 3)
    
    scala> val set = Set("1", "2")
    set: scala.collection.immutable.Set[java.lang.String] = Set(1, 2)
    
    scala> map filterKeys set
    res0: scala.collection.immutable.Map[java.lang.String,Int] = Map(1 -> 1, 2 -> 2)
    

    Or if you prefer:

    scala> map filterKeys Set("1", "2")
    res1: scala.collection.immutable.Map[java.lang.String,Int] = Map(1 -> 1, 2 -> 2)
    

    Predicates

    It's actually really useful to have some wrapper around a predicate. Like so:

    scala> class PredicateW[A](self: A => Boolean) {
       | def and(other: A => Boolean): A => Boolean = a => self(a) && other(a)
       | def or(other: A => Boolean): A => Boolean = a => self(a) || other(a)
       | def unary_! : A => Boolean = a => !self(a)
       | }
    defined class PredicateW
    

    And an implicit conversion:

    scala> implicit def Predicate_Is_PredicateW[A](p: A => Boolean) = new PredicateW(p)
    Predicate_Is_PredicateW: [A](p: A => Boolean)PredicateW[A]
    

    And then you can use it:

    scala> map filterKeys (Set("1", "2") and Set("2", "3"))
    res2: scala.collection.immutable.Map[java.lang.String,Int] = Map(2 -> 2)
    
    scala> map filterKeys (Set("1", "2") or Set("2", "3"))
    res3: scala.collection.immutable.Map[java.lang.String,Int] = Map(1 -> 1, 2 -> 2, 3 -> 3)
    
    scala> map filterKeys !Set("2", "3")
    res4: scala.collection.immutable.Map[java.lang.String,Int] = Map(1 -> 1)
    

    This can be extended to xor, nand etc etc and if you include symbolic unicode can make for amazingly readable code:

    val mustReport = trades filter (uncoveredShort ∨ exceedsDollarMax)
    
    val european = { 
      val Europe = (_ : Market).exchange.country.region == Region.EU
      trades filter (_.market ∈: Europe)
    }
    
    0 讨论(0)
提交回复
热议问题