【问题标题】:How to fold/reduce a map?如何折叠/缩小地图?
【发布时间】:2021-09-27 14:50:06
【问题描述】:

我希望能够折叠/缩小地图,就像使用 Array 和 Set 一样。我看到的最接近的是 getFoldableWithIndex,但我不知道如何使用它或让它与 Typescript 一起编译。我觉得烦人的一件事是它需要一个 ORD。也许这是使函数更具确定性所必需的,但排序对于许多折叠/归约任务并不重要,并且会降低性能。我的解决方法是离开 fp-ts,生成一个数组或可迭代的 [key,value] 对,然后对其进行简单的 reduce 以找到年龄最大的人。

import { map as MAP, ord as ORD } from "fp-ts"

type Person = string;
type Age = number;

const oldestPerson = (ps:Map<Person, Age>) =>
  pipe(ps, MAP.getFoldableWithIndex<Person>(ORD.fromCompare<Person>((a,b)=>0)))......

刚刚注意到一个更新的未发布版本支持reduce。仍然不知道为什么需要 ORD 或如何使用 getFolderableWithIndex 或新版本何时发布。

https://github.com/gcanti/fp-ts/blob/2.11/src/ReadonlyMap.ts

【问题讨论】:

    标签: fp-ts


    【解决方案1】:

    如果您想留在“fp-ts 内”,您可以执行以下操作:

    import { flow, pipe, tuple } from "fp-ts/function"
    import * as Mn from "fp-ts/Monoid"
    import * as N from "fp-ts/number"
    import * as O from "fp-ts/Option"
    import * as Ord from "fp-ts/Ord"
    import * as RM from "fp-ts/ReadonlyMap"
    import * as RT from "fp-ts/ReadonlyTuple"
    import * as Sg from "fp-ts/Semigroup"
    import * as S from "fp-ts/string"
    
    type Person = string
    type Age = number
    type PersonAge = readonly [Person, Age]
    
    const ordByAge: Ord.Ord<PersonAge> = pipe(N.Ord, Ord.contramap(RT.snd))
    
    const monoidMaxByAge: Mn.Monoid<O.Option<PersonAge>> = pipe(
      ordByAge,
      Sg.max,
      O.getMonoid
    )
    
    const oldestPerson = (ps: ReadonlyMap<Person, Age>) =>
      pipe(
        ps,
        RM.foldMapWithIndex(S.Ord)(monoidMaxByAge)(flow(tuple, O.some)),
        O.map(RT.fst)
      )
    

    这非常冗长,并没有解决您对性能的担忧。

    我觉得烦人的一件事是它需要一个 ORD。也许这是使函数更具确定性所必需的,但排序对于许多折叠/归约任务并不重要并且会降低性能。

    你说得对,它是关于决定论的。原生 JS Map 按插入顺序存储条目,因此如果没有 Ord 实例作为键,这个 oldestPerson 函数可能会为两个给定的 Maps 产生不同的结果,除了插入顺序之外它们是等效的。我会认为这是出乎意料的行为。

    这是我期望持有的:

    import * as assert from "assert"
    
    const aliceBob: ReadonlyMap<Person, Age> = new Map([
      ["Alice", 25],
      ["Bob", 25],
    ])
    
    const bobAlice: ReadonlyMap<Person, Age> = new Map([
      ["Bob", 25],
      ["Alice", 25],
    ])
    
    const emptyMap: ReadonlyMap<Person, Age> = new Map([])
    
    assert.deepStrictEqual(oldestPerson(aliceBob), oldestPerson(bobAlice))
    assert.deepStrictEqual(oldestPerson(emptyMap), O.none)
    

    我同意fp-ts/ReadonlyMap 中的许多函数效率不高,我认为它们只是围绕原生数据结构的最小“函数式 API”包装器,不能很好地发挥不变性和决定论。如果性能更受关注,我可能会像您一样使用本机方法。

    getFoldableWithIndex 的东西,但我不知道如何使用它或让它与 Typescript 一起编译。

    getFoldableWithIndex 将返回一个FoldableWithIndex 类型的类实例,它在这里对你没有用处。这是一个,您可以将其作为参数传递给一个需要FoldableWithIndex 实例的函数,我什至不确定是否有一个很好的例子?。在fp-ts/Foldable 中有一个traverse_,它采用Foldable 实例,但没有对应的traverseWithIndex_,尽管该假设函数采用FoldableWithIndex 实例。 some functionsfp-ts/FoldableWithIndex 中有 functions,但它们是关于组合两个 FoldableWithIndex 实例到另一个实例(这在这里没有用)。

    你可以直接使用FoldableWithIndex:类型类实例是一个带有uncurried foldMapWithIndex 方法的对象,所以如果你想制作原始的sn-p 更详细的,你可以这样做:

    const oldestPerson = (ps: ReadonlyMap<Person, Age>) =>
      pipe(
        RM.getFoldableWithIndex(S.Ord).foldMapWithIndex(monoidMaxByAge)(
          ps,
          flow(tuple, O.some)
        ),
        O.map(RT.fst)
      )
    

    但这些实例并不是真的要直接在pipe 中使用,这就是我使用顶级RM.foldMapWithIndex 的原因。还有更多关于fp-ts 中的类型类如何工作here

    【讨论】:

      【解决方案2】:

      你可以:

      import { ordNumber } from 'fp-ts/Ord'
      
      type Name = string
      type Age = number
      type Persons = Map<Name, Age>
      const persons: Persons = new Map()
      
      persons.set('Joao', 13)
      persons.set('Roberto', 20)
      
      const sorted = new Map(
        [...persons.entries()].sort((a, b) => ordNumber.compare(b[1], a[1]))
      )
      

      但是,需要注意的是,在此示例中,您通过调用 .set 来改变 persons 变量。

      更纯粹、更实用的方法是:

      import { ordNumber } from 'fp-ts/Ord'
      import { Eq as StringEq } from 'fp-ts/string'
      import * as F from 'fp-ts/function'
      import * as M from 'fp-ts/Map'
      
      type Name = string
      type Age = number
      const insertIntoPersons = M.upsertAt(StringEq)
      
      const persons = F.pipe(
        new Map<Name, Age>(),
        insertIntoPersons('Joao', 13),
        insertIntoPersons('Roberto', 20)
      )
      
      const sorted = new Map(
        [...persons.entries()].sort((a, b) => ordNumber.compare(b[1], a[1]))
      )
      

      【讨论】:

      • 谢谢。但我想知道是否有办法使用 fp-ts 来做到这一点。它有一个 Array.reduce,我正在寻找与 Set 和 Map 类似的东西。
      • @JustinM 嗯,我不知道,不。但我想您可以将Map 转换为Array 并从那里调用reduce?你的最终目标是什么?
      • 在这种情况下,对 Map 和 Set 进行有效的缩减/折叠。要找到最大值,您不需要排序 - 只需循环并跟踪最大值。我发现 fp-ts 不允许这样做很奇怪,特别是对于像“getFoldableWithIndex”和“toUnfoldable”这样的函数,文档使它听起来几乎像我想要的那样。我真的很想了解 fp-ts 的工作原理,而不仅仅是编写代码。
      • 这种作品... pipe(ps, MAP.toUnfoldable(ordString, ARRAY.Unfoldable))。它创建一个 [key,value] 元组数组。仍然需要不必要的排序。还不如跳出 fp-ts 来避免排序。
      • 实际上,fp-ts 允许您使用getFoldableWithIndex 折叠Map,正如您所提到的,以及您为键提供Ord 实例......这很奇怪。你可以在这里查看代码:github.com/gcanti/fp-ts/blob/master/src/Map.ts#L662 老实说,我不知道为什么会这样。我想你可以复制reduceWithIndex 函数并对其进行修改以使键未排序,除非有理由对它们进行排序我不知道
      猜你喜欢
      • 1970-01-01
      • 2013-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多