Search code examples
stringgroovynullremove-if

Remove elements in a string separated by commas in groovy


I am building a string in this way:

def presentationType = "${feedDisplayType}, ${moduleType}, ${cellType}"

What happens is that sometimes the variables has null values, and the resulting string is showing this null values, I have to avoid show null values.

I want to know if there is some way to remove this possible null values in the string or avoid add this when they will be presented.

I know that it is possible and more easy to do with arrays, but I want to do it with strings in a more direct way.

Thanks for any help in advance.


Solution

  • There are 3 options:

    1. GString interpolation

    def presentationType = "${feedDisplayType != null && !feedDisplayType.isEmpty() ? feedDisplayType + ', ' : ''}${moduleType != null && !moduleType.isEmpty() ? moduleType + ', ' : ''}${cellType != null && !cellType.isEmpty() ? cellType : ''}".toString()
    

    2. Using StringBuilder

    def sb = new StringBuilder()
    if (feedDisplayType != null && !feedDisplayType.isEmpty()) {
        sb.append(feedDisplayType)
        sb.append(', ')
    }
    if (moduleType != null && !moduleType.isEmpty()) {
        sb.append(moduleType)
        sb.append(', ')
    }
    if (cellType != null && !cellType.isEmpty()) {
        sb.append(cellType)
    }
    def presentationType = sb.toString()
    

    3. Joining a list with , as a delimiter

    def presentationType = [feedDisplayType, moduleType, cellType].findAll { str -> str != null && !str.isEmpty() }.join(', ')
    

    Benchmark

    Before going into conclusion let's benchmark all 3 methods using GBench tool:

    @Grab(group='org.gperfutils', module='gbench', version='0.4.3-groovy-2.4')
    
    def feedDisplayType = 'test'
    def moduleType = null
    def cellType = ''
    
    def r = benchmark {
    
        'GString method' {
            def presentationType = "${feedDisplayType != null && !feedDisplayType.isEmpty() ? feedDisplayType + ', ' : ''}${moduleType != null && !moduleType.isEmpty() ? moduleType + ', ' : ''}${cellType != null && !cellType.isEmpty() ? cellType : ''}".toString()
        }
        'StringBuilder method' {
            def sb = new StringBuilder()
            if (feedDisplayType != null && !feedDisplayType.isEmpty()) {
                sb.append(feedDisplayType)
                sb.append(', ')
            }
            if (moduleType != null && !moduleType.isEmpty()) {
                sb.append(moduleType)
                sb.append(', ')
            }
            if (cellType != null && !cellType.isEmpty()) {
                sb.append(cellType)
            }
            def presentationType = sb.toString()
        }
        'Join list method' {
            def presentationType = [feedDisplayType, moduleType, cellType].findAll { str -> str != null && !str.isEmpty() }.join(', ')
        }
    }
    r.prettyPrint()
    

    Output

    Environment
    ===========
    * Groovy: 2.4.12
    * JVM: OpenJDK 64-Bit Server VM (25.171-b10, Oracle Corporation)
        * JRE: 1.8.0_171
        * Total Memory: 236 MB
        * Maximum Memory: 3497 MB
    * OS: Linux (4.16.5-200.fc27.x86_64, amd64)
    
    Options
    =======
    * Warm Up: Auto (- 60 sec)
    * CPU Time Measurement: On
    
                          user  system  cpu  real
    
    GString method         265       2  267   268
    StringBuilder method    72       4   76    77
    Join list method       484       3  487   495
    

    Conclusion

    If you aim towards highest throughput, StringBuilder method is the best one (77 nanoseconds mean time).

    GString method is a few times slower than StringBuilder and it is much less readable due to all condition statements inside a single GString. It is also pretty error prone - it's easy to make a mistake when interpolating String in this case.

    Joining list method is the slowest one (only 2 times slower approximately than GString method), but it is the cleanest one. And it is still pretty fast - 495 nanoseconds is acceptable in most cases. Of course optimization depends on specific use case - if you have to execute this part of code million times per second, then using StringBuilder instead makes much more sense.

    Benchmark corner cases

    To make this example complete let's also take a look at corner cases in benchmarking. We use the same code with different input.

    Input:

    def feedDisplayType = 'lorem ipsum'
    def moduleType = 'dolor sit amet'
    def cellType = '123456789'
    

    Output:

                          user  system  cpu  real
    
    GString method         387       1  388   390
    StringBuilder method   170       0  170   175
    Join list method       847       6  853   859
    

    Input:

    def feedDisplayType = ''
    def moduleType = ''
    def cellType = ''
    

    Output:

                          user  system  cpu  real
    
    GString method         237       5  242   242
    StringBuilder method    44       0   44    44
    Join list method       441       0  441   446