Wait the light to fall

编程之全套工具

焉知非鱼

几年前,我在 Raku 中创建了一个关于 “transparadigm 编程"的演讲(以及后来的整个课程)。

基本的前提是,当一些语言限制你只能使用一个单一的锤子(或者更糟的是:一个装满锤子的盒子)时,Raku 被设计成一个完整的工具箱:整合了 OO、函数式、并发、声明式和过程式工具,使你能够为每个工作选择正确的组合。

最近,这个想法又在我脑海中全面浮现。在上周的每周挑战中,第二个任务是把文件路径列表,找到最长的共同初始子路径(即它们都共享的最深的目录)。

各个注册参与者提供的解决方案都非常简洁,而且通常既高效又优雅。然而他们中的大多数都是同一个程序解决方案的变体。在目录分隔符上拆分每条路径,然后对于1到N: 比较所有第N个组件,如果它们不一样就退出。类似于:

my @components = @list.map({.split('/')};

for 1..* -> $n {
    next if all(@components).elems > $n
         && [~~] @components.values».[$n];

    say @components.first.[0..$n-1].join('/');
    last;
}

并不是说这种方法有什么不好。

只是感觉比实际需要的"体力劳动"多了很多;很多低级的程序化的"告诉我怎么做”,而不是高级的声明性或功能性的"告诉我做什么"。

当我自己来解决这个问题的时候,我是这样想的。

我的搜索空间是所有可能的初始子路径的所有路径。在这个空间内,我需要找到所有路径共享的最长的初始子路径。换句话说,我需要将每条路径转换成一组越来越长的子路径,然后找到这些集合的交集,再找到该交集中最长的元素。它可以直接翻译成 Raku,比如这样:

@list.map({m:ex{^.*\/.Str}).reduce({$^a$^b}).keys.max(*.chars).say;

或(对于喜欢注释的人)喜欢这样:

@list\                             # In the list...
   .map({m:ex{^ .* '/'}\           # Find all initial subpaths
      ».Str})\                     # ...as lists of strings
         .reduce({$^a  $^b})\     # Then find all shared subpaths
             .keys.max(*.chars)\   # Then find the longest 
                .say;              # Then print it

我们从路径列表开始,通过 .map 操作对每个路径进行转换。

映射中的 m:ex{^ .* '/'} 是一个详尽的匹配操作。它不是只匹配以斜线结尾的最长字符序列,而是匹配以斜线结尾的所有可能的字符序列。换句话说,它匹配每一个可能的越来越长的初始子路径,并将它们全部返回…作为一个匹配对象列表。

在详尽匹配后后面的 ».Str 将每个这样的 Match 对象转换为一个简单的字符串,这样周围的 .map 操作就会产生一个字符串列表,代表所有可能的初始子路径。

然后,我们需要将这些列表中的每一个都视为一个集合,并找到它们所有的交集(即它们都共享的子路径)。因此,我们需要在每个列表之间注入一个集交运算符(),这正是 .reduce({$^a ∩ $^b}) 所做的事情。

这个操作的结果是一个单一的集合,包含所有原始路径共享的所有初始子路径,其中每个子路径是集合的一个键。所以调用 .keys 可以得到所有子路径的列表,我们可以通过求其中字符数最大的子路径来找到最长的子路径:.max(*.chars)

然后我们只要打印出那个最大的子路径(.say),就可以了。

对我来说,尽管这个版本比程序化的方法短得多,但也更容易理解。它严格地从左到右阅读,只需一行代码。而且在每一步的过程中,它只是描述了下一步想要的转换,让 Raku 来做艰苦的工作。它很直接…因为它可以为整个任务中的每一步准确地使用正确的工具(OO、函数式或声明式)。而且它很短…因为所有这些工具都已经直接内置于核心语言中。

所以这也是我喜欢 Raku 的另一点。“所有正确的工具,唾手可得。”

Damian

(而且,是的,我很清楚,我可以把我的版本至少缩短30%,就像这样:

keys([∩] @list.map:{~«m:ex{^.*\/}}).max(*.chars).say

问题是,这也让它至少难懂了30%!;-)

by Demian