F# :: 遍历列表并再次返回

发布于 2024-08-12 04:16:21 字数 2663 浏览 5 评论 0原文

编写一个函数来计算列表中大于或等于平均值​​的元素数量(为简单起见,使用整数除法)。
仅使用列表结构的单次遍历


我已经有一个解决方案,但它涉及ref变量从闭包 foo' 更改。

我对如何功能在满足[]时传递值的方式感兴趣?


我使用 ref 的幼稚解决方案:

let foo ls =
    let avg = ref 0
    let rec foo' xs sumAcc lenAcc  =
        match xs with
        | x'::xs'   ->
            let s = foo' xs' (x' + sumAcc) (1 + lenAcc)
            if x' < !avg then s else s + 1
        | []        ->
            avg := (sumAcc / lenAcc) //? how to change THIS to functional code ?
            0
    foo' ls 0 0


编辑(3):

我对性能感兴趣...... 在列表[1..11000]上,

`(my solution with REF) 5501: elapsed <00.0108708>`  
`(nlucaroni)            5501: elapsed <00.0041484>`  
`(kvb)                  5501: elapsed <00.0029200>`  <-- continuation is fastest
`(two pass solution)    5501: elapsed <00.0038364>`  

1.3.解决方案是非尾递归的,


// simple two-pass solution
let foo2pass (xs : System.Numerics.BigInteger list) =
    let len = System.Numerics.BigInteger.Parse(xs.Length.ToString())
    let avg = List.sum xs / len
    (List.filter (fun x -> x >= avg) xs).Length

两个passkvb 的版本适用于大列表,即:list [1I .. 10 000 000I]

(two-pass solution)   5000001: elapsed <00:00:12.3200438>     <-- 12 first time
(two-pass solution)   5000001: elapsed <00:00:06.7956307>     <-- 6
(two-pass solution)   5000001: elapsed <00:00:09.1390587>     <-- 9? WHY IS THAT
(two-pass solution)   5000001: elapsed <00:00:06.8345791>     <-- 6
(two-pass solution)   5000001: elapsed <00:00:09.1071856>     <-- 9? WHY IS THAT

每个解 5 次

(kvb tail-recursive) 5000001I: elapsed <00:00:21.1825866>   <-- 21 first time
(kvb tail-recursive) 5000001I: elapsed <00:00:14.8113939>   <-- stable
(kvb tail-recursive) 5000001I: elapsed <00:00:14.8335997>
(kvb tail-recursive) 5000001I: elapsed <00:00:14.8418234>
(kvb tail-recursive) 5000001I: elapsed <00:00:14.8331327>

,对于 list [1I .. 1 000 000I]kvb 的解更快

(two-pass solution) 500001I: elapsed <00:00:01.8975782>
(kvb tail-recursive) 500001: elapsed <00:00:00.6004453>

Write a function that counts the number of elements in the list that are larger than or equal to the average (using integer division for simplicity).
Using just a single traversal of the list structure!


I already have a solution to this, BUT it involves ref variable changed from closure foo'.

I'm interested in a way how to functionally pass value when [] is met?


My naïve solution using ref:

let foo ls =
    let avg = ref 0
    let rec foo' xs sumAcc lenAcc  =
        match xs with
        | x'::xs'   ->
            let s = foo' xs' (x' + sumAcc) (1 + lenAcc)
            if x' < !avg then s else s + 1
        | []        ->
            avg := (sumAcc / lenAcc) //? how to change THIS to functional code ?
            0
    foo' ls 0 0


EDIT(3):

I was interested in performance...
on list [1..11000]

`(my solution with REF) 5501: elapsed <00.0108708>`  
`(nlucaroni)            5501: elapsed <00.0041484>`  
`(kvb)                  5501: elapsed <00.0029200>`  <-- continuation is fastest
`(two pass solution)    5501: elapsed <00.0038364>`  

since 1. and 3. solutions are non-tail-recursive,


// simple two-pass solution
let foo2pass (xs : System.Numerics.BigInteger list) =
    let len = System.Numerics.BigInteger.Parse(xs.Length.ToString())
    let avg = List.sum xs / len
    (List.filter (fun x -> x >= avg) xs).Length

two pass and kvb's version works on big lists, ie: list [1I .. 10 000 000I]:

(two-pass solution)   5000001: elapsed <00:00:12.3200438>     <-- 12 first time
(two-pass solution)   5000001: elapsed <00:00:06.7956307>     <-- 6
(two-pass solution)   5000001: elapsed <00:00:09.1390587>     <-- 9? WHY IS THAT
(two-pass solution)   5000001: elapsed <00:00:06.8345791>     <-- 6
(two-pass solution)   5000001: elapsed <00:00:09.1071856>     <-- 9? WHY IS THAT

5 times for each solution

(kvb tail-recursive) 5000001I: elapsed <00:00:21.1825866>   <-- 21 first time
(kvb tail-recursive) 5000001I: elapsed <00:00:14.8113939>   <-- stable
(kvb tail-recursive) 5000001I: elapsed <00:00:14.8335997>
(kvb tail-recursive) 5000001I: elapsed <00:00:14.8418234>
(kvb tail-recursive) 5000001I: elapsed <00:00:14.8331327>

and for list [1I .. 1 000 000I], kvb's solution is faster

(two-pass solution) 500001I: elapsed <00:00:01.8975782>
(kvb tail-recursive) 500001: elapsed <00:00:00.6004453>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

软的没边 2024-08-19 04:16:21

您只需将平均值与返回值一起传递到堆栈即可:

let foo ls =
    let rec foo xs sumAcc lenAcc  = match xs with
        | x::xs -> let avg,s = foo xs (x + sumAcc) (1 + lenAcc) in
                   if x < avg then (avg,s) else (avg,s+1)
        | []    -> (sumAcc / lenAcc),0
    in
    let avg,res = foo ls 0 0 in
    res

You just need to pass the average up the stack with the return value:

let foo ls =
    let rec foo xs sumAcc lenAcc  = match xs with
        | x::xs -> let avg,s = foo xs (x + sumAcc) (1 + lenAcc) in
                   if x < avg then (avg,s) else (avg,s+1)
        | []    -> (sumAcc / lenAcc),0
    in
    let avg,res = foo ls 0 0 in
    res
青柠芒果 2024-08-19 04:16:21

这是另一个选项:

let foo =
  let rec helper sum ct getCt = function
  | x::xs -> 
      helper (sum+x) (ct+1) (fun avg -> getCt(avg) + (if avg <= x then 1 else 0)) xs
  | [] -> getCt(sum/ct)
  helper 0 0 (fun avg -> 0)

为了帮助澄清这里发生的情况,我将描述辅助函数的参数:

  • sum:到目前为止看到的所有项目的总和
  • ct:到目前为止看到的所有项目的计数
  • getCt:采用单个值的函数参数,它返回到目前为止看到的项目数量的计数,这些项目至少与该参数一样大
  • 模式匹配的最终列表参数
    • 如果为空,则通过总数除以计数来计算所有项目的平均值,然后将其传递给 getCt 函数以确定有多少项目大于它。
    • 否则,递归到列表的尾部,传入更新的总计和计数。新的 getCt 函数应调用之前的 getCt 函数来查看此之前有多少项大于平均值,然后如果该项也大于平均值,则增加该总数.

还可以创建仅使用尾部调用的修改版本,因此即使在任意大小的列表上也不会导致堆栈溢出。为此,我们的 getCt 函数现在需要一个表示到目前为止计数的累加器参数:

let foo =
  let rec helper sum ct getCt = function
  | x::xs -> 
      helper (sum+x) (ct+1) (fun avg n -> getCt avg (if avg <= x then n+1 else n)) xs
  | [] -> getCt (sum/ct) 0
  helper 0 0 (fun avg n -> n)

Here's another option:

let foo =
  let rec helper sum ct getCt = function
  | x::xs -> 
      helper (sum+x) (ct+1) (fun avg -> getCt(avg) + (if avg <= x then 1 else 0)) xs
  | [] -> getCt(sum/ct)
  helper 0 0 (fun avg -> 0)

To help clarify what's going on here, I'll describe the parameters for the helper function:

  • sum: the sum of all items seen so far
  • ct: the count of all items seen so far
  • getCt: a function taking a single parameter and which returns the tally of the number of items seen so far which are at least as large as that parameter
  • the final list parameter which is pattern matched
    • if it's empty, then calculate the average of all items by dividing the total by the count, and then pass this to the getCt function to determine how many items were greater than it.
    • otherwise, recurse into the tail of the list, passing in an updated total and count. The new getCt function should call the previous getCt function to see how many items prior to this one are greater than the average, and then increment that total if this item was also greater.

It's also possible to create a modified version that uses only tail calls, so it won't cause a stack overflow even on lists of arbitrary size. To do this, our getCt function now needs an accumulator parameter representing the count so far:

let foo =
  let rec helper sum ct getCt = function
  | x::xs -> 
      helper (sum+x) (ct+1) (fun avg n -> getCt avg (if avg <= x then n+1 else n)) xs
  | [] -> getCt (sum/ct) 0
  helper 0 0 (fun avg n -> n)
原谅我要高飞 2024-08-19 04:16:21

Haskell 的懒惰评价确实在“喜结连理”中大放异彩:

avgList t = let (final, sum, count) = go t 0 0 0
                avg = sum `div` count
                go [] finalA sumA countA = (finalA, sumA, countA)
                go (x:xs) finalA sumA countA = go xs (x' + finalA) (sumA + x) (countA + 1)
                    where x' = if x >= avg then 1 else 0
            in final

Haskell's lazy evaluation really shines in "tying the knot":

avgList t = let (final, sum, count) = go t 0 0 0
                avg = sum `div` count
                go [] finalA sumA countA = (finalA, sumA, countA)
                go (x:xs) finalA sumA countA = go xs (x' + finalA) (sumA + x) (countA + 1)
                    where x' = if x >= avg then 1 else 0
            in final
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文