Groovy 乐高积木™

作者: Paul King

发布时间:2023-04-25 11:28PM (最后更新:2023-04-27 10:42PM)


Donald Raab 继续了他的有趣的学习 Eclipse Collections 系列。他最新的博文,第 4 部分,着眼于处理集合中的信息

基本集合处理

Donald 有一个有用的基本集合处理操作比较表。我们将添加一个 Groovy 列

操作 Eclipse Collections Java Streams Groovy

执行

forEach
(或 each)

forEach

each

过滤

select (包含)
reject (排除)
partition (两者)

filter
filter (否定谓词)
Collectors.partitioningBy

findAll
findAll (否定谓词)
split

转换

collect

map

collect

查找

detect

filter().findFirst().orElse(null)

find

测试

anySatisfy
allSatisfy
nonSatisfy

anyMatch
allMatch
noneMatch

any
every
every (否定谓词)

计数

count

filter().count()

count

我们强烈建议阅读 Donald 的博文,以了解这些类别的更多背景信息。

另请注意,虽然 Groovy 具有内置的集合处理能力(第三列),但它也与 Eclipse Collections 和 Java Streams 配合得很好。因此,在使用 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 (“执行”)

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 (“过滤”)

现在让我们找出宽度为二的红色积木的独特尺寸(我们将按长度排序)

var redWidthTwo = bricks.findAll(b -> b.width() == 2 && b.color == RED)
        .toSet()
        .sort(LegoBrick::length)
assert redWidthTwo.join(',\n') == '''\
🔴🔴
🔴🔴,
🔴🔴🔴
🔴🔴🔴,
🔴🔴🔴🔴
🔴🔴🔴🔴'''

使用 split (也是“过滤”)

让我们找出长度为 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 (“转换”)

让我们将每个积木转换为其尺寸的 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 (“查找”)

让我们再次洗牌(这里没有作弊!)然后找到第一个宽度和长度为 2 的绿色积木

var greenTwoByTwo = bricks.shuffled().find {
    b -> b.width() == b.length() && b.color == GREEN
}
assert greenTwoByTwo.toString() == '🟢🟢\n🟢🟢'

使用 anyevery (“测试”)

让我们检查是否没有 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 (“计数”)

让我们数一数有多少绿色积木,以及有多少积木的长度为 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 示例。