javascript - Ramda JS 获得最严格地理限制的最佳方式

标签 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
    }
]

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

我想编写返回第一个地址组件的 long_name 的代码,该组件的字段 R.get(R.lensPath('types', '0'))'邻居'(如果存在)locality,否则,则为 postal_townadministrative_area_level_2administrative_area_level_1,然后 国家

所以我从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)]

虽然确实可以通过将所有 native .reduce.map 替换为 R.map 来使这稍微更惯用。 code>/R.reduce 这并没有真正解决根本问题。

1) 即使找到结果后,这也会迭代列表中的每个成员。

2) 生成的结构仍然需要迭代(例如查找)才能真正找到最紧边界。

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

可以将原生 map/reduce 与 ramda 混合搭配吗?当然,只要有可能, native 调用就比库调用更好吗?

最佳答案

一种方法是创建 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>

关于javascript - Ramda JS 获得最严格地理限制的最佳方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44131662/

相关文章:

c# - F# 中的字典推导式(?)(从 C# 转换)

javascript - 混合两个对象数组并使用 ramda 添加属性

javascript - 嵌套的 JSON 数组和 D3JS

javascript - 如何从对象键构建标签

java - 如何使用 Stream API 随机播放流?

javascript - TaskEither sequenceArray 函数处理不同类型返回类型的问题

javascript - 'map' ping multiple lists有名称吗?

ramda.js - lambda : Filtering through arrays with associated value

javascript - jquery 数据表 : how to shift columns mappings (because of inserted "row header column")?

JavaScript:Internet Explorer 中的 document.getElementById() 错误