Groovy 海库处理
作者:Paul King
发布日期:2023-11-07 07:22PM
这篇博客介绍了一些用 Groovy 解决 使用文本块的 Java 海库 文章中示例的方法。在他的示例中,他使用的是 Java 文本块,但 Groovy 已经支持类似的功能,即多行字符串,因此我们不再赘述这方面的内容。
以下是 Donald 的一些创意写作
在他的示例中,他以各种方式处理这些示例。我们将看看如何使用 Groovy 做相同的示例。
在最近的 JEP Café 视频 中,José Paumard 对这些示例进行了很好的后续讨论。
示例 1:查找不同的字母
在这个示例中,我们想查看海库文本中使用的所有单个字母。我们将忽略任何标点符号,并将所有字母转换为小写,因为我们不关心大小写区别。
以下是 Groovy 代码
assert haiku.codePoints().toArray()
.findAll(Character::isAlphabetic)
.collect(Character::toLowerCase)
.toUnique()
.collect(Character::toString)
.join() == 'breakingthoupvmwcdflsy'
与博客和视频中的内容相比,我们做了一个小小的改变。我们使用了 codePoints()
而不是 chars()
。虽然 Donald 目前的海库文本中不包含任何代理对,但我们最好做好准备,以防将来出现这种情况。你可以看到,下面的笑脸表情符号是用两个字符编码的
assert "😃".codePoints().mapToObj(Character::toString).toList()[0].size() == 2
我们确信,这类符号迟早会开始更频繁地出现在某人的海库中。
示例 2:将字母拆分为唯一和重复分区
在下一个示例中,我们想统计每个字母出现的次数,并区分多次重复的字母和可能只出现一次的字母。
我们将使用一个映射来存储看到的字母(键)以及它们出现的次数(值)。我们将创建一个条件,该条件对只出现一次的映射条目为真。
var uniqueAndDuplicatePartitions = e -> e.value == 1
我们使用 Groovy 的 countBy
方法创建我们的映射,然后使用之前的条件使用 split
方法。这将映射划分为唯一集合和重复集合。
assert haiku.codePoints().toArray()
.findAll(Character::isAlphabetic)
.collect(Character::toLowerCase)
.collect(Character::toString)
.countBy{ it }
.split(uniqueAndDuplicatePartitions)
*.size() == [0, 22]
当我们检查这两个集合的大小后,我们发现没有字母只出现一次,所有字母都重复了。
示例 3:查找使用频率最高的字母
我们最后的示例是上一个示例的变体。我们不想仅仅找到唯一和重复的字符,而是想找到三个出现频率最高的字母。
与之前一样,我们需要一个条件。这次,我们将用它来进行排序(以逆序排序)
var byCountDescending = e -> -e.value
现在,我们只需要使用我们的条件进行排序,并取前 3 个。
assert haiku.codePoints().toArray()
.findAll(Character::isAlphabetic)
.collect(Character::toLowerCase)
.collect(Character::toString)
.countBy{ it }
.sort(byCountDescending)
.take(3) == [e:94, t:65, i:62]
示例 3:其他变体
我们也可以使用 Eclipse 集合来实现这一点
var top3 = Strings.asCodePoints(haiku)
.select(Character::isAlphabetic)
.collectInt(Character::toLowerCase)
.collect(Character::toString)
.toBag()
.topOccurrences(3)
[e:94, t:65, i:62].eachWithIndex{ k, v, i ->
assert top3[i] == PrimitiveTuples.pair(k, v)
}
使用 Bag
及其 topOccurrences
方法为我们完成了大部分繁重的工作。实际上,这个解决方案在出现平局的情况下也有行为上的差异,我们将在后面讨论。
我们当然可以使用 Stream API,就像博客和视频中所做的那样。以下是 Groovy 等效代码
assert haiku.codePoints()
.filter(Character::isAlphabetic)
.map(Character::toLowerCase)
.mapToObj(Character::toString)
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
))
.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue().reversed())
.limit(3)
.toList()
.collectEntries() == [e:94, t:65, i:62]
视频指出,上面的代码本质上是相当技术的,因为你需要跟踪我们是如何使用映射来模拟我们的问题域,以便理解每个处理步骤的作用。
它建议使用记录来更好地捕捉一些领域模型,使我们的代码更直观。让我们看看如何在 Groovy 上做同样的事情。
以下是我们将使用的三个记录
record Letter(int codePoint) {
Letter(int codePoint) {
this.codePoint = Character.toLowerCase(codePoint)
}
}
record LetterCount(int count) implements Comparable<LetterCount> {
int compareTo(LetterCount other) {
Integer.compare(this.count, other.count)
}
}
record LetterByCount(Letter letter, LetterCount count) {
LetterByCount(Letter letter, Integer count) {
this(letter, new LetterCount(count))
}
static Comparator<? super LetterByCount> comparingByCount() {
Comparator.comparing(LetterByCount::count)
}
}
现在,我们的“收集”和“排序”步骤是针对我们的领域模型的,它更容易理解
assert haiku.codePoints().toArray()
.findAll(Character::isAlphabetic)
.collect(Letter::new)
.countBy{ it }
.collect(LetterByCount::new)
.toSorted(LetterByCount.comparingByCount().reversed())
.take(3)
*.letter
*.codePoint
.collect(Character::toString) == ['e', 't', 'i']
视频还讨论了 Eclipse 集合版本的一个有趣的差异。Bag
类中的 topOccurrences
方法处理平局,在出现平局的情况下,返回两个结果。在排名前 3 的结果中没有平局,事实上,排名前 14 的结果中也没有平局,但如果你调用 topOccurrences(15)
,那么将返回 16 个结果。我们可以按照视频中的建议,得到以下 Groovy 代码
var byCountReversed = e -> -e.key
assert haiku.codePoints().toArray()
.findAll(Character::isAlphabetic)
.collect(Character::toLowerCase)
.collect(Character::toString)
.countBy{ it }
.groupBy{ k, v -> v }
.sort(byCountReversed)
.take(15)
*.value.sum()*.key == ['e', 't', 'i', 'a',
'o', 'n', 's', 'r',
'h', 'd', 'w', 'l',
'u', 'm', 'p', 'c']
我们实际上是在做两个“分组”语句,第一个作为 countBy
的一部分,然后在值上进行后续的 groupBy
。正如我们所看到的,如果我们查看排名前 15 的结果,将返回 16 个值。