用 Groovy 玩乐高积木
作者:Paul King
发布时间:2023-04-25 11:28PM (最后更新:2023-04-27 10:42PM)
Donald Raab 继续了他关于学习 Eclipse Collections 的有趣系列。他最新的博客文章,第 4 部分,探讨了在集合中处理信息。
基本集合处理
Donald 有一个关于基本集合处理操作的有用比较表。我们将添加一个 Groovy 列
操作 | Eclipse Collections | Java 流 | Groovy |
---|---|---|---|
Do |
|
|
|
Filter |
|
|
|
Transform |
|
|
|
Find |
|
|
|
Test |
|
|
|
Count |
|
|
|
我们强烈建议您阅读 Donald 的博客文章,以了解更多关于这些类别的信息。
另外请注意,虽然 Groovy 具有内置的集合处理功能(第三列),但它也与 Eclipse Collections 和 Java 流良好兼容。因此,使用 Groovy 时,前两列同样有效。
我们的示例域
我们将遵循 Donald 文章中的一个示例,即乐高积木的示例。
为了我们的目的,我们将稍微简化示例,并且在本篇文章中,我们将忽略不同的积木类型。
因此,我们将从一个颜色枚举开始。我们只对表示简单的积木感兴趣,并使用彩色点来表示积木的俯视图
enum Color {
RED("🔴"),
YELLOW("🟡"),
BLUE("🔵"),
GREEN("🟢"),
WHITE("⚪️"),
BLACK("⚫️")
final String circle
Color(String circle) {
this.circle = circle
}
}
我们将使用与 Donald 文章中类似的记录来表示尺寸(但包括一个 toString()
)
record Dimensions(int width, int length) {
String toString() { "$length X $width" }
}
现在,我们的乐高积木记录只是将颜色和尺寸组合在一起
record LegoBrick(Color color, Dimensions dimensions) {
LegoBrick(Color color, int width, int length) {
this(color, new Dimensions(width, length))
}
static generateMultipleSizedBricks(int count, Set<Color> colors, Set<Dimensions> sizes) {
[[colors, sizes].combinations() * count]*.collect{
Color c, Dimensions d -> new LegoBrick(c, d)
}.sum()
}
String toString() {
([color.circle * dimensions.length] * dimensions.width).join('\n')
}
int length() {
dimensions.length()
}
int width() {
dimensions.width()
}
}
虽然我们没有在本篇文章中使用它,但我们创建了一个额外的构造函数,以便更容易地创建特定尺寸的积木。还存在一个工厂方法,用于将积木集合放在一起。
一些可以玩的积木
在我们的测试脚本中,我们首先设置一些积木,以便在后面的示例中使用
Set sizes = [[1, 2], [2, 2], [1, 3], [2, 3], [2, 4]].collect {
h, w -> new Dimensions(h, w)
}
Set colors = Color.values()
var bricks = LegoBrick.generateMultipleSizedBricks(5, colors, sizes)
assert bricks.size() == 150
积木的类型由其颜色和尺寸决定。有五种不同的尺寸,六种颜色。集合中每种类型有五个积木。总共有 150 个积木。
使用 each
("Do")
Set seen = []
bricks.shuffled().each {
if (seen.add(it.dimensions)) {
println "$it ($it.dimensions)"
}
}
我们将积木洗牌,然后一个接一个地处理它们。如果我们看到一个以前从未见过的尺寸的积木,我们将输出它以及它的尺寸。
输出将类似于以下内容
🔴🔴 (2 X 1) 🔵🔵🔵 (3 X 1) ⚫️⚫️⚫️ ⚫️⚫️⚫️ (3 X 2) 🔴🔴 🔴🔴 (2 X 2) ⚪️⚪️⚪️⚪️ ⚪️⚪️⚪️⚪️ (4 X 2)
由于洗牌,您可能会看到不同的颜色或尺寸的顺序不同。
使用 findAll
("Filter")
现在让我们找到宽度为 2 的红色积木的唯一尺寸(我们将按长度对它们进行排序)
var redWidthTwo = bricks.findAll(b -> b.width() == 2 && b.color == RED)
.toSet()
.sort(LegoBrick::length)
assert redWidthTwo.join(',\n') == '''\
🔴🔴
🔴🔴,
🔴🔴🔴
🔴🔴🔴,
🔴🔴🔴🔴
🔴🔴🔴🔴'''
使用 split
(也是 "Filter")
让我们找到长度为 4 或更长的积木(我们将只找到唯一的变体并按颜色对其进行排序)
def (selected, rejected) = bricks.findAll(b -> b.length() > 3)
.toSet()
.sort(LegoBrick::color)
.split { b ->
switch (b.color) {
case GREEN, WHITE, YELLOW -> true
case BLUE, RED, BLACK -> false
}
}
assert selected.join(',\n') == '''
🟡🟡🟡🟡
🟡🟡🟡🟡,
🟢🟢🟢🟢
🟢🟢🟢🟢,
⚪️⚪️⚪️⚪️
⚪️⚪️⚪️⚪️
'''.stripIndent().trim()
assert rejected.join(',\n') == '''
🔴🔴🔴🔴
🔴🔴🔴🔴,
🔵🔵🔵🔵
🔵🔵🔵🔵,
⚫️⚫️⚫️⚫️
⚫️⚫️⚫️⚫️
'''.stripIndent().trim()
使用 collect
("Transform")
让我们将每个积木转换为其尺寸的 toString,然后找到唯一的值
Set dims = bricks.collect(b -> b.dimensions.toString()).toUnique()
assert dims == ['2 X 1', '2 X 2', '3 X 1', '3 X 2', '4 X 2'] as Set
使用 find
("Find")
让我们再次洗牌(不要作弊!),然后找到第一个宽度和长度均为 2 的绿色积木
var greenTwoByTwo = bricks.shuffled().find {
b -> b.width() == b.length() && b.color == GREEN
}
assert greenTwoByTwo.toString() == '🟢🟢\n🟢🟢'
使用 any
和 every
("Test")
让我们检查一下是否存在 1 x 1(或某种 0 尺寸积木)。宽度或长度必须严格大于 1。此外,让我们检查一下是否存在宽度与长度相同的积木(回想一下我们之前的 greenTwoByTwo
,这只是一个例子)。
assert bricks.every { b -> b.width() > 1 || b.length() > 1 }
assert bricks.any { b -> b.width() == b.length() }
使用 count
("Count")
让我们计算一下有多少个绿色积木,以及有多少个长度为 4 的积木
assert bricks.count { b -> b.color == GREEN } == 25
assert bricks.count { b -> b.length() == 4 } == 30
积木的马赛克
在我们的最后一个示例中,我们从 (1 x 1) 和更大的尺寸中选取了积木,并将它们放在一起。我们拿走了 toString,为了节省空间(并带来片刻的悬念),我们将其压缩并用分块的 base64 编码。您的挑战,如果您愿意接受,就是从其压缩表示中解码积木马赛克。以下是一些可能会有帮助的代码
var encodedCompressedLegoMosaic = '''
eJztmj1uwzAMhfdcvkunLN3duUDPkwskRwiCoKljm9Tjn0gZBmLCkmmB+h5NyUZOl/P39fdjOJse
gLs9VQhCYW9fnz/pQRw6HDpUsA8R/o5nT1Ywxs7B6M+5v/Mf1O6Af5HMFzVDsU/Eudhu0R4q61+/
IN5rupOFelHe6bApnHrYHOlZXamQw4ylLjkKwMONQl8g6RUSuGBHinc09ir1Zn3iAnqNmKrjdsRt
r/Qs5pspxNv09RRLMJdaNX88s912jcTIyJi853+/eQrTfJBGGVziZ12RHSG+a6TuxasWvoRwPYZh
t/vZUsnlO/3M8/R09ca+UshSkknSCPIL/7nWUMFBRPAtTPhoKSKgU0PXIIEWdCC6LbxlYxSnxguK
VVIdUqMea7J4EcKXCERLPgZ8wcF9pNp3EsMim10wL0+LGPjuLFkMHQ7kKYlCJkx2HyWC9ODp++qx
bSWvetaX67fIFg7Npvv6oOHdIoB/oPD5UkRGvHkL33T80KaDGnkYo8zC2VC5K8JdoETKW2+QlpJf
j6WWxpV6gRMO4j4nJH8DqYLcZOtGlJjGB70HVXmJ62fVsSlYO8NVoMyirA4+k3KF9OwpQzL0PWP2
nz91abD/we3DHmIUsqQYd3YE/rA=
'''.trim()
var os = new ByteArrayOutputStream()
try (var ios = new InflaterOutputStream(os)) {
ios.write(encodedCompressedLegoMosaic.decodeBase64())
}
println os
输出留作读者的练习(但如果您在终端上尝试,您应该至少有一个 72 x 36 的布局,并使用支持 Unicode 的终端)。
剧透警告:如果您不想自己运行脚本,请尝试 这里。
结论
我们快速浏览了 Groovy 的一些基本集合处理功能。我们只是触及了表面。如果您想查看更多方法,请查看之前的一篇博客文章,该文章提供了 Groovy 列表处理的 速查表。此外,我们强烈建议您使用 Groovy 尝试 Donald 原文中的所有 Eclipse Collections 示例。