Search

February 23, 2020

Groovy Goodness: Lambda Default Parameter Value

Groovy 3 adds support for Java's lambda syntax expressions. This way we can write code in Groovy using lambda expressions just like in Java. But Groovy adds an additional feature and that is default parameter values for lambda expressions.

In the following example we use a default parameter value for a lambda expression.

// Groovy 3 supports Java's lambda syntax expressions.
def inc = n -> n + 1

assert inc(1) == 2


// But also adds default parameter values.
def multiplyBy = (n, factor = 2) -> n * factor

assert multiplyBy(1) == 2
assert multiplyBy(1, 10) == 10

Written with Groovy 3.0.1.

February 21, 2020

Groovy Goodness: Check Item Is Not In A Collection With !in

Groovy contains lots of little gems that just make live easier and our code more expressive. Groovy 3 doesn't disappoint by adding some nice syntax additions. For example we can now use !in to check if an item is not in a collection, opposed to in that checks if an item is in a collection.

In the following example we use !in:

def list = ['Groovy', 3, 'is', 'awesome', '!']

// New Groovy 3 syntax to check item 
// is NOT in a collection.
assert 'Java' !in list

// Old syntax.
assert !('Java' in list)

// Just to show in still works.
assert 'Groovy' in list

Written with Groovy 3.0.1.

February 19, 2020

Groovy Goodness: Shuffle List or Array

In Java we can use Collections.shuffle method to randomly reorder items in a list. Groovy 3.0.0 adds the shuffle and shuffled methods to a List or array directly. The implementation delegates to Collections.shuffle. The shuffle method will reorder the original list, so there is a side effect using this method. Or we can use the shuffled method that will return a copy of the original list where the items are randomly ordered.

In the next example we use both methods to randomly order lists:

import java.nio.charset.Charset

def signs = (2..10) + ['J', 'Q', 'K', 'A']
def symbols = ['♣', '♦', '♥', '♠']

// Create list as [♣2, ♦2, ♥2, ♠2, ..., ♣A, ♦A, ♥A, ♠A] 
def cards = [symbols, signs].combinations().collect { it.join() }

// Store original cards list.
def deck = cards.asImmutable()

// We should have 52 cards.
assert cards.size() == 52

// Let's shuffle the cards.
// Notice this will change the cards list.
cards.shuffle()
 
assert cards.every { card -> deck.contains(card) }

println cards.take(5) // Possible output: [♣6, ♠A,  ♥Q, ♦Q, ♠5]

// We can use our own Random object for shuffling.
cards.shuffle(new Random(42))

assert cards.every { card -> deck.contains(card) }

println cards.take(5)  // Possible output: [♦5, ♦2, ♦3, ♣7, ♦J]


// Store first 5 cards.
def hand = cards.take(5)

// Using shuffled we get a new list 
// with items in random order. 
// The original list is not changed.
def shuffledCards = cards.shuffled()

assert shuffledCards.size() == cards.size()
assert shuffledCards.every { card -> cards.contains(card) }

// Original list has not changed.
assert hand == cards.take(5)

println shuffledCards.take(5) // Possible output: [♣4, ♠2, ♠6, ♥Q, ♦4]

// We can pass our own Random object.
def randomizer = new Random(42)
def randomCards = cards.shuffled(randomizer)

assert randomCards.size() == cards.size()
assert randomCards.every { card -> cards.contains(card) }

println randomCards.take(5) // Possible output: [♥5, ♠6, ♠8, ♣3, ♠4]

Written with Groovy 3.0.0.

February 17, 2020

Groovy Goodness: Parse YAML With YamlSlurper

In Groovy we have useful classes to parse JSON and XML: JsonSlurper and XmlSlurper. Groovy 3 adds the YamlSlurper class to read in YAML formatted strings. The result of parsing the YAML content is a Map object.

In the next example we have a sample YAML as string that we parse using the parseText method of YamlSlurper:

import groovy.yaml.YamlSlurper

def configYaml = '''\
---
application: "Sample App"
users:
- name: "mrhaki"
  likes: 
  - Groovy
  - Clojure
  - Java
- name: "Hubert"
  likes:
  - Apples
  - Bananas
connections: 
- "WS1"
- "WS2"
'''

// Parse the YAML.
def config = new YamlSlurper().parseText(configYaml)

assert config.application == 'Sample App'

assert config.users.size() == 2
assert config.users[0] == [name: 'mrhaki', likes: ['Groovy', 'Clojure', 'Java']]
assert config.users[1] == [name: 'Hubert', likes: ['Apples', 'Bananas']]

assert config.connections == ['WS1', 'WS2']

We can also use Reader with the parse method of YamlSlurlper:

// Create YAML file.
def yamlFile = new File("sample.yml")
// with YAML contents.
yamlFile.write('''\
---
sample: true
Groovy: "Rocks!"
''')

// Using File.withReader, 
// so reader is closed by Groovy automatically.
yamlFile.withReader { reader ->
    // Use parse method of YamlSlurper.
    def yaml = new YamlSlurper().parse(reader)
    
    assert yaml.sample
    assert yaml.Groovy == 'Rocks!'
}

Finally we need to do an extra step if we want to read in a multiple YAML documents defined in one string or file. The underlying parser of YamlSlurper only reads in one document. A simple workaround is to remove the document separator (---) before parsing the YAML:

def multiDocYaml = '''\
---
version: 1
--- 
loadAtStartup: true
'''

// For YAML with multiple documents separated by ---
// we first need to remove the separators, otherwise
// only the first document is parsed.
def multiDoc = new YamlSlurper().parseText(multiDocYaml.replaceAll('---', ''))

assert multiDoc.version == 1
assert multiDoc.loadAtStartup

Written with Groovy 3.0.0.

February 14, 2020

Groovy Goodness: Create YAML With YamlBuilder

Groovy 3 adds the YamlBuilder class to create YAML output using a Groovy syntax. The YamlBuilder is closely related to JsonBuilder that is described in a previous post. We define a hierarchy using a builder syntax where we can use primitive types, strings, collections and objects. Once we have build our structure we can use the toString() method to get a string representation in YAML format.

In the following example we use YamlBuilder to create YAML:

import groovy.yaml.YamlBuilder

// Sample class and object to transform in YAML.
class User { String firstName, lastName, alias, website }
def userObj = new User(firstName: 'Hubert', lastName: 'Klein Ikkink', alias: 'mrhaki', website: 'https://www.mrhaki.com/') 


// Create YamlBuilder.
def config = new YamlBuilder()

config {
    application 'Sample App'
    version '1.0.1'
    autoStart true
    
    // We can nest YAML content.
    database {
        url 'jdbc:db//localhost'    
    }
    
    // We can use varargs arguments that will
    // turn into a list.
    // We could also use a Collection argument.
    services 'ws1', 'ws2'

    // We can even apply a closure to each
    // collection element.
    environments(['dev', 'acc']) { env ->
        name env.toUpperCase()
        active true
    }

    // Objects with their properties can be converted.
    user(userObj)
}

assert config.toString() == '''\
---
application: "Sample App"
version: "1.0.1"
autoStart: true
database:
  url: "jdbc:db//localhost"
services:
- "ws1"
- "ws2"
environments:
- name: "DEV"
  active: true
- name: "ACC"
  active: true
user:
  firstName: "Hubert"
  alias: "mrhaki"
  lastName: "Klein Ikkink"
  website: "https://www.mrhaki.com/"
'''

Written with Groovy 3.0.0.

February 12, 2020

Groovy Goodness: Calculate Average For Collection

Groovy 3 adds the average method to collections to calculate the average of the items in the collections. When the items are numbers simply the average is calculated. But we can also use a closure as argument to transform an item into a number value and then the average on that number value is calculated.

In the following example code we use the average method on a list of numbers and strings. And we use a closure to first transform an element before calculating the average:

def numbers = [10, 20, 30, 40, 50]

assert numbers.average() == 30

// We can use a closure to transform an item
// and the result is used for calculating an average.
assert numbers.average { n -> n / 10 } == 3


def words = ['Groovy', 'three', 'is', 'awesome']

// Use supported Java method reference syntax to first 
// get length of word.
assert words.average(String::size) == 5

// Calculate average number of vowels in the words.
assert words.average { s -> s.findAll(/a|e|i|o|u/).size() } == 2.25

Written with Groovy 3.0.0.

January 12, 2020

Groovy Goodness: Transform Elements While Flattening

We can use the flatten method in Groovy to flatten a collection that contains other collections into a single collection with all elements. We can pass a closure as extra argument to the flatten method to transform each element that is flattened. The argument of the closure is the element from the original collection.

In the following example we first use the flatten method without a closure argument. Then we pass a closure argument and transform the element:

def list = [1, [2, 3], [[4]]]

// Simple flatten the nested collections.
assert list.flatten() == [1, 2, 3, 4]

// We can use a closure to transform
// the elements in the resulting collection.
assert list.flatten { it * 2 } == [2, 4, 6, 8]

Written with Groovy 2.5.7.

January 10, 2020

Clojure Goodness: Flatten Collections

We can use the flatten function when we have a collection with nested sequential collections as elements and create a new sequence with the elements from all nested collections.

In the following example we use the flatten function:

(ns mrhaki.sample
  (:require [clojure.test :refer [is]]))

;; Elements from nested sequential collections are flattend into new sequence.
(is (= [1 2 3 4 5] (flatten [1 [2 3] [[4]] 5])))
(is (sequential? (flatten [1 [2 3] [[4]] 5])))
(is (= [1 2 3 4 5] (flatten [[1] [2 3] [[4 5]]])))

;; We can use different sequential collection types.
;; We might have to force a type to a sequential collection with seq.
(is (= '(1 2 3 4 5) (flatten [1 (seq (java.util.List/of 2 3)) ['(4 5)]])))
(is (= (quote (1 2 3 4 5)) (flatten [[1] [(range 2 6)]])))

;; flatten on nil returns empty sequence.
(is (= () (flatten nil)))

Written with Clojure 1.10.1.

January 8, 2020

Clojure Goodness: Getting Intersections Between Sets

In the clojure.set namespace we can find the intersection function. This functions accepts one or more sets as arguments and return a new set with all elements that are present in the sets that are passed as arguments to the intersection function. The argument must be a set, so we need to convert other collection or seq values to a set first before we use it as an argument for the function.

In the following example we use one, two or three arguments for the intersection function and also convert other types to a set to be used as argument:

(ns mrhaki.sample
  (:require [clojure.set :refer [intersection]]
            [clojure.test :refer [is]]))

;; Use intersection with sets to find common elements.
(is (= #{"Clojure"} (intersection #{"Java" "Scala" "Clojure"} #{"Clojure" "Groovy"})))

;; An empty set is returned if there is no common element.
(is (= #{} (intersection #{"Java" "Groovy" "Clojure"} #{"C++" "C#"})))

;; We can use more than two sets to find intersections.
(is (= #{"Clojure"} (intersection #{"Java" "Scala" "Clojure"} 
                                  #{"Clojure" "Groovy"} 
                                  #{"Groovy" "JRuby" "Clojure"})))

;; With one set intersections returns the set.
(is (= #{"Clojure" "Groovy"} (intersection #{"Clojure" "Groovy"})))

;; Only sets are allowed as arguments for the intersection function.
;; If one of the arguments is not a set the return value is unexpected. 
(is (= #{} (intersection #{"Clojure" "Groovy"} ["Java" "Scala" "Clojure"])))
;; But we can convert a non-set to a set with the set function.
(is (= #{"Clojure"} (intersection #{"Clojure" "Groovy"} (set ["Java" "Scala" "Clojure"]))))
(is (= #{"Clojure"} (intersection #{"Clojure" "Groovy"} (set '("Java" "Scala" "Clojure")))))
;; Or using into #{}.
(is (= #{"Clojure"} (intersection #{"Clojure" "Groovy"} 
                                  (into #{} (vals {:platform "Java" :language "Clojure"})))))

Written with Clojure 1.10.1

January 6, 2020

Clojure Goodness: Joining Elements in a Collection

We can use the join function from the clojure.string namespace to join elements from a collection into a string. We can optionally specify a separator that is used to separate each element in the string output. The separator is not used after the last element of the collection. If we don't specify a separator the elements are concatenated without separation. The string representation for each element in the collection is used in the joined end result.

In the following example code we see different usages of the join function:

(ns mrhaki.sample
  (:import [java.util Currency Locale])
  (:require [clojure.string :refer [join]]
            [clojure.test :refer [is]]))

;; Join without explicit separator simply concats values in collection.
(is (= "abc" (join ["a" "b" "c"])))

;; Join with separator uses separator between elements from collection
;; and omits the separator after the last element.
(is (= "a, b, c" (join ", " ["a" "b" "c"])))

;; Join works on multiple collection types, 
;; because each collection is transformed to a seq.
(is (= "a::b::c" (join "::" #{"a" "b" "c"})))

;; Collection with non-strings is also returned as string.
;; The string representation of each element is used.
(is (= "0 1 2 3 4 5 6 7 8 9 10" (join " " (range 11))))
(is (= "https://www.mrhaki.com:443/,EUR" (join \, [(java.net.URL. "https" "www.mrhaki.com" 443 "/")
                                                    (Currency/getInstance (Locale. "nl" "NL"))])))

;; Nil values are ignored in the join results, 
;; but separator is still used for nil element.
(is (= "Clojure--is cool--!" (join "-" ["Clojure" nil "is cool" nil "!"])))

;; Function query-params to transform a map structure with 
;; keyword keys to URL request parameters.
(defn query-params 
  "Return key/value pairs as HTTP request parameters separated by &.
   Each request parameter name and value is separated by =.
   E.g. {:q \"Clojure\" :max 10 :start 0 :format \"xml\"} is transformed
   to q=Clojure&max=10&start=0&format=xml."
  [params]
  (let [query-param (fn [[param-name param-value]] (join "=" [(name param-name) param-value]))]
    (join "&" (map query-param params))))

(is (= "q=Clojure&max=10&start=0&format=xml" (query-params {:q "Clojure" :max 10 :start 0 :format "xml"})))

Written with Clojure 1.10.1