使用 ramdajs 将记录数组转换为摘要或数据透视表
Transform array of records to summary or pivot using ramdajs
[
{
"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 将此对象数组转换为预期格式。
打开和关闭动作将总是按顺序出现,但不一定是完整的一组(例如开门有一个日志, 但没有 关门 的日志,因为门是开着的)
我更喜欢逐步使用映射器 approach/partial 函数。
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)
在这样的转型中,当我想不出优雅的东西时,我会求助于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_id
、date
等)所以我最终得到了 reduce
.
显然这远非优雅。部分原因是底层转换不是特别优雅(为什么 actions_set_count
字段只是 actions
的长度?),数据也不是(为什么 date
and timestamp
字段?)但我怀疑我也错过了一些可以实现更好实现的东西。我很想听听那些是什么。
请注意,我选择使用最后一个 date
字段而不是初始字段。有时这在 reduce
调用中更容易做到,而且听起来好像这还不重要。
[
{
"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 将此对象数组转换为预期格式。
打开和关闭动作将总是按顺序出现,但不一定是完整的一组(例如开门有一个日志, 但没有 关门 的日志,因为门是开着的)
我更喜欢逐步使用映射器 approach/partial 函数。
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)
在这样的转型中,当我想不出优雅的东西时,我会求助于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_id
、date
等)所以我最终得到了 reduce
.
显然这远非优雅。部分原因是底层转换不是特别优雅(为什么 actions_set_count
字段只是 actions
的长度?),数据也不是(为什么 date
and timestamp
字段?)但我怀疑我也错过了一些可以实现更好实现的东西。我很想听听那些是什么。
请注意,我选择使用最后一个 date
字段而不是初始字段。有时这在 reduce
调用中更容易做到,而且听起来好像这还不重要。