使用ramdajs将记录数组转换为摘要或数据透视

时间:2019-04-12 15:59:05

标签: ramda.js

[
  {
    "door_id": 324,
    "action": "door open",
    "timestamp": "2018-03-30 10:34:44",
    "date": "2018-03-30"
  },
  {
    "door_id": 324,
    "action": "door close",
    "timestamp": "2018-03-30 10:39:44",
    "date": "2018-03-30"
  },
  {
    "door_id": 324,
    "action": "door open",
    "timestamp": "2018-03-30 10:59:44",
    "date": "2018-03-30"
  },
  {
    "door_id": 325,
    "action": "door open",
    "timestamp": "2018-03-31 14:59:44",
    "date": "2018-03-31"
  },
  {
    "door_id": 325,
    "action": "door close",
    "timestamp": "2018-03-31 15:00:44",
    "date": "2018-03-31"
  }
]

我正在尝试使用ramda.js将对象数组转换为预期的格式。

打开和关闭操作将始终进行顺序执行,但不一定要完整设置(例如,开门有日志,而没有日志关门(因为门是开着的)

我更喜欢使用映射器方法/部分功能的逐步操作。

const expected = [
  {
    "door_id": 324,
    "date": "2018-03-30",
    "status" : "Open",
    "actions_set_count": 2,
    "actions": [
      {
        "open": "2018-03-30 10:34:44",
        "close": "2018-03-30 10:39:44",
        "duration": 300
      },
      {
        "open": "2018-03-30 10:59:44",
        "close": null,
        "duration": null
      }
    ]
  },
  {
    "door_id": 325,
    "date": "2018-03-31",
    "status" : "Closed",
    "actions_set_count": 1,
    "actions": [
      {
        "open": "2018-03-30 14:59:44",
        "close": "2018-03-30 15:00:44",
        "duration": 60
      }
    ]
  }
]

到目前为止我做了什么,但还远远没有完成

const isOpen = R.propEq('action','door open')
const isClosed = R.propEq('action','door close')


R.pipe(
  R.groupBy(R.prop('date')),
  R.map(R.applySpec({
    "date": R.pipe(R.head(), R.prop('date')),
    "door_id": R.pipe(R.head(), R.prop('door_id')),
    "open" : R.filter(isOpen),
    "close" : R.filter(isClosed),
    "sets": R.zip(R.filter(isOpen),R.filter(isClosed))
  })),
)(logs)

1 个答案:

答案 0 :(得分:1)

在这样的转换中,当我想到优雅的内容时,我会退回到reduce上。使用groupBy(如果需要,可以使用sortBy)和values,我们可以按一定的顺序将数据放在一起,这样我们就可以做一个简单的(如果有点乏味的)减少在上面。

const duration = (earlier, later) => 
  (new Date(later) - new Date(earlier)) / 1000

const transform = pipe(
  groupBy(prop('door_id')),
  map(sortBy(prop('timestamp'))), // Perhaps unnecessary, if data is already sorted
  values,
  map(reduce((
    {actions, actions_set_count}, 
    {door_id, action, timestamp, date}
  ) => ({
    door_id, 
    date, 
    ...(action == "door open" 
      ? {
          status: 'Open',
          actions_set_count: actions_set_count + 1,
          actions: actions.concat({
            open: timestamp, 
            close: null, 
            duration: null
          })
        }
      : {
          status: 'Closed',
          actions_set_count,
          actions: [
            ...init(actions), 
            {
              ...last(actions), 
              close: timestamp, 
              duration: duration(last(actions).open, timestamp)
            }
          ]
        }
    )
  }), {actions: [], actions_set_count: 0}))
)

const doors = [
  {door_id: 324, action: "door open",  timestamp: "2018-03-30 10:34:44", date: "2018-03-30"},
  {door_id: 324, action: "door close", timestamp: "2018-03-30 10:39:44", date: "2018-03-30"},
  {door_id: 324, action: "door open",  timestamp: "2018-03-30 10:59:44", date: "2018-03-30"},
  {door_id: 325, action: "door open",  timestamp: "2018-03-31 14:59:44", date: "2018-03-31"},
  {door_id: 325, action: "door close", timestamp: "2018-03-31 15:00:44", date: "2018-03-31"}
]

console.log(transform(doors))
<script src="https://bundle.run/ramda@0.26.1"></script><script>
const {pipe, groupBy, prop, map, sortBy, values, reduce, init, last} = ramda    </script>

还有其他方法可以解决此问题。我的第一个想法是使用splitEvery(2)将它们配对成开闭对,然后生成动作。麻烦的是,我们仍然需要实际的原始数据来填充其余的数据(door_iddate等),因此我最终得到了reduce

显然,这远非优雅。部分原因在于基础转换不是特别优雅(为什么actions_set_count字段只是actions的长度?),也不是数据(为什么date < em>和 timestamp字段?)但是我怀疑我也错过了一些可以实现更好实现的东西。我很想听听它们是什么。

请注意,我选择使用最后一个date字段,而不是最初的字段。有时,在reduce调用中更容易做到这一点,听起来似乎并不重要。