0

"When you've found the treasure, stop digging!"

I'm wanting to use more functional programming in Groovy, and thought rewriting the following method would be good training. It's harder than it looks because Groovy doesn't appear to build short-circuiting into its more functional features.

Here's an imperative function to do the job:

fullyQualifiedNames = ['a/b/c/d/e', 'f/g/h/i/j', 'f/g/h/d/e']
String shortestUniqueName(String nameToShorten) {
    def currentLevel = 1
    String shortName = ''
    def separator = '/'
    while (fullyQualifiedNames.findAll { fqName ->
        shortName = nameToShorten.tokenize(separator)[-currentLevel..-1].join(separator)
        fqName.endsWith(shortName)
    }.size() > 1) {

        ++currentLevel

    }

    return shortName
}

println shortestUniqueName('a/b/c/d/e')

Result: c/d/e

It scans a list of fully-qualified filenames and returns the shortest unique form. There are potentially hundreds of fully-qualified names.

As soon as the method finds a short name with only one match, that short name is the right answer, and the iteration can stop. There's no need to scan the rest of the name or do any more expensive list searches.

But turning to a more functional flow in Groovy, neither return nor break can drop you out of the iteration:

return simply returns from the present iteration, not from the whole .each so it doesn't short-circuit.

break isn't allowed outside of a loop, and .each {} and .eachWithIndex {} are not considered loop constructs.

I can't use .find() instead of .findAll() because my program logic requires that I scan all elements of the list, nut just stop at the first.

There are plenty of reasons not to use try..catch blocks, but the best I've read is from here:

Exceptions are basically non-local goto statements with all the consequences of the latter. Using exceptions for flow control violates the principle of least astonishment, make programs hard to read (remember that programs are written for programmers first).

Some of the usual ways around this problem are detailed here including a solution based on a new flavour of .each. This is the closest to a solution I've found so far, but I need to use .eachWithIndex() for my use case (in progress.)

Here's my own poor attempt at a short-circuiting functional solution:

fullyQualifiedNames = ['a/b/c/d/e', 'f/g/h/i/j', 'f/g/h/d/e']
def shortestUniqueName(String nameToShorten) {
    def found = ''
    def final separator = '/'
    def nameComponents = nameToShorten.tokenize(separator).reverse()
    nameComponents.eachWithIndex { String _, int i ->
        if (!found) {
            def candidate = nameComponents[0..i].reverse().join(separator)
            def matches = fullyQualifiedNames.findAll { String fqName ->
                fqName.endsWith candidate
            }
            if (matches.size() == 1) {
                found = candidate
            }
        }
    }
    return found
}

println shortestUniqueName('a/b/c/d/e')

Result: c/d/e

Please shoot me down if there is a more idiomatic way to short-circuit in Groovy that I haven't thought of. Thank you!

Community
  • 1
  • 1
Nick
  • 1,149
  • 10
  • 24
  • Possible duplicate of [How does one return from a groovy closure and stop its execution?](http://stackoverflow.com/questions/765605/how-does-one-return-from-a-groovy-closure-and-stop-its-execution) – Michael Easter Oct 19 '16 at 03:05

1 Answers1

1

There's probably a cleaner looking (and easier to read) solution, but you can do this sort of thing:

String shortestUniqueName(String nameToShorten) {
    // Split the name to shorten, and make a list of all sequential combinations of elements 
    nameToShorten.split('/').reverse().inject([]) { agg, l ->
        if(agg) agg + [agg[-1] + l] else agg << [l]
    }
    // Starting with the smallest element
    .find { elements ->
        fullyQualifiedNames.findAll { name ->
            name.endsWith(elements.reverse().join('/'))
        }.size() == 1
    }
    ?.reverse()
    ?.join('/')
    ?: ''
}    
tim_yates
  • 154,107
  • 23
  • 313
  • 320