我一直在做一些函数式编程并且有一个问题。也许我可能会遗漏一些东西,但有没有办法在中途停止“减少()”功能?让我说当我达到一定条件?这个想法似乎有点反功能。我没有在python或F#,
中看到任何这样的选项举个例子,假设我有一个列表,如[1,2,3,4,5]。我想总结这个列表中的元素,直到总和不大于某个数字(比方说8),并以某种方式返回/标记/存储/识别我实际添加的元素数量。
如果我们以python为例,我可能会尝试像
这样的东西reduce(lambda a,b : a if a + b > 8 else a + b, input)
这给了我正确答案6,但我怎么发现我已经添加了3个元素来到这里。没有这样的反击。我不能在lambdas里面做任务。我认为F#具有相同的情况。
我知道我可以使用for循环或使用可以存储状态等的函数。但是这样做的功能方法是什么。 Reduce()想要一直运行到最后,但是在这个处理线的某个地方,我们要么想要停止它(因为我们不关心处理其余的元素)或至少记下我们所在的地方停止照顾。
答案 0 :(得分:11)
减少通常与地图结合使用。例如,谷歌开发了一个map-reduce框架来查询他们的数据库,这个map-reduce模式现在用于其他几个项目(例如CouchDB,Hadoop等)。
首先,您需要将input
变量[2, 1, 3, 4, 5]
映射到类似:
[(1, 2), (1, 1), (1, 3), (1, 4), (1, 5)]
在这种情况下,x[0]
将表示获得总和x[1]
的元素数量。当然,每个元素的元素数量都是1
。
接下来,就是对这些元组进行操作:
reduce(
lambda a, b: a if a[1] + b[1] > 8 else (a[0] + b[0], a[1] + b[1]),
map(lambda x: (1, x), input))
这将返回(3, 6)
,这意味着使用6
元素的部分和为3
。
我希望你有了map-reduce-algorithms背后的想法。
的问候,
克里斯托弗
答案 1 :(得分:9)
我同意JaredPar,编写自己的递归函数,其行为与fold
类似,但允许您提前停止计算是最好的方法。我写它的方式有点笼统(这样你就可以在任何你需要折叠的情况下使用该函数可以先前停止):
// Generalized 'fold' function that allws you to stop the execution earlier
// The function 'f' has a type 'State -> 'T -> Option<'State>
// By returning 'None' we can stop the execution (and return the
// current state), by returning Some(newState), we continue folding
let rec foldStop f state input =
match input with
| x::xs ->
match f state x with
| None -> state
| Some(newState) -> foldStop f newState xs
| [] -> state
// Example that stops folding after state is larger than 10
foldStop (fun st n -> if st > 10 then None else Some(st + n)) 0 [ 1 .. 10 ]
这是一个非常通用的功能,您可以将它用于所有类似的场景。编写它的好处是你永远不需要再次编写类似的显式递归(因为你可以只使用foldStop
)。
请注意,您可以使用foldStop
来实现fold
,方法是始终将累积函数的结果包装在“Some”中(因此它更为通用):
let fold f state input =
foldStop (fun st n -> Some(f st n)) state input
答案 2 :(得分:6)
让我们假设Python有两个函数ireduce(类似于 reduce 但它会产生中间值;在某些语言中称为scanl)和ilast(获取最后一项)一个可迭代的):
from itertools import takewhile
from operator import add
xs = [1, 2, 3, 4, 5]
pair = ilast(enumerate(takewhile(lambda x: x < 8, ireduce(add, xs, 0))))
# (3, 6)
在Haskell:
last $ zip [0..] (takeWhile (< 8) (scanl (+) 0 xs))
答案 3 :(得分:5)
我认为“最实用”的方法可能是通过懒惰的评估。如果您使用的是惰性语言(如Haskell),或者使用热门语言但使用惰性列表数据结构(如F#PowerPack中的LazyList
),则可以创建例如运行总和的“扫描”,然后将其留在列表消费者的手中,以决定她想要/需要评估多少。
或者,你知道,编写一个简单的递归函数,比如@JaredPar的答案。出于某种原因,我常常对此产生心理障碍,阻止我注意到“并非所有内容都必须是fold
,您实际上可以编写自己的递归函数”:)
答案 4 :(得分:3)
尝试以下
let sumUntil list stopAfter =
let rec inner list sum =
if sum >= stopAfter then sum
else
match list with
| [] -> sum
| h::t-> inner t (sum + h)
inner list 0
F#互动结果
> sumUntil [1;2;3;4;5] 8;;
val it : int = 10
答案 5 :(得分:2)
这是一个实现该功能程序的函数:
>>> def limited_reduce(reducer, pred, lst):
... i = 0
... y = lst[0]
... while pred(y) and i < len(lst):
... i += 1
... y = reducer(lst[i], y)
... return (i, y)
或递归:
>>> def limited_reduce(reducer, pred, lst):
... def helper(i, accum, rest):
... if not rest or not pred(accum): return (i, accum)
... return helper(i+1, reducer(rest[0], accum), rest[1:])
... return helper(0, lst[0], lst[1:])
可能有一种方法可以清理它,但你会像这样使用它:
>>>> limited_reduce(lambda x,y: x+y, lambda r: r < 6, [1,2,1,3,2])
(3, 7)
答案 6 :(得分:2)
另一个功能性的approch可能是使用基于“continution”的reduce / fold版本:
let rec foldC fn acc cont = function
| [] -> acc
| x :: xs -> fn x acc (fun acc -> foldC fn acc cont xs)
使用'id'(有趣的x - &gt; x)作为'初始延续'进行调用:
foldC (fun x sum c ->
if (sum + x) > 8
then sum
else c (sum + x))
0
(fun x -> x)
[1; 2; 3; 4; 5]
你会得到'6'。
请注意,此版本的foldC
不是尾递归 - 或者是其他推荐的 - 认为......
答案 7 :(得分:2)
我认为这可以使用内置于F#Seq模块的函数来完成您的工作:
let answer =
[1; 2; 3; 4; 5]
|> Seq.scan (fun (count,sum) x -> (count+1, sum + x) ) (0,0)
|> Seq.find (fun (_,x) -> x > 8)
“scan”函数类似于“fold”,但返回包含中间(和最终)状态的序列,而不仅仅是最终状态。在这种情况下,状态是一个元组,包含一个计数和迄今处理的项目总和,从(0,0)开始。这将被一次一个地计算和馈送到“查找”函数中,该函数返回与提供的条件匹配的第一个元素(v> 8),在这种情况下为(4,10)。
上面需要处理的唯一问题是永远不会满足“find”条件,在这种情况下会引发KeyNotFoundException。您可以使用“tryFind”返回一个选项值。但是,如果没有先前状态与条件匹配,我无法看到优雅的方式返回计算的最后一个元素,而不是预先计算序列的长度:
let xs = [1; 2; 3; 4; 5]
let len = Seq.length xs
let answer =
xs
|> Seq.scan (fun (count,acc) v -> (count+1, v + acc) ) (0,0)
|> Seq.find (fun (count,v) -> v > 99 || count = len)
答案 8 :(得分:1)
退出内置reduce
部分方法的唯一方法是抛出异常。幸运的是,以这种方式获得所需结果并不难:
def interruptible_reduce(fn, *args):
try:
return reduce(fn, *args)
except StopIteration, e:
return e.args[0]
def reducefn(a, b):
total = a[1] + b[1]
if total > 8:
raise StopIteration(a)
return (a[0]+b[0], total)
input = [2, 1, 3, 4, 5]
>>> from itertools import imap
>>> interruptible_reduce(reducefn, imap(lambda x: (1,x), input))
(3, 6)
答案 9 :(得分:1)
我知道你对python特别感兴趣,但我认为我会对Clojure如何实现这一点感兴趣,因为它可以非常优雅和直接地解决问题。
Clojure有一个reduced
function,它返回传递的任何版本,这样该版本将立即终止在reduce的调用中。这样做很简单:
(reduce (fn [a v]
(if (< a 100)
(+ a v)
(reduced a)))
(range 20))
;; => 105
这将返回大于或等于100的第一个总和,或者如果没有超过则返回最大总和。并且值得注意的是,它在没有消耗/迭代整个集合被减少的情况下这样做,这可能是非常大或甚至无限的懒惰序列。此外,这比首先应用某些过滤操作具有明显的优势,因为您可以使终止条件取决于构造的值,而不仅仅是减少集合中的单个值。
你提到这个想法似乎有点&#34; anit-functional&#34;。这个可能在python中似乎就是这种情况,它不清楚如何在不诉诸一些凌乱的外部状态的情况下完成它(或者最好是reduce
的替代版本) 。然而,这在Clojure中干净利落地运作(甚至纯粹),因为它已被烘焙到语言中。关键是reduce
知道查找reduced
值,对象可以随身携带这些信息(作为元数据的包装值;不确定实际上是哪个......)。
当我需要它时,它确实是一个非常方便的功能。
答案 10 :(得分:0)
以下是Stephen代码的略微变化,使用foldl
代替foldr
(我希望)并且不需要序列:
#!/usr/bin/env python
import operator
import functools
def limited_reduce(op, it, start, pred):
if not pred(start):
return 0, start
for i, x in enumerate(it):
y = op(start, x)
if pred(y):
start = y
else:
break
return i, start
print limited_reduce(operator.add, xrange(1, 6), 0,
functools.partial(operator.gt, 8))
答案 11 :(得分:0)
如果你想避免进行不必要的计算(你仍然会使用map-reduce算法),编写你自己的reduce并捕获StopIteration
:
from functools import reduce as _reduce
def stop_iter(rv=None):
raise StopIteration(rv)
def reduce(*args):
try: return _reduce(*args)
except StopIteration as e: return e.args[0]
然后,编写一个步骤函数,当您达到某个条件时,它会在对stop_iter
的调用中包装返回值。使用原来的lambda:
reduce(lambda a, b : stop_iter(a) if a + b > 8 else a + b, input)
与Duncan的答案类似,但允许你使用lambdas(不会手动提出异常)。
答案 12 :(得分:0)
首先,在F#中。什么是第一个大于100的三角形数?
> [1..1000] |> Seq.scan (+) 0 |> Seq.find (fun x -> x > 100);;
val it : int = 105
请注意Seq.scan是懒惰的,因此永远不会计算解决方案之外的三角形数字。
要查找解决方案的序数,我们会为find
findIndex
> [1..1000] |> Seq.scan (+) 0 |> Seq.findIndex (fun x -> x > 100);;
val it : int = 14
在Python中,F#的List.scan类似于itertools.accumulate,引入了Python 3.2(2011)。
>>> from itertools import accumulate
>>> next(x for x in accumulate(range(0,1000)) if x > 100)
105
>>> next(i for (i,x) in enumerate(accumulate(range(0,1000))) if x > 100)
14