4

Given a predicate (String) -> Boolean I wondered whether there is an easy way to negate the outcome of that predicate. As long as I use a list, I can simply switch from filter to filterNot, but what if I have, lets say... a Map and use filterKeys?

What I used so far is:

val myPredicate : (String) -> Boolean = TODO()
val map : Map<String, String> = TODO()

map.filterKeys { !myPredicate(it) }

But I wonder why there is an overloaded filter-function for Collection, but not for Map. Moreover I also wonder, why there isn't something similar to what we have in Java, i.e. Predicate.negate() and since Java 11 Predicate.not(..).

Or does it exist and I just haven't found it?

Roland
  • 22,259
  • 4
  • 57
  • 84
  • There is `filterNot` for `Map` as well: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/filter-not.html – msrd0 Aug 28 '18 at 12:04
  • ok... I specifically meant `filterKeys` and `filterValues` but also any other function accepting a ~predicate.. Do there exist any easy convenience methods as the ones shown in the question? If not would be interesting why... – Roland Aug 28 '18 at 12:06
  • 1
    Actually this is a really interesting question. It might be a performance issue (or I was just not able to figure it out): https://stackoverflow.com/q/52057967/3755692 – msrd0 Aug 28 '18 at 12:30

1 Answers1

5

My approach at that time was to have two functions, one using the not-operator and the other being a simple not-function accepting a predicate. Today I can't really recommend that approach anymore, but would rather choose the following instead, if I have to deal with many predicate negations for keys or values again:

inline fun <K, V> Map<out K, V>.filterKeysNot(predicate: (K) -> Boolean) = filterKeys { !predicate(it) }
inline fun <K, V> Map<out K, V>.filterValuesNot(predicate: (V) -> Boolean) = filterValues { !predicate(it) }

That way a given predicate can simply be used by just calling filterKeysNot(givenPredicate) similar to what was already possible with filterNot on collections.

For the problem I had at that time I was able to do a refactoring so that the data could be partitioned appropriately and therefore the predicate negation wasn't needed anymore.

If I only need it in rare occasions I would rather stick to filterKeys { !predicate(it) } or filterNot { (key, _) -> predicate(key) }.

The following variants show how something like Predicates.not or Predicate.negate could be implemented:

The following will allow to use the !-operator to negate a predicate (if several parameters should be allowed an appropriate overload is required):

operator fun <T> ((T) -> Boolean).not() = { e : T -> !this(e) }

The next allows to use not( { /* a predicate */ } ). This however, at least for me, isn't really more readable:

inline fun <T> not(crossinline predicate: (T) -> Boolean)  = { e : T -> !predicate(e)}

Usages:

val matchingHello : (String) -> Boolean = { it == "hello" }

mapOf("hello" to "world", "hi" to "everyone")
       .filterKeys(!matchingHello)
// or  .filterKeys(not(matchingHello))
// or  .filterKeys(matchingHello.not())
// or as shown above:
//     .filterKeysNot(matchingHello)
       .forEach(::println)   
Roland
  • 22,259
  • 4
  • 57
  • 84
  • 1
    Note that this will not be as performant as writing `filterKeys { it != "hello" }` because you are creating two instances of internal objects containing a lambda each, instead of just inlining everything – msrd0 Aug 30 '18 at 15:23
  • well yes... maybe I should rather deprecate the `operator` and use the other method instead... but .. ... the main "problem" is, that I have a predicate already... so it is rather a matter of whether to call `filterKeys { ! matchingHello(it) }` or `filterKeys(not(matchingHello))` then... i really liked the way `filterKeys(!matchingHello)` looked like however ;-) or maybe I will just add additional extension functions a la `filterKeysNot`... doesn't sound nice, but is congruent to `filterNot` which doesn't sound that nice too ;-) – Roland Aug 30 '18 at 15:35
  • Kotlin Collections has filterNot(predicate). Isn't it something you are looking for? `/** * Returns a list containing all elements not matching the given [predicate]. */ public inline fun Iterable.filterNot(predicate: (T) -> Boolean): List { return filterNotTo(ArrayList(), predicate) }` – Anton Mar 12 '19 at 10:55
  • Why not `inline operator fun`? – Alexey Romanov Mar 12 '19 at 13:02
  • @Anton Not really. If you look at the question then you see, that I already knew of `filterNot`. It's rather that I had several predicates where I needed the non-matching keys and values and just wondered, whether there is something as simple as `filterNot` for keys (i.e. `filterKeysNot(predicate)`) or an easy negation-mechanism for a given predicate similar to Javas `negate`. The answer itself should mainly show that missing piece... I could however improve the code in question to use a partition instead which eliminated the predicate negation in the first place; but that just as a side-note. – Roland Mar 12 '19 at 13:24
  • @AlexeyRomanov at least Intellij states that `inline` in that place doesn't really have any benefit ;-) I think the reason is that we deal with a function type already and that the extension functions are translated to static methods in Java, so there is probably nothing to inline anymore... but that's rather a guess now... – Roland Mar 12 '19 at 13:26
  • @Roland Well, after searching some more I found that it _isn't_ inlined and why :( https://youtrack.jetbrains.com/issue/KT-25798 This seems really unfortunate. I think I'll propose a workaround there... – Alexey Romanov Mar 12 '19 at 20:22
  • @AlexeyRomanov this and the related issue only deal with inspection behaviour... I don't see something related to functional type as receiver not being inlined (or that it could)... – Roland Mar 12 '19 at 20:38
  • @Roland The last sentence: "the noinline is the correct modifier as the ext and no calls look the same in generated bytecode." – Alexey Romanov Mar 12 '19 at 20:39
  • @AlexeyRomanov boah... took me a while ;-) thanks for the hint... needed to read all that part to get what was really meant ;-) To be honest I didn't really expect `inline`-fun + `noinline`-function-parameter to look the same as an `inline`-function-type extension function... at least I thought one would be a static method and the other not... ;-) To be honest I did not always check what bytecode is produced... maybe I need to do that more often ;-) – Roland Mar 12 '19 at 20:56
  • 1
    Finally: https://youtrack.jetbrains.com/issue/KT-5837 (+ my proposal in https://youtrack.jetbrains.com/issue/KT-30398) – Alexey Romanov Mar 12 '19 at 21:21