groovy
  1. groovy
  2. GROOVY-4869

Add ability to 'slice' Map, querying multiple keys at once

    Details

    • Type: New Feature New Feature
    • Status: Reopened Reopened
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: None
    • Fix Version/s: None
    • Component/s: groovy-jdk
    • Labels:
      None
    • Testcase included:
      yes
    • Patch Submitted:
      Yes
    • Number of attachments :
      2

      Description

      It was brought up on the dev mailing list that you could query multiple list values at once, but not multiple Map values:

      Message from groovy-dev by Jon Cox

      Groovy currently lets you say:

      def    x       =  [100,200,300,400]
      assert x[0]    == 100
      assert x[2]    == 300
      assert x[0,2]  == [100,300]
      

      You can say:

      def    y        =  [moo: 100, cow: 200, egg:300 hen:400]
      assert y["moo"] == 100
      assert y["cow"] == 300
      

      But currently, you cannot slice a Map like this:

      assert y["moo","egg"] == [100,300] 
      

      This patch adds that functionality, so you can do (from the unit test)

          void testMapSlice() {
              def m = [ a:1, b:2, c:3 ]
              
              assert m[ 'a', 'b' ] == [ 1, 2 ]
              assert m[ 'a', 'c' ] == [ 1, 3 ]
              assert m[ 'a', 'd', 'c' ] == [ 1, null, 3 ]
          }
      

      Note that the resultant List contains null for keys that were not found. This (I believe) differs from the way that Perl handles this (I believe they are just skipped in perl), but I think that having the null values gives the developer more information.

      And they could be filtered out by doing something like:

      m[ 'a', 'd', 'c' ].findAll { it }
      

      Hope it's ok!

      1. map_slice.diff
        2 kB
        Tim Yates
      2. slice_instead_of_getAt.diff
        2 kB
        Tim Yates

        Activity

        Hide
        Jon Cox added a comment -

        Wow, that was quick!

        Incidentally, hash slices in Perl work just like yours
        (nulls are returned for keys that have no associated value).

        The behavior you've implemented looks correct/intuitive.

        Tim Yates, you rock.
        Thanks!

        Cheers,
        -Jon

        Show
        Jon Cox added a comment - Wow, that was quick! Incidentally, hash slices in Perl work just like yours (nulls are returned for keys that have no associated value). The behavior you've implemented looks correct/intuitive. Tim Yates, you rock. Thanks! Cheers, -Jon
        Hide
        Paul King added a comment - - edited

        This looks interesting and close to ready for inclusion. The main thing that worries me is that there is a slight lack of symmetry. For example, in the examples given, we can do both of these:

        assert y["moo"] == 100
        assert y["moo","egg"] == [100,300]
        

        but there is no way to for instance just return [100].

        This might not seem important, but if you consider the following code:

        def m = [:]
        def key = [1, 2]
        m[key] = 'foo'
        println m[key]
        

        It currently prints 'foo' but the above change would result in '[null, null]'. So it is a breaking change albeit one that hopefully doesn't arise too often in current code.

        Show
        Paul King added a comment - - edited This looks interesting and close to ready for inclusion. The main thing that worries me is that there is a slight lack of symmetry. For example, in the examples given, we can do both of these: assert y[ "moo" ] == 100 assert y[ "moo" , "egg" ] == [100,300] but there is no way to for instance just return [100] . This might not seem important, but if you consider the following code: def m = [:] def key = [1, 2] m[key] = 'foo' println m[key] It currently prints ' foo ' but the above change would result in ' [null, null] '. So it is a breaking change albeit one that hopefully doesn't arise too often in current code.
        Hide
        Tim Yates added a comment - - edited

        Gah, can't believe I missed that, thanks Paul!

        Not sure how to get round this blocker...I think overloading getAt to give you seamless integration is a no-go... So the best I could come up with was to change the getAt method to:

        public static <K,V> List<V> slice(Map<K,V> self, K... keys) {
            ArrayList<V> ret = new ArrayList<V>() ;
            for( K key : keys ) {
                ret.add( self.get( key ) ) ;
            }
            return ret ;
        }
        

        So thn you can do:

        def map = [ a:10, b:20 ]
        assert map.slice( 'a', 'b' ) == [ 10, 20 ]
        

        and maps with list keys work too:

        def m = [ (['a','b']):10, c:20 ]
        assert m.slice( [ 'a', 'b' ] ) == [ 10 ]
        assert m.slice( [ 'a', 'b' ], 'c', 'd' ) == [ 10, 20, null ]
        

        Does this look ok? I guess even without the seamless integration, it's still a nice method to have?

        Show
        Tim Yates added a comment - - edited Gah, can't believe I missed that, thanks Paul! Not sure how to get round this blocker...I think overloading getAt to give you seamless integration is a no-go... So the best I could come up with was to change the getAt method to: public static <K,V> List<V> slice(Map<K,V> self, K... keys) { ArrayList<V> ret = new ArrayList<V>() ; for( K key : keys ) { ret.add( self.get( key ) ) ; } return ret ; } So thn you can do: def map = [ a:10, b:20 ] assert map.slice( 'a', 'b' ) == [ 10, 20 ] and maps with list keys work too: def m = [ (['a','b']):10, c:20 ] assert m.slice( [ 'a', 'b' ] ) == [ 10 ] assert m.slice( [ 'a', 'b' ], 'c', 'd' ) == [ 10, 20, null ] Does this look ok? I guess even without the seamless integration, it's still a nice method to have?
        Hide
        Tim Yates added a comment -

        Ignore this... I just discovered you can do:

        [ a:10, b:20, c:30 ].subMap( [ 'a', 'X', 'c' ] ).values()
        

        to get exactly the same functionality (the intermediate subMap call actually gives you more functionality) with existing methods...

        Show
        Tim Yates added a comment - Ignore this... I just discovered you can do: [ a:10, b:20, c:30 ].subMap( [ 'a', 'X', 'c' ] ).values() to get exactly the same functionality (the intermediate subMap call actually gives you more functionality) with existing methods...
        Hide
        Tim Yates added a comment -

        It was pointed out to me that although it duplicates the functionality, the slice method is much nicer and this issue should remain open... Sorry about the faffing...

        Show
        Tim Yates added a comment - It was pointed out to me that although it duplicates the functionality, the slice method is much nicer and this issue should remain open... Sorry about the faffing...

          People

          • Assignee:
            Unassigned
            Reporter:
            Tim Yates
          • Votes:
            1 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

            • Created:
              Updated: