用 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

forEach
(或 each)

forEach

each

Filter

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

filter
filter (否定谓词)
Collectors.partitioningBy

findAll
findAll (否定谓词)
split

Transform

collect

map

collect

Find

detect

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

find

Test

anySatisfy
allSatisfy
nonSatisfy

anyMatch
allMatch
noneMatch

any
every
every (否定谓词)

Count

count

filter().count()

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🟢🟢'

使用 anyevery ("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 示例。