Groovy
  1. Groovy
  2. GROOVY-4899

findResults object/collection/map enhancement patch (CLONED from findResult)

    Details

    • Type: Improvement Improvement
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: None
    • Fix Version/s: 1.8.1
    • Component/s: None
    • Labels:
      None
    • Flags:
      Patch

      Description


      CLONED!! Read first comment rather than entire description.

      Groovy find and findAll take the result of the passed in closure and use groovy truth to either find the first element in the collection/map or filter the collection/map.

      There are many situations situations where what you actually want to find or collect is the result of the closure, rather than test original elements and then reprocessing them all again.

      The attached patch (with javadocs & unit tests) adds 2 methods to groovy objects, collections, and maps; enhanced versions of the existing groovy find and findAll methods:

      <object/collection/map>.findResult(closure)
      <object/collection/map>.findAllResults(closure)
      

      findResult and findAllresults are valuable for shortening/clarifying code in many situations, but they're the most valuable when the closure calls a relatively expensive method (or one that isn't idempotent). When calling the method again with the results of a find/findAll is impractical.

      I had a discussion today on twitter with Robert Fletcher (http://twitter.com/rfletcherEW/status/16410798707) that spurred this patch, though there have been many times in the past that I've wanted this functionality and ended up writing the 4+ lines of boilerplate code to work around it being missing.

      Details:

      The object/collection version of findAll iterates through the object and passes each value to the closure. The first closure result that is non-null (including false) will short circuit evaluation and return the value, example:

      // works on lists/collections
      assert "RAB" == ["foo", "bar", "baz"].findResult {
          def uCaseReversed = it.reverse().toUpperCase()  // proxy for expensive method that we don't want to do more often than necessary
          if (uCaseReversed ==~ /..B/) return uCaseReversed
      }
      
      // and objects with iterators like ranges
      assert "Found 4" == (1..10).findResult {
          if (it > 3) return "Found $it"
      }
      

      The map version of findResult is slightly different as what it returns is a Map.Entry with the key equal to the key of the map entry that generated a non-null value from the closure:

      // closure has 0/1 parameters, it passes the current Map.Entry in as it iterates
      [a: 1, b: 2, c: 3, d: 4].findResult { 
          if (it.value > 2) return "I found ${it.key}:${it.value}"
      }.with {
          assert "c" == it.key
          assert "I found c:3" == it.value
      }
      
      // closure has 2 parameters, it passes the key and value in as it iterates
      [a: 1, b: 2, c: 3, d: 4].findResult { key, value ->
          if (value > 2) return "I found ${key}:${value}"
      }.with {
          assert "c" == it.key
          assert "I found c:3" == it.value
      }
      

      findAllResults will spin through the whole iterator, but will collect only those results from the closure that are non-null, so it can both collect and filter in one step:

      assert ["Found 4", "Found 5"] == (1..5).findAllResults {
          if (it > 3) return "Found $it"
      }
      

      The map version returns a map that contains the keys that had a non-null result with the result of the closure:

      def origMap = [a:1, b:3, c:5]
      origMap.findAllResults { if (it.value >= 3) return "Found ${it.key}:${it.value}" }.with { mappedResults ->
          assert 2 == mappedResults.size()
          assert "Found b:3" == mappedResults.b
          assert "Found c:5" == mappedResults.c
      }
      

      Other groovy iterators (like collect and inject) are not able to easily fulfill this role. Collect will not do any filtering, but instead returns a full list with nulls that need to be removed. Inject can sort of be made to fill this role, but it's often more work than it's worth as you need to manage the list creation/appending and the additional closure parameter. There isn't an equivalent of collect/inject on Maps.

      I'd love it if this could be added to 1.7.4, but 1.8 would work too .

        Issue Links

          Activity

          Ted Naleid created issue -
          Paul King made changes -
          Field Original Value New Value
          Link This issue is related to GROOVY-4253 [ GROOVY-4253 ]
          Paul King made changes -
          Description Groovy find and findAll take the result of the passed in closure and use groovy truth to either find the first element in the collection/map or filter the collection/map.

          There are many situations situations where what you actually want to find or collect is the result of the closure, rather than test original elements and then reprocessing them all again.

          The attached patch (with javadocs & unit tests) adds 2 methods to groovy objects, collections, and maps; enhanced versions of the existing groovy find and findAll methods:

          {code}
          <object/collection/map>.findResult(closure)
          <object/collection/map>.findAllResults(closure)
          {code}

          findResult and findAllresults are valuable for shortening/clarifying code in many situations, but they're the most valuable when the closure calls a relatively expensive method (or one that isn't idempotent). When calling the method again with the results of a find/findAll is impractical.

          I had a discussion today on twitter with Robert Fletcher (http://twitter.com/rfletcherEW/status/16410798707) that spurred this patch, though there have been many times in the past that I've wanted this functionality and ended up writing the 4+ lines of boilerplate code to work around it being missing.

          Details:

          The object/collection version of findAll iterates through the object and passes each value to the closure. The first closure result that is non-null (including false) will short circuit evaluation and return the value, example:

          {code}
          // works on lists/collections
          assert "RAB" == ["foo", "bar", "baz"].findResult {
              def uCaseReversed = it.reverse().toUpperCase() // proxy for expensive method that we don't want to do more often than necessary
              if (uCaseReversed ==~ /..B/) return uCaseReversed
          }

          // and objects with iterators like ranges
          assert "Found 4" == (1..10).findResult {
              if (it > 3) return "Found $it"
          }
          {code}

          The map version of findResult is slightly different as what it returns is a Map.Entry with the key equal to the key of the map entry that generated a non-null value from the closure:

          {code}
          // closure has 0/1 parameters, it passes the current Map.Entry in as it iterates
          [a: 1, b: 2, c: 3, d: 4].findResult {
              if (it.value > 2) return "I found ${it.key}:${it.value}"
          }.with {
              assert "c" == it.key
              assert "I found c:3" == it.value
          }

          // closure has 2 parameters, it passes the key and value in as it iterates
          [a: 1, b: 2, c: 3, d: 4].findResult { key, value ->
              if (value > 2) return "I found ${key}:${value}"
          }.with {
              assert "c" == it.key
              assert "I found c:3" == it.value
          }
          {code}

          findAllResults will spin through the whole iterator, but will collect only those results from the closure that are non-null, so it can both collect and filter in one step:

          {code}
          assert ["Found 4", "Found 5"] == (1..5).findAllResults {
              if (it > 3) return "Found $it"
          }
          {code}

          The map version returns a map that contains the keys that had a non-null result with the result of the closure:

          {code}
          def origMap = [a:1, b:3, c:5]
          origMap.findAllResults { if (it.value >= 3) return "Found ${it.key}:${it.value}" }.with { mappedResults ->
              assert 2 == mappedResults.size()
              assert "Found b:3" == mappedResults.b
              assert "Found c:5" == mappedResults.c
          }
          {code}

          Other groovy iterators (like collect and inject) are not able to easily fulfill this role. Collect will not do any filtering, but instead returns a full list with nulls that need to be removed. Inject can sort of be made to fill this role, but it's often more work than it's worth as you need to manage the list creation/appending and the additional closure parameter. There isn't an equivalent of collect/inject on Maps.

          I'd love it if this could be added to 1.7.4, but 1.8 would work too :).
          {color:red}
          CLONED!! Read first comment rather than entire description.
          {color}

          Groovy find and findAll take the result of the passed in closure and use groovy truth to either find the first element in the collection/map or filter the collection/map.

          There are many situations situations where what you actually want to find or collect is the result of the closure, rather than test original elements and then reprocessing them all again.

          The attached patch (with javadocs & unit tests) adds 2 methods to groovy objects, collections, and maps; enhanced versions of the existing groovy find and findAll methods:

          {code}
          <object/collection/map>.findResult(closure)
          <object/collection/map>.findAllResults(closure)
          {code}

          findResult and findAllresults are valuable for shortening/clarifying code in many situations, but they're the most valuable when the closure calls a relatively expensive method (or one that isn't idempotent). When calling the method again with the results of a find/findAll is impractical.

          I had a discussion today on twitter with Robert Fletcher (http://twitter.com/rfletcherEW/status/16410798707) that spurred this patch, though there have been many times in the past that I've wanted this functionality and ended up writing the 4+ lines of boilerplate code to work around it being missing.

          Details:

          The object/collection version of findAll iterates through the object and passes each value to the closure. The first closure result that is non-null (including false) will short circuit evaluation and return the value, example:

          {code}
          // works on lists/collections
          assert "RAB" == ["foo", "bar", "baz"].findResult {
              def uCaseReversed = it.reverse().toUpperCase() // proxy for expensive method that we don't want to do more often than necessary
              if (uCaseReversed ==~ /..B/) return uCaseReversed
          }

          // and objects with iterators like ranges
          assert "Found 4" == (1..10).findResult {
              if (it > 3) return "Found $it"
          }
          {code}

          The map version of findResult is slightly different as what it returns is a Map.Entry with the key equal to the key of the map entry that generated a non-null value from the closure:

          {code}
          // closure has 0/1 parameters, it passes the current Map.Entry in as it iterates
          [a: 1, b: 2, c: 3, d: 4].findResult {
              if (it.value > 2) return "I found ${it.key}:${it.value}"
          }.with {
              assert "c" == it.key
              assert "I found c:3" == it.value
          }

          // closure has 2 parameters, it passes the key and value in as it iterates
          [a: 1, b: 2, c: 3, d: 4].findResult { key, value ->
              if (value > 2) return "I found ${key}:${value}"
          }.with {
              assert "c" == it.key
              assert "I found c:3" == it.value
          }
          {code}

          findAllResults will spin through the whole iterator, but will collect only those results from the closure that are non-null, so it can both collect and filter in one step:

          {code}
          assert ["Found 4", "Found 5"] == (1..5).findAllResults {
              if (it > 3) return "Found $it"
          }
          {code}

          The map version returns a map that contains the keys that had a non-null result with the result of the closure:

          {code}
          def origMap = [a:1, b:3, c:5]
          origMap.findAllResults { if (it.value >= 3) return "Found ${it.key}:${it.value}" }.with { mappedResults ->
              assert 2 == mappedResults.size()
              assert "Found b:3" == mappedResults.b
              assert "Found c:5" == mappedResults.c
          }
          {code}

          Other groovy iterators (like collect and inject) are not able to easily fulfill this role. Collect will not do any filtering, but instead returns a full list with nulls that need to be removed. Inject can sort of be made to fill this role, but it's often more work than it's worth as you need to manage the list creation/appending and the additional closure parameter. There isn't an equivalent of collect/inject on Maps.

          I'd love it if this could be added to 1.7.4, but 1.8 would work too :).
          Paul King made changes -
          Summary findResults object/collection/map enhancement patch (CLONED from as findResult) findResults object/collection/map enhancement patch (CLONED from findResult)
          Paul King made changes -
          Attachment DGM_other_jun11_findResult.patch [ 55719 ]
          Paul King made changes -
          Attachment DGM_other_jun11_findResults_B.patch.patch [ 55729 ]
          Paul King made changes -
          Assignee Guillaume Laforge [ guillaume ] Paul King [ paulk ]
          Fix Version/s 1.8.1 [ 17223 ]
          Resolution Fixed [ 1 ]
          Fix Version/s 1.8-beta-2 [ 16629 ]
          Fix Version/s 1.7.5 [ 16639 ]
          Status Open [ 1 ] Resolved [ 5 ]
          Paul King made changes -
          Status Resolved [ 5 ] Closed [ 6 ]
          Mark Thomas made changes -
          Project Import Sun Apr 05 13:32:57 UTC 2015 [ 1428240777691 ]
          Mark Thomas made changes -
          Workflow jira [ 12733826 ] Default workflow, editable Closed status [ 12745612 ]
          Mark Thomas made changes -
          Flags Patch [ 10430 ]
          Patch Submitted Yes [ 10763 ]
          Mark Thomas made changes -
          Project Import Mon Apr 06 02:11:23 UTC 2015 [ 1428286283443 ]
          Mark Thomas made changes -
          Workflow jira [ 12971336 ] Default workflow, editable Closed status [ 12979072 ]

            People

            • Assignee:
              Paul King
              Reporter:
              Ted Naleid
            • Votes:
              0 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development