【问题标题】:How do you use the Seq. average by function in F# to average data in one row, grouped by data in another row?您如何使用 Seq.在 F# 中按函数平均以平均一行中的数据,按另一行中的数据分组?
【发布时间】:2018-06-06 22:42:49
【问题描述】:

我有两行,州和收入。有多个具有相同状态的行,但我希望每个状态都有一行。因此,我想按州对所有收入数据进行平均,这样我就可以获得每个州的平均值,并且每个州只有一个值/行。这是我尝试按州行平均收入行中的值。 我已经像这样对序列进行了分组:

Seq.groupBy(fun row -> row.State)

但是当我尝试对已经按州分组的收入列中的数据进行平均时(从上面):

Seq.average(fun row -> row.Income)

它给了我这个错误:

" 错误 FS0001:需要一个支持运算符 '+' 的类型,但给定了一个函数类型。您可能缺少函数的参数。"

我做错了什么?

【问题讨论】:

    标签: f#


    【解决方案1】:

    如果你想传入一个函数,你需要Seq.averageBy,而不是Seq.averageSeq.average 接受一个数字序列,而Seq.averageBy 接受一个 函数 和一系列 T 类型的事物(并且该函数应该是一个接受 T 类型的事物并返回一个数字的函数)。

    另外,如果您首先使用Seq.groupBy,请注意它会返回一个元组序列,其中元组的第一个元素是键,第二个元素是具有该键的值序列。 (在类型签名中,这由类型seq<'Key * seq<'T>> 表示)。所以你想要的有点复杂,我会引导你完成它:

    1. 首先,如果您想获得整个序列的平均值,则应为 rows |> Seq.averageBy(fun row -> row.Income)
    2. 但首先,您调用的是Seq.groupBy,它返回一个元组序列。如果你做了rows |> Seq.groupBy (fun row -> row.State) |> Seq.averageBy (fun row -> row.Income),那么你会得到一个错误,说一个元组没有一个名为Income的属性。因为Seq.groupBy 调用已经将你的数据变成了这样的东西:

      seq {
          (TX, seq { row1, row4, row7 })
          (CA, seq { row2, row5, row8 })
          (NY, seq { row3, row6, row9 })
      }
      
    3. 最后你想要的是:

      seq {
          (TX, 12345.0)
          (CA, 34567.0)
          (NY, 23456.0)
      }
      
    4. 因此,您需要采用Seq.groupBy 生成的序列并转换它以保留键但转换值序列的方式。每当您认为“我想保留此序列但将其内容转换为其他内容”时,您需要Seq.map

    5. Seq.map 采用一个函数,该函数采用 T 类型的单个项目(无论 T 可能是什么),但我们可以使用 destructuring in function parameters(在该页面上查找 addOneToTuple 示例)使其更简单:因为我们知道我们映射的“外部”序列是(key, values) 的元组,我们可以编写函数来获取(key, values) 元组:fun (key, values) -> key, (values |> Seq.averageBy ...) 就是你想要的。
    6. 因此,您要使用的管道,首先分组然后平均每个组中的值(同时保留组键)将如下所示:

      rows
      |> Seq.groupBy (fun row -> row.State)
      |> Seq.map (fun (state, groupedRows) ->
          let averageIncome = groupedRows |> Seq.averageBy (fun row -> row.Income)
          (state, averageIncome))
      

    应该这样做。请注意,在最后的Seq.map 步骤中,我必须确保返回一个(state, averageIncome) 的元组;如果我刚刚返回了groupedRows |> Seq.averageBy (fun row -> row.Income) 的结果,那么我将把一个元组映射到一个单一的值,你就会得到一个不再附加状态的平均收入序列。

    我希望这可以帮助您了解如何在 F# 中解决此类问题的过程。有lots of different functions that work on collections like lists or sequences,一开始可能会有点混乱。但无论您是初学者还是经验丰富的 F# 开发人员,基本方法都是相同的:您首先要说“我有什么样的数据,完成后我想要什么样的数据?”然后你寻找一个具有正确“形状”的函数将 A 类型的数据转换为 B 类型的数据;如果没有单一功能,您可以将多个功能(如构建块)组合在一起以获得您需要的整体功能。 (例如,我们如何在此处组合 Seq.mapSeq.averageBy)。

    【讨论】:

    • 非常感谢。如果序列中除了我想要的 State 和 Income 之外还有其他值怎么办。我只是在 Let 函数中指定它们吗?
    • 这个问题太模糊了,我不太确定如何回答。你的意思是序列中的项目除了状态和收入之外还有其他属性吗?那么是的,您可能会更改最终函数以返回类似(state, averageIncome, averagePopulation) 的内容,其中averagePopulation 将由Seq.averageBy (fun row -> row.Population) 制作。如果你的意思是别的,那么让我更具体地知道你需要什么,我会给你一个更好的答案。
    • 无法在 cmets 中格式化代码;我建议询问有关此错误的新问题,以便您可以正确格式化代码。但乍一看,我看到你调用了Seq.groupBy,它创建了一个 (state, groupedRows) 的 2 元组序列,然后将其传递给 Seq.map,你的函数需要一个 (state, country) 的 3 元组,分组行)。国家从哪里来?到目前为止,您在管道中没有任何代码可以将其拉出到单独的元组成员中。这似乎是您的第一个错误。
    • 当你建立一个像这样的数据转换管道时,我发现一次做一步很有用,并且你的管道的最后一步是|> (fun result -> printfn "Pipeline produced: %A" result)%A 格式代码的意思是“弄清楚这是什么类型的东西,并适当地打印出来”,因此它对于调试类型错误(如您遇到的错误)非常有用。然后一次添加一个管道的每个步骤,然后重新运行代码并查看该步骤导致管道生成的原因。
    • 请注意,代码fun result -> printfn "Pipeline produced: %A" result 可以压缩为printfn "Pipeline produced: %A"。但是由于您似乎是 F# 的初学者,因此我建议您编写长格式(以 fun result 开头),因为您可能还不了解短格式的内容。 (正在发生的事情是currying,一旦你理解了它,这是一个非常有用的技术 - 但首先要专注于让你的头脑围绕管道,然后你可以担心以后会柯里化)。
    猜你喜欢
    • 1970-01-01
    • 2016-06-05
    • 2013-11-24
    • 1970-01-01
    • 1970-01-01
    • 2018-01-25
    • 2022-01-11
    • 1970-01-01
    • 2018-02-01
    相关资源
    最近更新 更多