# Working with collections

version 3.0.22

Groovy provides native support for various collection types, including lists, maps or ranges. Most of those are based on the Java collection types and decorated with additional methods found in the Groovy development kit.

## 1. Lists

### 1.1. List literals

You can create lists as follows. Notice that `[]` is the empty list expression.

    def list = [5, 6, 7, 8]
    assert list.get(2) == 7
    assert list[2] == 7
    assert list instanceof java.util.List

    def emptyList = []
    assert emptyList.size() == 0
    emptyList.add(5)
    assert emptyList.size() == 1

Each list expression creates an implementation of java.util.List.

Of course lists can be used as a source to construct another list:

    def list1 = ['a', 'b', 'c']
    //construct a new list, seeded with the same items as in list1
    def list2 = new ArrayList<String>(list1)

    assert list2 == list1 // == checks that each corresponding element is the same

    // clone() can also be called
    def list3 = list1.clone()
    assert list3 == list1

A list is an ordered collection of objects:

    def list = [5, 6, 7, 8]
    assert list.size() == 4
    assert list.getClass() == ArrayList     // the specific kind of list being used

    assert list[2] == 7                     // indexing starts at 0
    assert list.getAt(2) == 7               // equivalent method to subscript operator []
    assert list.get(2) == 7                 // alternative method

    list[2] = 9
    assert list == [5, 6, 9, 8,]           // trailing comma OK

    list.putAt(2, 10)                       // equivalent method to [] when value being changed
    assert list == [5, 6, 10, 8]
    assert list.set(2, 11) == 10            // alternative method that returns old value
    assert list == [5, 6, 11, 8]

    assert ['a', 1, 'a', 'a', 2.5, 2.5f, 2.5d, 'hello', 7g, null, 9 as byte]
    //objects can be of different types; duplicates allowed

    assert [1, 2, 3, 4, 5][-1] == 5             // use negative indices to count from the end
    assert [1, 2, 3, 4, 5][-2] == 4
    assert [1, 2, 3, 4, 5].getAt(-2) == 4       // getAt() available with negative index...
    try {
        [1, 2, 3, 4, 5].get(-2)                 // but negative index not allowed with get()
        assert false
    } catch (e) {
        assert e instanceof IndexOutOfBoundsException
    }

### 1.2. List as a boolean expression

Lists can be evaluated as a `boolean` value:

    assert ![]             // an empty list evaluates as false

    //all other lists, irrespective of contents, evaluate as true
    assert [1] && ['a'] && [0] && [0.0] && [false] && [null]

### 1.3. Iterating on a list

Iterating on elements of a list is usually done calling the `each` and `eachWithIndex` methods, which execute code on each item of a list:

    [1, 2, 3].each {
        println "Item: $it" // `it` is an implicit parameter corresponding to the current element
    }
    ['a', 'b', 'c'].eachWithIndex { it, i -> // `it` is the current element, while `i` is the index
        println "$i: $it"
    }

In addition to iterating, it is often useful to create a new list by transforming each of its elements into something else. This operation, often called mapping, is done in Groovy thanks to the `collect` method:

    assert [1, 2, 3].collect { it * 2 } == [2, 4, 6]

    // shortcut syntax instead of collect
    assert [1, 2, 3]*.multiply(2) == [1, 2, 3].collect { it.multiply(2) }

    def list = [0]
    // it is possible to give `collect` the list which collects the elements
    assert [1, 2, 3].collect(list) { it * 2 } == [0, 2, 4, 6]
    assert list == [0, 2, 4, 6]

### 1.4. Manipulating lists

#### 1.4.1. Filtering and searching

The Groovy development kit contains a lot of methods on collections that enhance the standard collections with pragmatic methods, some of which are illustrated here:

    assert [1, 2, 3].find { it > 1 } == 2           // find 1st element matching criteria
    assert [1, 2, 3].findAll { it > 1 } == [2, 3]   // find all elements matching critieria
    assert ['a', 'b', 'c', 'd', 'e'].findIndexOf {      // find index of 1st element matching criteria
        it in ['c', 'e', 'g']
    } == 2

    assert ['a', 'b', 'c', 'd', 'c'].indexOf('c') == 2  // index returned
    assert ['a', 'b', 'c', 'd', 'c'].indexOf('z') == -1 // index -1 means value not in list
    assert ['a', 'b', 'c', 'd', 'c'].lastIndexOf('c') == 4

    assert [1, 2, 3].every { it < 5 }               // returns true if all elements match the predicate
    assert ![1, 2, 3].every { it < 3 }
    assert [1, 2, 3].any { it > 2 }                 // returns true if any element matches the predicate
    assert ![1, 2, 3].any { it > 3 }

    assert [1, 2, 3, 4, 5, 6].sum() == 21                // sum anything with a plus() method
    assert ['a', 'b', 'c', 'd', 'e'].sum {
        it == 'a' ? 1 : it == 'b' ? 2 : it == 'c' ? 3 : it == 'd' ? 4 : it == 'e' ? 5 : 0
        // custom value to use in sum
    } == 15
    assert ['a', 'b', 'c', 'd', 'e'].sum { ((char) it) - ((char) 'a') } == 10
    assert ['a', 'b', 'c', 'd', 'e'].sum() == 'abcde'
    assert [['a', 'b'], ['c', 'd']].sum() == ['a', 'b', 'c', 'd']

    // an initial value can be provided
    assert [].sum(1000) == 1000
    assert [1, 2, 3].sum(1000) == 1006

    assert [1, 2, 3].join('-') == '1-2-3'           // String joining
    assert [1, 2, 3].inject('counting: ') {
        str, item -> str + item                     // reduce operation
    } == 'counting: 123'
    assert [1, 2, 3].inject(0) { count, item ->
        count + item
    } == 6

And here is idiomatic Groovy code for finding the maximum and minimum in a collection:

    def list = [9, 4, 2, 10, 5]
    assert list.max() == 10
    assert list.min() == 2

    // we can also compare single characters, as anything comparable
    assert ['x', 'y', 'a', 'z'].min() == 'a'

    // we can use a closure to specify the sorting behaviour
    def list2 = ['abc', 'z', 'xyzuvw', 'Hello', '321']
    assert list2.max { it.size() } == 'xyzuvw'
    assert list2.min { it.size() } == 'z'

In addition to closures, you can use a `Comparator` to define the comparison criteria:

    Comparator mc = { a, b -> a == b ? 0 : (a < b ? -1 : 1) }

    def list = [7, 4, 9, -6, -1, 11, 2, 3, -9, 5, -13]
    assert list.max(mc) == 11
    assert list.min(mc) == -13

    Comparator mc2 = { a, b -> a == b ? 0 : (Math.abs(a) < Math.abs(b)) ? -1 : 1 }

    assert list.max(mc2) == -13
    assert list.min(mc2) == -1

    assert list.max { a, b -> a.equals(b) ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } == -13
    assert list.min { a, b -> a.equals(b) ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } == -1

#### 1.4.2. Adding or removing elements

We can use `[]` to assign a new empty list and `<<` to append items to it:

    def list = []
    assert list.empty

    list << 5
    assert list.size() == 1

    list << 7 << 'i' << 11
    assert list == [5, 7, 'i', 11]

    list << ['m', 'o']
    assert list == [5, 7, 'i', 11, ['m', 'o']]

    //first item in chain of << is target list
    assert ([1, 2] << 3 << [4, 5] << 6) == [1, 2, 3, [4, 5], 6]

    //using leftShift is equivalent to using <<
    assert ([1, 2, 3] << 4) == ([1, 2, 3].leftShift(4))

We can add to a list in many ways:

    assert [1, 2] + 3 + [4, 5] + 6 == [1, 2, 3, 4, 5, 6]
    // equivalent to calling the `plus` method
    assert [1, 2].plus(3).plus([4, 5]).plus(6) == [1, 2, 3, 4, 5, 6]

    def a = [1, 2, 3]
    a += 4      // creates a new list and assigns it to `a`
    a += [5, 6]
    assert a == [1, 2, 3, 4, 5, 6]

    assert [1, *[222, 333], 456] == [1, 222, 333, 456]
    assert [*[1, 2, 3]] == [1, 2, 3]
    assert [1, [2, 3, [4, 5], 6], 7, [8, 9]].flatten() == [1, 2, 3, 4, 5, 6, 7, 8, 9]

    def list = [1, 2]
    list.add(3)
    list.addAll([5, 4])
    assert list == [1, 2, 3, 5, 4]

    list = [1, 2]
    list.add(1, 3) // add 3 just before index 1
    assert list == [1, 3, 2]

    list.addAll(2, [5, 4]) //add [5,4] just before index 2
    assert list == [1, 3, 5, 4, 2]

    list = ['a', 'b', 'z', 'e', 'u', 'v', 'g']
    list[8] = 'x' // the [] operator is growing the list as needed
    // nulls inserted if required
    assert list == ['a', 'b', 'z', 'e', 'u', 'v', 'g', null, 'x']

It is however important that the `+` operator on a list is not mutating. Compared to `<<`, it will create a new list, which is often not what you want and can lead to performance issues.

The Groovy development kit also contains methods allowing you to easily remove elements from a list by value:

    assert ['a','b','c','b','b'] - 'c' == ['a','b','b','b']
    assert ['a','b','c','b','b'] - 'b' == ['a','c']
    assert ['a','b','c','b','b'] - ['b','c'] == ['a']

    def list = [1,2,3,4,3,2,1]
    list -= 3           // creates a new list by removing `3` from the original one
    assert list == [1,2,4,2,1]
    assert ( list -= [2,4] ) == [1,1]

It is also possible to remove an element by passing its index to the `remove` method, in which case the list is mutated:

    def list = ['a','b','c','d','e','f','b','b','a']
    assert list.remove(2) == 'c'        // remove the third element, and return it
    assert list == ['a','b','d','e','f','b','b','a']

In case you only want to remove the first element having the same value in a list, instead of removing all elements, you can call the `remove` method passing the value:

    def list= ['a','b','c','b','b']
    assert list.remove('c')             // remove 'c', and return true because element removed
    assert list.remove('b')             // remove first 'b', and return true because element removed

    assert ! list.remove('z')           // return false because no elements removed
    assert list == ['a','b','b']

As you can see, there are two `remove` methods available. One that takes an integer and removes an element by its index, and another that will remove the first element that matches the passed value. So what should we do when we have a list of integers? In this case, you may wish to use `removeAt` to remove an element by its index, and `removeElement` to remove the first element that matches a value.

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

    assert list.remove(2) == 3          // this removes the element at index 2, and returns it
    assert list == [1,2,4,5,6,2,2,1]

    assert list.removeElement(2)        // remove first 2 and return true
    assert list == [1,4,5,6,2,2,1]

    assert ! list.removeElement(8)      // return false because 8 is not in the list
    assert list == [1,4,5,6,2,2,1]

    assert list.removeAt(1) == 4        // remove element at index 1, and return it
    assert list == [1,5,6,2,2,1]

Of course, `removeAt` and `removeElement` will work with lists of any type.

Additionally, removing all the elements in a list can be done by calling the `clear` method:

    def list= ['a',2,'c',4]
    list.clear()
    assert list == []

#### 1.4.3. Set operations

The Groovy development kit also includes methods making it easy to reason on sets:

    assert 'a' in ['a','b','c']             // returns true if an element belongs to the list
    assert ['a','b','c'].contains('a')      // equivalent to the `contains` method in Java
    assert [1,3,4].containsAll([1,4])       // `containsAll` will check that all elements are found

    assert [1,2,3,3,3,3,4,5].count(3) == 4  // count the number of elements which have some value
    assert [1,2,3,3,3,3,4,5].count {
        it%2==0                             // count the number of elements which match the predicate
    } == 2

    assert [1,2,4,6,8,10,12].intersect([1,3,6,9,12]) == [1,6,12]

    assert [1,2,3].disjoint( [4,6,9] )
    assert ![1,2,3].disjoint( [2,4,6] )

#### 1.4.4. Sorting

Working with collections often implies sorting. Groovy offers a variety of options to sort lists, from using closures to comparators, as in the following examples:

    assert [6, 3, 9, 2, 7, 1, 5].sort() == [1, 2, 3, 5, 6, 7, 9]

    def list = ['abc', 'z', 'xyzuvw', 'Hello', '321']
    assert list.sort {
        it.size()
    } == ['z', 'abc', '321', 'Hello', 'xyzuvw']

    def list2 = [7, 4, -6, -1, 11, 2, 3, -9, 5, -13]
    assert list2.sort { a, b -> a == b ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } ==
            [-1, 2, 3, 4, 5, -6, 7, -9, 11, -13]

    Comparator mc = { a, b -> a == b ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 }

    // JDK 8+ only
    // list2.sort(mc)
    // assert list2 == [-1, 2, 3, 4, 5, -6, 7, -9, 11, -13]

    def list3 = [6, -3, 9, 2, -7, 1, 5]

    Collections.sort(list3)
    assert list3 == [-7, -3, 1, 2, 5, 6, 9]

    Collections.sort(list3, mc)
    assert list3 == [1, 2, -3, 5, 6, -7, 9]

#### 1.4.5. Duplicating elements

The Groovy development kit also takes advantage of operator overloading to provide methods allowing duplication of elements of a list:

    assert [1, 2, 3] * 3 == [1, 2, 3, 1, 2, 3, 1, 2, 3]
    assert [1, 2, 3].multiply(2) == [1, 2, 3, 1, 2, 3]
    assert Collections.nCopies(3, 'b') == ['b', 'b', 'b']

    // nCopies from the JDK has different semantics than multiply for lists
    assert Collections.nCopies(2, [1, 2]) == [[1, 2], [1, 2]] //not [1,2,1,2]

## 2. Maps

### 2.1. Map literals

In Groovy, maps (also known as associative arrays) can be created using the map literal syntax: `[:]`:

    def map = [name: 'Gromit', likes: 'cheese', id: 1234]
    assert map.get('name') == 'Gromit'
    assert map.get('id') == 1234
    assert map['name'] == 'Gromit'
    assert map['id'] == 1234
    assert map instanceof java.util.Map

    def emptyMap = [:]
    assert emptyMap.size() == 0
    emptyMap.put("foo", 5)
    assert emptyMap.size() == 1
    assert emptyMap.get("foo") == 5

Map keys are strings by default: `[a:1]` is equivalent to `['a':1]`. This can be confusing if you define a variable named `a` and that you want the value of `a` to be the key in your map. If this is the case, then you must escape the key by adding parenthesis, like in the following example:

    def a = 'Bob'
    def ages = [a: 43]
    assert ages['Bob'] == null // `Bob` is not found
    assert ages['a'] == 43     // because `a` is a literal!

    ages = [(a): 43]            // now we escape `a` by using parenthesis
    assert ages['Bob'] == 43   // and the value is found!

In addition to map literals, it is possible, to get a new copy of a map, to clone it:

    def map = [
            simple : 123,
            complex: [a: 1, b: 2]
    ]
    def map2 = map.clone()
    assert map2.get('simple') == map.get('simple')
    assert map2.get('complex') == map.get('complex')
    map2.get('complex').put('c', 3)
    assert map.get('complex').get('c') == 3

The resulting map is a shallow copy of the original one, as illustrated in the previous example.

### 2.2. Map property notation

Maps also act like beans so you can use the property notation to get/set items inside the `Map` as long as the keys are strings which are valid Groovy identifiers:

    def map = [name: 'Gromit', likes: 'cheese', id: 1234]
    assert map.name == 'Gromit'     // can be used instead of map.get('name')
    assert map.id == 1234

    def emptyMap = [:]
    assert emptyMap.size() == 0
    emptyMap.foo = 5
    assert emptyMap.size() == 1
    assert emptyMap.foo == 5

Note: by design `map.foo` will always look for the key `foo` in the map. This means `foo.class` will return `null` on a map that doesn’t contain the `class` key. Should you really want to know the class, then you must use `getClass()`:

    def map = [name: 'Gromit', likes: 'cheese', id: 1234]
    assert map.class == null
    assert map.get('class') == null
    assert map.getClass() == LinkedHashMap // this is probably what you want

    map = [1      : 'a',
           (true) : 'p',
           (false): 'q',
           (null) : 'x',
           'null' : 'z']
    assert map.containsKey(1) // 1 is not an identifier so used as is
    assert map.true == null
    assert map.false == null
    assert map.get(true) == 'p'
    assert map.get(false) == 'q'
    assert map.null == 'z'
    assert map.get(null) == 'x'

### 2.3. Iterating on maps

As usual in the Groovy development kit, idiomatic iteration on maps makes use of the `each` and `eachWithIndex` methods. It’s worth noting that maps created using the map literal notation are ordered, that is to say that if you iterate on map entries, it is guaranteed that the entries will be returned in the same order they were added in the map.

    def map = [
            Bob  : 42,
            Alice: 54,
            Max  : 33
    ]

    // `entry` is a map entry
    map.each { entry ->
        println "Name: $entry.key Age: $entry.value"
    }

    // `entry` is a map entry, `i` the index in the map
    map.eachWithIndex { entry, i ->
        println "$i - Name: $entry.key Age: $entry.value"
    }

    // Alternatively you can use key and value directly
    map.each { key, value ->
        println "Name: $key Age: $value"
    }

    // Key, value and i as the index in the map
    map.eachWithIndex { key, value, i ->
        println "$i - Name: $key Age: $value"
    }

### 2.4. Manipulating maps

#### 2.4.1. Adding or removing elements

Adding an element to a map can be done either using the `put` method, the subscript operator or using `putAll`:

    def defaults = [1: 'a', 2: 'b', 3: 'c', 4: 'd']
    def overrides = [2: 'z', 5: 'x', 13: 'x']

    def result = new LinkedHashMap(defaults)
    result.put(15, 't')
    result[17] = 'u'
    result.putAll(overrides)
    assert result == [1: 'a', 2: 'z', 3: 'c', 4: 'd', 5: 'x', 13: 'x', 15: 't', 17: 'u']

Removing all the elements of a map can be done by calling the `clear` method:

    def m = [1:'a', 2:'b']
    assert m.get(1) == 'a'
    m.clear()
    assert m == [:]

Maps generated using the map literal syntax are using the object `equals` and `hashcode` methods. This means that you should never use an object which hash code is subject to change over time, or you wouldn’t be able to get the associated value back.

It is also worth noting that you should never use a `GString` as the key of a map, because the hash code of a `GString` is not the same as the hash code of an equivalent `String`:

    def key = 'some key'
    def map = [:]
    def gstringKey = "${key.toUpperCase()}"
    map.put(gstringKey,'value')
    assert map.get('SOME KEY') == null

#### 2.4.2. Keys, values and entries

We can inspect the keys, values, and entries in a view:

    def map = [1:'a', 2:'b', 3:'c']

    def entries = map.entrySet()
    entries.each { entry ->
      assert entry.key in [1,2,3]
      assert entry.value in ['a','b','c']
    }

    def keys = map.keySet()
    assert keys == [1,2,3] as Set

Mutating values returned by the view (be it a map entry, a key or a value) is highly discouraged because success of the operation directly depends on the type of the map being manipulated. In particular, Groovy relies on collections from the JDK that in general make no guarantee that a collection can safely be manipulated through `keySet`, `entrySet`, or `values`.

#### 2.4.3. Filtering and searching

The Groovy development kit contains filtering, searching and collecting methods similar to those found for lists:

    def people = [
        1: [name:'Bob', age: 32, gender: 'M'],
        2: [name:'Johnny', age: 36, gender: 'M'],
        3: [name:'Claire', age: 21, gender: 'F'],
        4: [name:'Amy', age: 54, gender:'F']
    ]

    def bob = people.find { it.value.name == 'Bob' } // find a single entry
    def females = people.findAll { it.value.gender == 'F' }

    // both return entries, but you can use collect to retrieve the ages for example
    def ageOfBob = bob.value.age
    def agesOfFemales = females.collect {
        it.value.age
    }

    assert ageOfBob == 32
    assert agesOfFemales == [21,54]

    // but you could also use a key/pair value as the parameters of the closures
    def agesOfMales = people.findAll { id, person ->
        person.gender == 'M'
    }.collect { id, person ->
        person.age
    }
    assert agesOfMales == [32, 36]

    // `every` returns true if all entries match the predicate
    assert people.every { id, person ->
        person.age > 18
    }

    // `any` returns true if any entry matches the predicate

    assert people.any { id, person ->
        person.age == 54
    }

#### 2.4.4. Grouping

We can group a list into a map using some criteria:

    assert ['a', 7, 'b', [2, 3]].groupBy {
        it.class
    } == [(String)   : ['a', 'b'],
          (Integer)  : [7],
          (ArrayList): [[2, 3]]
    ]

    assert [
            [name: 'Clark', city: 'London'], [name: 'Sharma', city: 'London'],
            [name: 'Maradona', city: 'LA'], [name: 'Zhang', city: 'HK'],
            [name: 'Ali', city: 'HK'], [name: 'Liu', city: 'HK'],
    ].groupBy { it.city } == [
            London: [[name: 'Clark', city: 'London'],
                     [name: 'Sharma', city: 'London']],
            LA    : [[name: 'Maradona', city: 'LA']],
            HK    : [[name: 'Zhang', city: 'HK'],
                     [name: 'Ali', city: 'HK'],
                     [name: 'Liu', city: 'HK']],
    ]

## 3. Ranges

Ranges allow you to create a list of sequential values. These can be used as `List` since Range extends java.util.List.

Ranges defined with the `..` notation are inclusive (that is the list contains the from and to value).

Ranges defined with the `..<` notation are half-open, they include the first value but not the last value.

    // an inclusive range
    def range = 5..8
    assert range.size() == 4
    assert range.get(2) == 7
    assert range[2] == 7
    assert range instanceof java.util.List
    assert range.contains(5)
    assert range.contains(8)

    // lets use a half-open range
    range = 5..<8
    assert range.size() == 3
    assert range.get(2) == 7
    assert range[2] == 7
    assert range instanceof java.util.List
    assert range.contains(5)
    assert !range.contains(8)

    //get the end points of the range without using indexes
    range = 1..10
    assert range.from == 1
    assert range.to == 10

Note that int ranges are implemented efficiently, creating a lightweight Java object containing a from and to value.

Ranges can be used for any Java object which implements java.lang.Comparable for comparison and also have methods `next()` and `previous()` to return the next / previous item in the range. For example, you can create a range of `String` elements:

    // an inclusive range
    def range = 'a'..'d'
    assert range.size() == 4
    assert range.get(2) == 'c'
    assert range[2] == 'c'
    assert range instanceof java.util.List
    assert range.contains('a')
    assert range.contains('d')
    assert !range.contains('e')

You can iterate on a range using a classic `for` loop:

    for (i in 1..10) {
        println "Hello ${i}"
    }

but alternatively you can achieve the same effect in a more Groovy idiomatic style, by iterating a range with `each` method:

    (1..10).each { i ->
        println "Hello ${i}"
    }

Ranges can be also used in the `switch` statement:

    switch (years) {
        case 1..10: interestRate = 0.076; break;
        case 11..25: interestRate = 0.052; break;
        default: interestRate = 0.037;
    }

## 4. Syntax enhancements for collections

### 4.1. GPath support

Thanks to the support of property notation for both lists and maps, Groovy provides syntactic sugar making it really easy to deal with nested collections, as illustrated in the following examples:

    def listOfMaps = [['a': 11, 'b': 12], ['a': 21, 'b': 22]]
    assert listOfMaps.a == [11, 21] //GPath notation
    assert listOfMaps*.a == [11, 21] //spread dot notation

    listOfMaps = [['a': 11, 'b': 12], ['a': 21, 'b': 22], null]
    assert listOfMaps*.a == [11, 21, null] // caters for null values
    assert listOfMaps*.a == listOfMaps.collect { it?.a } //equivalent notation
    // But this will only collect non-null values
    assert listOfMaps.a == [11,21]

### 4.2. Spread operator

The spread operator can be used to "inline" a collection into another. It is syntactic sugar which often avoids calls to `putAll` and facilitates the realization of one-liners:

    assert [ 'z': 900,
             *: ['a': 100, 'b': 200], 'a': 300] == ['a': 300, 'b': 200, 'z': 900]
    //spread map notation in map definition
    assert [*: [3: 3, *: [5: 5]], 7: 7] == [3: 3, 5: 5, 7: 7]

    def f = { [1: 'u', 2: 'v', 3: 'w'] }
    assert [*: f(), 10: 'zz'] == [1: 'u', 10: 'zz', 2: 'v', 3: 'w']
    //spread map notation in function arguments
    f = { map -> map.c }
    assert f(*: ['a': 10, 'b': 20, 'c': 30], 'e': 50) == 30

    f = { m, i, j, k -> [m, i, j, k] }
    //using spread map notation with mixed unnamed and named arguments
    assert f('e': 100, *[4, 5], *: ['a': 10, 'b': 20, 'c': 30], 6) ==
            [["e": 100, "b": 20, "c": 30, "a": 10], 4, 5, 6]

### 4.3. The star-dot `*.' operator

The "star-dot" operator is a shortcut operator allowing you to call a method or a property on all elements of a collection:

    assert [1, 3, 5] == ['a', 'few', 'words']*.size()

    class Person {
        String name
        int age
    }
    def persons = [new Person(name:'Hugo', age:17), new Person(name:'Sandra',age:19)]
    assert [17, 19] == persons*.age

### 4.4. Slicing with the subscript operator

You can index into lists, arrays, maps using the subscript expression. It is interesting that strings are considered as special kinds of collections in that context:

    def text = 'nice cheese gromit!'
    def x = text[2]

    assert x == 'c'
    assert x.class == String

    def sub = text[5..10]
    assert sub == 'cheese'

    def list = [10, 11, 12, 13]
    def answer = list[2,3]
    assert answer == [12,13]

Notice that you can use ranges to extract part of a collection:

    list = 100..200
    sub = list[1, 3, 20..25, 33]
    assert sub == [101, 103, 120, 121, 122, 123, 124, 125, 133]

The subscript operator can be used to update an existing collection (for collection type which are not immutable):

    list = ['a','x','x','d']
    list[1..2] = ['b','c']
    assert list == ['a','b','c','d']

It is worth noting that negative indices are allowed, to extract more easily from the end of a collection:

    text = "nice cheese gromit!"
    x = text[-1]
    assert x == "!"

You can use negative indices to count from the end of the List, array, String etc.

    def name = text[-7..-2]
    assert name == "gromit"

Eventually, if you use a backwards range (the starting index is greater than the end index), then the answer is reversed.

    text = "nice cheese gromit!"
    name = text[3..1]
    assert name == "eci"

## 5. Enhanced Collection Methods

In addition to lists, maps or ranges, Groovy offers a lot of additional methods for filtering, collecting, grouping, counting, …​ which are directly available on either collections or more easily iterables.

In particular, we invite you to read the Groovy development kit API docs and specifically:

- methods added to `Iterable` can be found here

- methods added to `Iterator` can be found here

- methods added to `Collection` can be found here

- methods added to `List` can be found here

- methods added to `Map` can be found here