Vim脚本中的“reduce”函数

10

Vim脚本具有一些基本的函数式编程工具。

它具有map()filter(),但据我所知,它缺少reduce()函数。 "Reduce"将值的集合减小为单个值。

是否有办法在Vim脚本中创建reduce()函数或以某种方式模拟它?是否可能在Vim脚本表达式中减少值列表,而无需编写显式循环?例如,是否有一种方法可以使用加法运算减少前五个正整数,就像在函数式语言中一样?

在JavaScript中:

[1, 2, 3, 4, 5].reduce(function(x, y) { return x + y; });
15

在Clojure中:

(reduce + (range 1 (inc 5)))
15

在 Haskell 中:

foldl (+) 0 [1..5]
15

在 J 语言中:

+/>:i.5
15

Vim脚本中:...?


我现在看到 Learn Vimscript the hard way 书中的"函数式编程"章节在练习部分有这个要求!"实现 Reduced() 函数。" - glts
3个回答

7

为了以后参考,这里是我基于@MatthewStrawbridge提供的答案所做的主题变化。

原始示例问题的表达式:

eval(join(range(1, 5), '+'))

在同样的思路下,使用 Add()arange(1, 5),可以得到一个更通用的解决方案:
eval(repeat('Add(',len(a)-1).a[0].','.join(a[1:],'),').')')

这段代码构造了字符串"Add(Add(Add(Add(1,2),3),4),5)",然后使用eval函数运行它。最后,使用Vim的列表“解构”语法[x, y; z],循环地减少Funcref和列表。请参见:h :let-unpack
function! Reduce(f, list)
  let [acc; tail] = a:list
  while !empty(tail)
    let [head; tail] = tail
    let acc = a:f(acc, head)
  endwhile
  return acc
endfunction

这是它的使用方法:

:echo Reduce(function('Add'), range(1, 5))
15

事实上,中间的 let [head; tail] = tail 是非常实用的。 - Chromium

6

我认为你需要构建一个字符串然后执行它(我承认这感觉有点笨拙)。帮助文档(:h E714)给出了以下示例:

:exe 'let sum = ' . join(nrlist, '+')

在您的情况下,其中nrlist[1, 2, 3, 4, 5],它将构造字符串let sum = 1+2+3+4+5,然后执行它。
或者,您可以编写自己的reduce函数,因为没有内置的函数。
编辑:
我在vim_use Google Group(2010年1月25日)上找到了一次讨论,其中涉及Vim中的函数式编程,包括几个实现reduce函数的方法。
第一个是Tom Link提供的:
function! Reduce(ffn, list) "{{{3
    if empty(a:list)
        return ''
    else
        let list = copy(a:list)
        let s:acc = remove(list, 0)
        let ffn = substitute(a:ffn, '\<v:acc\>', "s:acc", 'g')
        for val in list
            let s:acc = eval(substitute(ffn, '\<v:val\>', val, 'g'))
        endfor
        return s:acc
    endif
endf


echom Reduce("v:val + v:acc", [1, 2, 3, 4])
echom Reduce("v:val > v:acc ? v:val : v:acc", [1, 2, 3, 4])
echom Reduce("'v:val' < v:acc ? 'v:val' : v:acc", split("characters",
'\zs'))

第二个是由Antony Scriven提出的:

fun Reduce(funcname, list)
    let F = function(a:funcname)
    let acc = a:list[0]
    for value in a:list[1:]
        let acc = F(acc, value)
    endfor
    return acc
endfun

fun Add(a,b)
    return a:a + a:b
endfun

fun Max(a,b)
    return a:a > a:b ? a:a : a:b
endfun

fun Min(a,b)
    return a:a < a:b ? a:a : a:b
endfun

let list = [1,2,3,4,5]
echo Reduce('Add', list)
echo Reduce('Max', list)
echo Reduce('Min', list)

非常好的信息,谢谢。你甚至在:help中找到了一个例子,我之前没有看到过。干得好! - glts
@MatthewStrawbridge 在 execute 'let var = '.expr 的位置,我真的建议使用 let var = eval(expr)。还要注意两个函数都有弱点:2. 第二个使用 let F=function(a:funcname)。虽然我真的需要传递函数引用来减少实现的弱点是一旦定义了函数 F(使用:function F()…endfunction命令),这个命令将抛出一个错误(变量名与现有函数冲突)。解决方法是使用 let d={}|let d.F=function(a:funcname) - ZyX
首先,使用s:acc没有明显的原因:只使用acc也可以正常工作。但是它的弱点在于构建递归时可能会出现问题(在reduce表达式中调用reduce)。我不会在这样的实现中使用v:…变量,而是直接使用本地变量,并且不使用substitute(),因为它会破坏所有v:acc字符串,无论它们是否实际上引用变量(在字符串中可能有v:acc)。此外,在reduce表达式中使用s:v将不起作用,因为s:v将在定义reduce函数的脚本的上下文中进行评估,这一点并不明显。 - ZyX
@glts 因此,如果需要减少,我建议采用第二种实现方法,但删除 let F 并将 F() 替换为 a:func(并将 a:funcname 替换为 a:func):在参数中明确要求函数引用。为了与 sort()(也是 map()filter())保持一致,最好交换 a:funca:list,并添加第三个可选参数,用于保存字典。 - ZyX

0

很遗憾它没有reduce函数,这是我的

function s:reduce(acc, fn, args) abort
  let acc = a:acc
  for item in a:args
    let acc = a:fn(a:acc, item)
  endfor
  return acc
endfunc

这是一个基于reduce定义的求和函数。
let Sum = {... -> s:reduce(0, {acc, arg -> acc + arg}, a:000)}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接