1

我正在尝试使用 Google Places API 来获取我所在位置的地名。

返回的数据结构有以下类型:

descriptor1: 'street number' | 'neighborhood' | 'postcode' | 'route' | 'locality' | 'postal_town' | 'administrative_area_level_2' | 'administrative_area_level_1' | 'country'

places: [
  {
    address_components: [{
      long_name: 'string',
      short_name: 'string',
      types: {
        0: descriptor1,
        1?: descriptor2
      }
    }],
    other_fields not relevant here
    }
]

无法保证任何给定地点将拥有多少地址组件,或者是否有任何地址组件。无法保证哪些类型会被表示,哪些不会被表示。

我想编写返回第一个 address_component 的 long_name 的代码,该地址的字段R.get(R.lensPath('types', '0'))'neighborhood'if one 存在locality,否则,然后postal_townadministrative_area_level_2然后administrative_area_level_1,然后country

所以我从R.pluck('address_components', places). 现在我可以构造一个对象,将列表缩减为一个对象,将我感兴趣的每个键中的第一个插入到对象中,然后找到一个值。就像是:

const interestingTypes = ['neighborhood', 'locality', 'postal_town', 'administrative_area 2', 'administrative_area_1', 'country']
const res = R.mergeAll(R.pluck('address_components', places).map((addressComponentList) => addressComponentList.reduce((memo, addressComponent) => {
     if (interestingTypes.indexOf(addressComponent.types[0]) !== -1) {
       if (!memo[addressComponent.types[0]]) {
         memo[addressComponent.types[0]] = addressComponent.long_name
       }  
     }
     return memo
   },{})))
res[R.find((type) => (Object.keys(res).indexOf(type) !== -1), interestingTypes)]

虽然可以肯定的是,这可以稍微更惯用地替换所有本机.reduce.mapwith R.map/R.reduce这并不能真正解决根本问题。

1) 即使在找到结果之后,这也会遍历列表的每个成员。

2)生成的结构仍然需要迭代(例如使用 find )才能真正找到最紧密的界限。

一个纯函数式的,最好是惰性的实现会是什么样子?Ramda 的哪些功能可以派上用场?我可以以某种方式使用镜头吗?功能构成?还有什么?

map可以将本机/reduce与ramda混合和匹配吗?在可能的情况下,本机调用肯定比库调用更好吗?

4

2 回答 2

1

一种方法是创建一个惰性版本R.reduceRight

const lazyReduceR = R.curry((fn, acc, list) => {
  function _lazyReduceR(i) {
    return i === list.length
      ? acc
      : fn(list[i], () => _lazyFoldR(i + 1))
  }
  return _lazyReduceR(0)
})

然后可以使用它来创建一个函数,该函数将找到(非空)列表的最小元素,具有已知的下限:

const boundMinBy = R.curry((byFn, lowerBound, list) =>
  lazyReduceR((x, lzMin) => {
    if (byFn(x) === lowerBound) {
      return x;
    } else {
      const min = lzMin()
      return byFn(x) < byFn(min) ? x : min
    }
  }, list[0], R.tail(list)))

如果遇到下限,则递归停止并立即返回该结果。

boundMinBy如果可用,我们可以创建地址类型的查找表来对顺序值进行排序:

const sortOrder = {
  neighborhood: 0,
  locality: 1,
  postal_town: 2,
  administrative_area_level_2: 3,
  administrative_area_level_1: 4,
  country: 5
}

连同将为给定地址组件生成排序顺序值的函数:

const sortOrderOfAddress = address => sortOrder[address.types[0]]

然后我们可以用一个管道完全组合它,例如:

const process = R.pipe(
  R.prop('places'),
  R.chain(R.pipe(
    R.prop('address_components'),
    R.unless(
      R.isEmpty,
      R.pipe(
        boundMinBy(sortOrderOfAddress, 0),
        R.prop('long_name'),
        R.of
      )
    )
  ))
)

R.chain上面用于连接所有地点的地址并过滤掉任何address_components为空的地点的地址。

如果您想用一些数据对其进行测试,我在下面的代码段中包含了一个示例。

const lazyReduceR = R.curry((fn, acc, list) => {
  function _lazyReduceR(i) {
    return i === list.length
      ? acc
      : fn(list[i], () => _lazyReduceR(i + 1))
  }
  return _lazyReduceR(0)
})

const boundMinBy = R.curry((byFn, lowerBound, list) =>
  lazyReduceR((x, lzMin) => {
    if (byFn(x) === lowerBound) {
      return x;
    } else {
      const min = lzMin()
      return byFn(x) < byFn(min) ? x : min
    }
  }, list[0], R.tail(list)))

const sortOrder = {
  neighborhood: 0,
  locality: 1,
  postal_town: 2,
  administrative_area_level_2: 3,
  administrative_area_level_1: 4,
  country: 5
}

const sortOrderOfAddress = address => sortOrder[address.types[0]]

const process = R.pipe(
  R.prop('places'),
  R.chain(R.pipe(
    R.prop('address_components'),
    R.unless(
      R.isEmpty,
      R.pipe(
        boundMinBy(sortOrderOfAddress, 0),
        R.prop('long_name'),
        R.of
      )
    )
  ))
)

////

const data = {
  places: [{
    address_components: [{
      long_name: 'a',
      types: ['country']
    }, {
      long_name: 'b',
      types: ['neighborhood']
    }, {
      long_name: 'c',
      types: ['postal_town']
    }]
  }, {
    address_components: [{
      long_name: 'd',
      types: ['country']
    }, {
      long_name: 'e',
      types: ['locality']
    }, {
      long_name: 'f',
      types: ['administrative_area_level_2']
    }]
  }]
}

console.log(process(data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.23.0/ramda.min.js"></script>

于 2017-05-23T14:33:43.550 回答
0

可以将本机 map/reduce 与 ramda 混合和匹配吗?

绝对地。但是您还必须考虑将它们结合起来所涉及的认知成本。

在可能的情况下,本机调用肯定比库调用更好吗?

更好怎么做?库函数旨在避免原生函数规范中的一些不幸的复杂性。

此外,当编写 Ramda 的核心函数时,它们的性能明显优于其原生对应物。随着原生引擎的进步以及 Ramda 的功能变得更加复杂,这种情况可能已经改变。但也很有可能没有。

于 2017-05-24T12:15:44.350 回答