Ramda JS获得最严格地理界限的最佳方式

时间:2017-05-23 10:01:49

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

我正在尝试使用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',则locality的字段为postal_town,然后administrative_area_level_2,{ {1}},然后是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 / .map替换所有原生R.mapR.reduce,但实际上并未解决这个问题。根本问题。

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

2)生成的结构仍然需要迭代(例如使用find)来实际找到最严格的边界。

这个纯粹的功能,最好是懒惰的实现是什么样的? Ramda的哪些功能可以派上用场?我可以用某种方式使用镜头吗?功能构成?还有别的吗?

将原生的map / reduce与ramda混合搭配是否可以?当然,本地调用尽可能比库调用更好吗?

2 个答案:

答案 0 :(得分: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为空的地方的任何地址。

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

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

答案 1 :(得分:0)

  

将原生地图/简化与ramda混合搭配是否可以?

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

  

当然,本地调用尽可能比库调用更好?

更好的方法?库函数旨在避免本机函数规范中的一些不幸的复杂性。

此外,当Ramda的核心功能被编写时,它们的性能明显优于本地同行。随着本机引擎的进步以及Ramda的功能变得越来越复杂,这可能已经发生了变化。但它也很可能没有。