Groovy 列表处理速查表
作者: Paul King
发布日期: 2022-08-28 08:46AM
声明列表
支持多种样式来声明列表
var pets = ['cat', 'canary', 'dog', 'fish', 'gerbil'] // idiomatic Groovy
var nums = [1, 2, 3, 4] as LinkedList // use 'as' for other kinds of list
var primes = List.of(2, 3, 5, 7) // Java 9+ style (immutable)
var range = 1..4 // a range is also a list (immutable)
var bigNums = [1000, 2000].asUnmodifiable() // unmodifiable (backed by original)
var negNums = [-100, -200].asImmutable() // immutable (backed by copy)
列表元素和属性
用于访问列表元素和列表属性的 Java 方法
assert !pets.isEmpty()
assert pets.size() == 5
assert pets.get(0) == 'cat'
assert pets.contains('dog')
assert pets.containsAll('cat', 'dog')
pets.forEach { assert it.size() > 2 }
assert ['a', 'b', 'a'].indexOf('a') == 0
assert ['a', 'b', 'a'].lastIndexOf('a') == 2
用于访问列表元素和列表属性的 Groovy 扩展
assert pets[0] == 'cat'
assert pets?[0] == 'cat' // safe indexing returns null if pets was null instead of NPE
assert pets.first() == 'cat'
assert pets.head() == 'cat'
assert pets[-1] == 'gerbil'
assert pets[1..3] == ['canary', 'dog', 'fish']
assert pets[3..1] == ['fish', 'dog', 'canary'] // reverse range
assert pets[1, 3, 3] == ['canary', 'fish', 'fish'] // arbitrary collection
assert pets[0..1, [3, 3]] == ['cat', 'canary', 'fish', 'fish'] // nested collections
assert [1, 2, 3, 1].count(1) == 2
assert [1, 2, 3, 4].min() == 1
assert [1, 2, 3, 4].max() == 4
assert [1, 2, 3, 4].sum() == 10
assert [1, 2, 3, 4].average() == 2.5
[1, 2, 3, 4].eachWithIndex{ val, idx -> assert val == idx + 1 }
def cpets = pets[0..1]
assert cpets == ['cat', 'canary']
assert pets.findAll { it =~ /c.*/ } == cpets
assert pets.find { it =~ /c.*/ } == cpets[0]
assert pets.grep(~/c.*/) == cpets
assert cpets.min { it.size() } == 'cat' // cpet with smallest size name
assert cpets.max { it.size() } == 'canary' // cpet with largest name
assert pets.groupBy { it.size() } == [3: ['cat', 'dog'], 4: ['fish'], 6: ['canary', 'gerbil']]
assert pets.countBy { it.size() } == [3: 2, 4: 1, 6: 2]
assert pets.sum{ it.size() } == 22 // total size of all pet names
assert pets.average{ it.size() } == 4.4 // average size of pet names
assert pets.indices == 0..4
assert pets.findIndexValues { it.size() == 6 } == [1, 4]
assert cpets.indexed() == [0: 'cat', 1: 'canary']
assert cpets.withIndex()*.toList() == [['cat', 0], ['canary', 1]]
修改可变列表
来自 Java 的用于修改列表的方法
pets.remove(2); assert pets == ['cat', 'canary', 'fish', 'gerbil'] // remove by index
pets.remove('fish'); assert pets == ['cat', 'canary', 'gerbil'] // remove by element
pets.removeIf(p -> p.startsWith("c")); assert pets == ['gerbil'] // remove by condition
pets.clear(); assert pets.isEmpty() // make empty
pets.add("kangaroo"); assert pets == ['kangaroo'] // add element
pets.add(0, "koala"); assert pets == ['koala', 'kangaroo'] // add element at index position
pets.addAll(['ant', 'bee']); assert pets == ['koala', 'kangaroo', 'ant', 'bee'] // add collection
pets.addAll(1, ['ant']); assert pets == ['koala', 'ant', 'kangaroo', 'ant', 'bee'] // add collection at index
pets.removeAll(['ant', 'flea']); assert pets == ['koala', 'kangaroo', 'bee'] // remove from collection
pets.retainAll(['bee', 'koala']); assert pets == ['koala', 'bee'] // retain from collection
pets.set(0, "zebra"); assert pets == ['zebra', 'bee'] // set element at index
pets.replaceAll(String::toUpperCase); assert pets == ['ZEBRA', 'BEE'] // replace elements by unary operator
pets.sort(Comparator.naturalOrder()); assert pets == ['BEE', 'ZEBRA'] // sort
用于修改列表的 Groovy 扩展
pets << 'rock'; assert pets == ['BEE', 'ZEBRA', 'rock'] // leftShift append
pets += ['rabbit', 'rock', 'hare']; assert pets == ['BEE', 'ZEBRA', 'rock', 'rabbit', 'rock', 'hare'] // append collection
pets.unique(); assert pets == ['BEE', 'ZEBRA', 'rock', 'rabbit', 'hare'] // remove duplicates
pets.sort{ it.size() }; assert pets == ['BEE', 'rock', 'hare', 'ZEBRA', 'rabbit'] // sort by size
pets[0] = 'ant'; assert pets == ['ant', 'rock', 'hare', 'ZEBRA', 'rabbit'] // replace element by index
pets[1..3] = ['snake', 'SNAKE']; assert pets == ['ant', 'snake', 'SNAKE', 'rabbit'] // replace range of elements
pets.unique{it.toLowerCase() }; assert pets == ['ant', 'snake', 'rabbit'] // remove duplicates ignoring case
pets.reverse(true); assert pets == ['rabbit', 'snake', 'ant'] // flip
pets.shuffle(); assert pets.size() == 3; // shuffle elements; resulting order will vary
def dice = [1, 2, 3, 4, 5, 6]
dice.removeAt(2); assert dice == [1, 2, 4, 5, 6]
dice.removeElement(2); assert dice == [1, 4, 5, 6]
dice.removeLast(); assert dice == [1, 4, 5]
dice.swap(0, 2); assert dice == [5, 4, 1]
其他列表功能
返回新列表或值的方法和运算符
assert [1, 2, 3] + [1] == [1, 2, 3, 1]
assert [1, 2, 3, 1] - [1] == [2, 3]
assert [1, 2, 3] * 2 == [1, 2, 3, 1, 2, 3]
assert [1, [2, 3]].flatten() == [1, 2, 3]
assert [1, 2, 3].disjoint([4, 5, 6])
assert [1, 2, 3].intersect([4, 3, 1]) == [1, 3]
assert [1, 2, 3].collect { it + 3 } == [4, 5, 6]
assert [1, 2, 3, 4].collect { 1 } == [1, 1, 1, 1]
assert [4, 2, 1, 3].findAll { it % 2 == 0 } == [4, 2]
assert [1, 2, 3, 4].take(3) == [1, 2, 3]
assert [1, 2, 3, 4].takeRight(3) == [2, 3, 4]
assert [1, 2, 3, 4].takeWhile{ it < 3 } == [1, 2]
assert [1, 2, 3, 4].drop(2) == [3, 4]
assert [1, 2, 3, 4].dropRight(2) == [1, 2]
assert [1, 2, 3, 4].dropWhile{it < 3 } == [3, 4]
assert [1, 2, 3, 4].join('-') == '1-2-3-4'
assert [1, 2, 3, 4].tail() == [2, 3, 4]
assert [1, 2, 3, 4].init() == [1, 2, 3]
assert [1, 2, 3, 4].tails() == [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []]
assert [1, 2, 3, 4].inits() == [[1, 2, 3, 4], [1, 2, 3], [1, 2], [1], []]
assert [1, 2, 3, 4].reverse() == [4, 3, 2, 1]
assert [1, 2, 3, 1].toUnique() == [1, 2, 3]
assert [1, 2, 3, 1].toSorted() == [1, 1, 2, 3]
assert [1, 2, 3, 4].collect { it * 2 } == [2, 4, 6, 8]
assert [[1, 2], [3, 4]].collectNested { it * 2 } == [[2, 4], [6, 8]]
def squaresAndCubesOfEvens = { it % 2 ? [] : [it**2, it**3] }
assert [1, 2, 3, 4].collectMany(squaresAndCubesOfEvens) == [4, 8, 16, 64]
assert [1, 2, 3, 4].any { it > 3 }
assert [1, 2, 3, 4].every { it < 5 }
assert ![1, 2, 3, 4].every { it > 3 }
assert [1, 2, 3, 4].find { it > 2 } == 3
assert [1, 2, 3, 4].findAll { it > 2 } == [3, 4]
assert [1, 2, 3, 4].findIndexOf { it > 2 } == 2
assert [1, 2, 3, 1].findLastIndexOf { it > 2 } == 2
assert [1, 2, 3, 4].inject { acc, i -> acc + i } == 10
assert (1..10).collate(3) == [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
assert (1..10).chop(1, 3, 2, -1) == [[1], [2, 3, 4], [5, 6], [7, 8, 9, 10]]
assert [1,2,3].permutations().toList() == [
[1, 2, 3], [3, 2, 1], [2, 1, 3], [3, 1, 2], [1, 3, 2], [2, 3, 1]
]
def matrix = [['a', 'b'], [ 1 , 2 ]]
assert matrix.transpose() == [ ['a', 1], ['b', 2] ]
assert matrix.combinations() == [ ['a', 1], ['b', 1], ['a', 2], ['b', 2] ]
assert [1, 2, 3].subsequences()*.toList() == [[1], [1, 2, 3], [2], [2, 3], [1, 2], [3], [1, 3]]
def answers = [1, 2, 3].withDefault{ 42 }
assert answers[2] == 3 && answers[99] == 42
GINQ 处理
Groovy 还支持语言集成查询支持来处理列表
// squares of odd numbers between 1 and 5
assert [1, 9, 25] == GQL {
from n in 1..5 where n % 2 != 0 select n ** 2
}
// group pets by name size
assert ["3:[cat, dog]", "4:[fish]", "6:[canary, gerbil]"] == GQL {
from p in pets
groupby p.size() as size
select size, agg(p) as names
}*.with{ "$it.size:$it.names" }
流方法
有用的流方法(值得将这些示例与早期的非流变体进行比较)
pets = ['cat', 'canary', 'dog', 'fish', 'gerbil']
assert pets.stream().filter(p -> p.size() == 3).map(String::toUpperCase).toList() == ['CAT', 'DOG']
assert pets.stream().map(p -> p.size()).distinct().sorted().toList() == [3, 4, 6] // ordered pet name sizes
assert nums.stream().reduce{ a, b -> a + b }.get() == 10
assert (1..10).stream().skip(3).limit(5).filter(i -> i % 2 == 0).map(i -> i ** 2).toList() == [16, 36, 64]
assert [1, 2, 3, 4].stream().flatMap(i -> i % 2 ? Stream.empty() : Stream.of(i**2, i**3)).toList() == [4, 8, 16, 64]
assert pets.stream().collect(Collectors.groupingBy(p -> p.size())) == [3:['cat', 'dog'], 4:['fish'], 6:['canary', 'gerbil']]
assert [1, 2, 3, 4].stream().map(Integer::toString).collect(Collectors.joining('-')) == '1-2-3-4'
Arrays.stream(0..9 as int[]).summaryStatistics().with {
assert sum == 45 && min == 0 && max == 9 && average == 4.5 && count == 10
}
assert pets.stream().allMatch(w -> w ==~ /.*[aeiou].*/) // all pet names contain a vowel
GPars
在查看 GPars 之前,值得查看一下并行流处理
// calculate squares of odd numbers from input list
assert (1..5).parallelStream().filter{ it % 2 != 0 }.map(n -> n ** 2).toList() == [1, 9, 25]
GPars 的设计是为了在流处理可用之前提供类似的功能。它仍然有一些有用的功能。
Groovy 有几个技巧可以去除外部的“withPool”子句,但我们这里会使用长格式。以上流示例的两种 GPars 变体
GParsPool.withPool {
assert (1..5).findAllParallel{ it % 2 }.collectParallel{ it ** 2 } == [1, 9, 25]
assert (1..5).parallel.filter{ it % 2 }.map{ it ** 2 }.collection == [1, 9, 25]
}
或使用 (JEP 425) 虚拟线程
GParsExecutorsPool.withExistingPool(Executors.newVirtualThreadPerTaskExecutor()) {
assert (1..5).findAllParallel{ it % 2 }.collectParallel{ it ** 2 } == [1, 9, 25]
}
其他库
JVM 上有许多与列表相关的库。我们将只关注其中的一些。
Eclipse 集合
Eclipse 集合 附带许多容器类型,包括不可变集合、原始集合、双向映射、多映射和集合,以及众多实用方法。它侧重于减少内存占用和高效的容器。如果您需要原始集合、不可变集合或一些更奇特的集合类型,例如集合或双向映射,它可能特别有用。以下只是一些示例
var certainties = Lists.immutable.of('death', 'taxes')
assert certainties.reduce{ a, b -> "$a & $b" }.get() == 'death & taxes'
var numBag = Bags.immutable.with('One', 'One', 'Two', 'Three')
assert numBag.toMapOfItemToCount() == [One:2, Two:1, Three:1]
var biMap = BiMaps.immutable.with(6, "six", 2, "two")
assert biMap.inverse().six == 6
Guava
Guava 为 JDK 集合生态系统提供了一些扩展。特别是,它具有不可变集合、新的集合类型(例如多集和双向映射)以及各种强大的扩展和实用程序。以下是一些示例
var set = TreeMultiset.create([1, 2, 3])
assert set == TreeMultiset.create([3, 2, 1])
set.addAll([1, 3, 5])
assert set.size() == 6 && set.elementSet().size() == 4
assert set.toList() == [1, 1, 2, 3, 3, 5]
var bimap = HashBiMap.create()
bimap.five = 5
assert bimap.inverse()[5] == 'five'
Apache Commons Collections
Apache Commons Collections 库扩展了 JDK 集合框架,添加了一些新的类型,例如双向映射和集合,并提供了许多比较器和迭代器实现。该库的设计目的是填补 JDK 中的空白,虽然 JDK 本身现在已经填补了一些空白,但 Commons Collections 仍然包含许多有用的功能。以下是一些示例
var six = [six: 6] as TreeBidiMap
assert six.inverseBidiMap() == [6: 'six']
var bag = new HashBag(['one'] * 6)
bag.remove('one', 2)
assert bag.getCount('one') == 4
更多信息
-
GPars (网站)
-
Guava (用户指南)
-
Apache Commons Collections (网站)
-
Groovy 实战(附录 D 有关于列表、GString、闭包、正则表达式和 GPath 表达式的速查表)
-
Groovy 中的列表(Baeldung)
结论
我们已经查看了使用 Groovy 处理列表的更常见方法以及一些其他有用的库。