groovy
  1. groovy
  2. GROOVY-3788

HashMap support for easy filtering on keys or values

    Details

    • Type: New Feature New Feature
    • Status: Closed Closed
    • Priority: Minor Minor
    • Resolution: Won't Fix
    • Affects Version/s: 1.7-beta-1
    • Fix Version/s: None
    • Component/s: groovy-jdk
    • Labels:
      None
    • Testcase included:
      yes
    • Number of attachments :
      0

      Description

      Proposed Enhancements to HashMap:

      • rejectKeys(list of keys)
      • rejectValues(list of values)
      • includeKeys(list of keys)
      • includeValues(list of values)

      Some examples:

      HashMap.metaClass.rejectKeys = {ArrayList keys ->
          delegate.findAll{ k,v -> !keys.contains(k)}
      } 
      
      HashMap.metaClass.rejectValues = {ArrayList values ->
          delegate.findAll{ k,v -> !values.contains(v)}
      } 
      
      HashMap.metaClass.includeKeys = {ArrayList keys ->
          delegate.findAll{ k,v -> keys.contains(k)}
      }
      
      HashMap.metaClass.includeValues = {ArrayList values ->
          delegate.findAll{ k,v -> values.contains(v)}
      } 
      
      
      map = [a:1, b:2, c:3, d:4]
      
      keys = ['a', 'c']
      
      values = [1, 2]
      
      def not_rejected = map.rejectKeys(keys)
      def included = map.includeKeys(keys)
      
      def not_rejected_v = map.rejectValues(values)
      def included_v = map.includeValues(values)
      
      println not_rejected
      println included
      
      println not_rejected_v
      println included_v
      

      with output:

      [b:2, d:4]
      [a:1, c:3]
      [c:3, d:4]
      [a:1, b:2]
      

        Activity

        Hide
        Paul King added a comment -

        add code tags

        Show
        Paul King added a comment - add code tags
        Hide
        Paul King added a comment -

        I am wondering whether it is better to just have rejectAll counterparts to the current findAll methods.

        Then the code above would become something like:

        def not_rejected = map.rejectAll{ k, v -> k in keys }
        def included = map.findAll{ k, v -> k in keys }
        def not_rejected_v = map.rejectAll{ k, v -> v in values }
        def included_v = map.findAll{ k, v -> v in values }
        
        Show
        Paul King added a comment - I am wondering whether it is better to just have rejectAll counterparts to the current findAll methods. Then the code above would become something like: def not_rejected = map.rejectAll{ k, v -> k in keys } def included = map.findAll{ k, v -> k in keys } def not_rejected_v = map.rejectAll{ k, v -> v in values } def included_v = map.findAll{ k, v -> v in values }
        Hide
        Brad Long added a comment -

        This has been discussed/resolved elsewhere (see GROOVY-4294). However, I can still see some (limited) value in this. There are now multiple ways to get this info, but considering a map is all about keys and values, more direct methods could be justified.

        A possible method naming scheme (in Java for clarity) could be:

        Map<K,V> retainKeys(Set<K> keys)
        Map<K,V> retainValues(Set<V> values)
        Map<K,V> removeKeys(Set<K> keys)
        Map<K,V> removeValues(Set<V> values)
        

        Naming variations removeEntriesWithKeys, removeWithKeys, removeByKeys.

        On the other hand, (considering GROOVY-4294), it could be argued that this issue should be closed.

        Show
        Brad Long added a comment - This has been discussed/resolved elsewhere (see GROOVY-4294 ). However, I can still see some (limited) value in this. There are now multiple ways to get this info, but considering a map is all about keys and values, more direct methods could be justified. A possible method naming scheme (in Java for clarity) could be: Map<K,V> retainKeys(Set<K> keys) Map<K,V> retainValues(Set<V> values) Map<K,V> removeKeys(Set<K> keys) Map<K,V> removeValues(Set<V> values) Naming variations removeEntriesWithKeys, removeWithKeys, removeByKeys. On the other hand, (considering GROOVY-4294 ), it could be argued that this issue should be closed.
        Hide
        Paul King added a comment -

        Yes, we already have something similar to removeKeys called subMap but subMap doesn't alter the original. Perhaps mutating variants would be useful. Even the non-mutating variations to subMap might be worth having. Including the originally proposed methods, the full set would be something like:

        // mutating variants
        Map<K,V> retainKeys(Set<K> keys)
        Map<K,V> retainValues(Set<V> values)
        Map<K,V> removeKeys(Set<K> keys)
        Map<K,V> removeValues(Set<V> values)
        
        // non-mutating variants
        Map<K,V> findKeys(Set<K> keys) // alias to subMap
        Map<K,V> findValues(Set<V> values)
        Map<K,V> rejectKeys(Set<K> keys)
        Map<K,V> rejectValues(Set<V> values)
        Map<K,V> rejectAll(Closure filter)
        Collection<T> rejectAll(Closure filter)
        
        Show
        Paul King added a comment - Yes, we already have something similar to removeKeys called subMap but subMap doesn't alter the original. Perhaps mutating variants would be useful. Even the non-mutating variations to subMap might be worth having. Including the originally proposed methods, the full set would be something like: // mutating variants Map<K,V> retainKeys(Set<K> keys) Map<K,V> retainValues(Set<V> values) Map<K,V> removeKeys(Set<K> keys) Map<K,V> removeValues(Set<V> values) // non-mutating variants Map<K,V> findKeys(Set<K> keys) // alias to subMap Map<K,V> findValues(Set<V> values) Map<K,V> rejectKeys(Set<K> keys) Map<K,V> rejectValues(Set<V> values) Map<K,V> rejectAll(Closure filter) Collection<T> rejectAll(Closure filter)
        Hide
        Paul King added a comment - - edited

        OK, another option is possible in conjunction with the proposed Closure Composition feature in GROOVY-4322:

        def isEven = { it % 2 == 0 }
        def value = { k, v -> v }
        def key = { k, v -> k }
        def valueIsEven = value >> isEven
        def not = { !it }
        def keyWithin = { k, v, s -> k in s }
        def valueWithin = { k, v, s -> v in s }
        def m = [a:1, b:1, c:2, d:3, e:5, f:8]
        
        // existing
        println m.findAll(valueIsEven)
        // emulate rejectAll
        println m.findAll(valueIsEven >> not)
        // emulate findKeys/findByKeys
        println m.findAll(keyWithin.rcurry('c'..'e')).collect(value)
        // emulate rejectKeys/rejectByKeys
        println m.findAll(valueWithin.rcurry(3..9) >> not).collect(key)
        

        which outputs:

        [c:2, f:8]
        [a:1, b:1, d:3, e:5]
        [2, 3, 5]
        [a, b, c]
        

        This approach is probably less accessible/convenient for some users/use cases but is fairly general purpose. The intent could be to have some predefined closures like not and keyWithin or equivalent static methods that could be referenced through an appropriate method Closure.

        Show
        Paul King added a comment - - edited OK, another option is possible in conjunction with the proposed Closure Composition feature in GROOVY-4322 : def isEven = { it % 2 == 0 } def value = { k, v -> v } def key = { k, v -> k } def valueIsEven = value >> isEven def not = { !it } def keyWithin = { k, v, s -> k in s } def valueWithin = { k, v, s -> v in s } def m = [a:1, b:1, c:2, d:3, e:5, f:8] // existing println m.findAll(valueIsEven) // emulate rejectAll println m.findAll(valueIsEven >> not) // emulate findKeys/findByKeys println m.findAll(keyWithin.rcurry('c'..'e')).collect(value) // emulate rejectKeys/rejectByKeys println m.findAll(valueWithin.rcurry(3..9) >> not).collect(key) which outputs: [c:2, f:8] [a:1, b:1, d:3, e:5] [2, 3, 5] [a, b, c] This approach is probably less accessible/convenient for some users/use cases but is fairly general purpose. The intent could be to have some predefined closures like not and keyWithin or equivalent static methods that could be referenced through an appropriate method Closure.
        Hide
        Brad Long added a comment - - edited

        I like the idea of utilising standard approaches rather than a "swiss army knife" of methods. If newbies (and I don't exclude myself) can understand the general pattern to filtering keys and values, then that is better than adding a multitude of methods. Unless, of course, the standard approaches have to be twisted and bent etc. to make things work!

        So, in my opinion, as long as Groovy has a reasonably straight-forward way of providing the equivalent of domain/range restriction and anti-restriction, then Groovy has sufficiently addressed this issue. However, I did not raise the original issue, so I can't speak for the "reporter" of it.

        Show
        Brad Long added a comment - - edited I like the idea of utilising standard approaches rather than a "swiss army knife" of methods. If newbies (and I don't exclude myself) can understand the general pattern to filtering keys and values, then that is better than adding a multitude of methods. Unless, of course, the standard approaches have to be twisted and bent etc. to make things work! So, in my opinion, as long as Groovy has a reasonably straight-forward way of providing the equivalent of domain/range restriction and anti-restriction, then Groovy has sufficiently addressed this issue. However, I did not raise the original issue, so I can't speak for the "reporter" of it.
        Hide
        blackdrag blackdrag added a comment -

        since the original reporter of the issue didn't say otherwise I close the issue as "Won't Fix"

        Show
        blackdrag blackdrag added a comment - since the original reporter of the issue didn't say otherwise I close the issue as "Won't Fix"

          People

          • Assignee:
            blackdrag blackdrag
            Reporter:
            Kristian Mandrup
          • Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: