【问题标题】:Ramda JS best way to get the tightest geographic boundRamda JS 获得最紧密地理界限的最佳方式
【发布时间】:2017-10-23 05:18:04
【问题描述】:

我正在尝试使用 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
    }
]

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

我想编写代码,返回第一个地址组件的长名称,该组件具有'neighborhood' 的字段R.get(R.lensPath('types', '0')),如果存在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)]

虽然用R.map/R.reduce 替换所有本机.reduce.map 确实可以稍微更惯用,但这并不能真正解决根本问题。

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

2) 生成的结构 still 需要迭代(以 find 为例)才能真正找到最紧密的界限。

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

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

【问题讨论】:

    标签: javascript functional-programming ramda.js list-processing


    【解决方案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为空的任何地点的地址。

    如果您想用一些数据进行测试,我在下面的 sn-p 中包含了一个示例。

    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))
    &lt;script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.23.0/ramda.min.js"&gt;&lt;/script&gt;

    【讨论】:

    • 非常好。请注意,如果您愿意,sortOrder 可以创建为 invertObj(['neighborhood', 'locality', /*, ... */ 'country']
    【解决方案2】:

    可以将原生 map/reduce 与 ramda 混合搭配吗?

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

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

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

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

    【讨论】:

      猜你喜欢
      • 2023-02-25
      • 1970-01-01
      • 2011-11-15
      • 1970-01-01
      • 1970-01-01
      • 2021-03-17
      • 2019-02-26
      • 2010-10-29
      • 1970-01-01
      相关资源
      最近更新 更多