5

I have a collection of UNIX timestamps that looks like this:

[
  {"start_time":1540458000000, "end_time":1540472400000}, 
  {"start_time":1540458000000, "end_time":1540486800000}, 
  {"start_time":1540458000000, "end_time":1540501200000}, 
  {"start_time":1540472400000, "end_time":1540486800000}, 
  {"start_time":1540472400000, "end_time":1540501200000}, 
  {"start_time":1540486800000, "end_time":1540501200000}
]

I’d like to pick out all the unique values from both start_time and end_time, so I’m left with:

[
  {"start_time":1540458000000}, 
  {"start_time":1540472400000}, 
  {"start_time":1540486800000}
  {"end_time":1540472400000},
  {"end_time":1540486800000}, 
  {"end_time":1540501200000}, 
]

I’ve looked at using something similar using groupBy, pluck, zipObj and more using the answer here. But with no luck unfortunately.

A nice-to-have would be a ramda function that worked without having to be given specific keys.

gosseti
  • 965
  • 16
  • 40
  • So, what is the logic for getting that output? Is it "get all distinct `start_time` values and all distinct `end_time` values and then put them in an array sorted by value"? – VLAZ Oct 19 '18 at 16:27
  • Something like this? https://ramdajs.com/docs/#union – joshbang Oct 19 '18 at 16:27
  • @vlaz That’s right. – gosseti Oct 19 '18 at 16:33
  • @joshbang It looks like `union` and `unionWith` only work when supplied with two lists, whereas here I’m supplying only one. – gosseti Oct 19 '18 at 16:33
  • You could split them out though by key with a for in loop. Create two arrays called startTimes and endTimes. Then use the union with two new arrays. – joshbang Oct 19 '18 at 16:34

3 Answers3

5

Another Ramda approach:

const {pipe, map, toPairs, unnest, groupBy, head, uniqBy, last, values, apply, objOf} = R

const uniqTimes = pipe(
  map(toPairs),          //=> [[['start', 1], ['end', 2]], [['start', 1], ['end', 3]], ...]
  unnest,                //=> [['start', 1], ['end', 2], ['start', 1], ['end', 3], ...]
  groupBy(head),         //=> {start: [['start', 1], ['start', 1], ['start', 4], ...], end: [['end', 2], ...]}
  map(uniqBy(last)),     //=> {start: [['start', 1], ['start', 4], ...], end: [['end', 2], ...]}
  values,                //=> [[['start', 1], ['start', 4], ...], [['end', 2], ...]]
  unnest,                //=> [['start', 1], ['start', 4], ..., ['end', 2], ...]
  map(apply(objOf))      //=> [{"start": 1}, {"start": 4}, ..., {"end": 2}, ...]
)

const timestamps = [{"start_time":1540458000000,"end_time":1540472400000},{"start_time":1540458000000,"end_time":1540486800000},{"start_time":1540458000000,"end_time":1540501200000},{"start_time":1540472400000,"end_time":1540486800000},{"start_time":1540472400000,"end_time":1540501200000},{"start_time":1540486800000,"end_time":1540501200000}]

console.log(uniqTimes(timestamps))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>

This is how I like to use Ramda: build a pipeline of functions that each do simple transformations.


Update

A comment asked how to generate an output more like

{
   "start_time": [1540458000000, 1540458000000],
   "end_time": [1540472400000, 1540486800000]
}

This should do that for the same input:

const uniqTimes = pipe(
  map(toPairs), 
  unnest,
  groupBy(head),
  map(map(last)),
  map(uniq)
)
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • This works perfectly, thanks as always Scott. One further question… how might I group all the values so it becomes: `[{"start_time":[1540458000000, 1540458000000],"end_time":[1540472400000, 1540486800000]}]` etc.? – gosseti Oct 19 '18 at 18:16
  • I'm sure there's a simpler way to get that, but replacing that `map(apply(objOf))` with `groupBy(head), map(map(last))` should do it. But if that were my goal, I would probably have an entirely different pipeline. – Scott Sauyet Oct 19 '18 at 18:30
  • If that last is your goal, something like this might be cleaner: `pipe(map(toPairs), unnest, groupBy(head), map(map(last)), map(uniq))`. – Scott Sauyet Oct 19 '18 at 19:27
2

Not sure about ramda, but below plain js function will do that

const arr = [
  {"start_time":1540458000000, "end_time":1540472400000}, 
  {"start_time":1540458000000, "end_time":1540486800000}, 
  {"start_time":1540458000000, "end_time":1540501200000}, 
  {"start_time":1540472400000, "end_time":1540486800000}, 
  {"start_time":1540472400000, "end_time":1540501200000}, 
  {"start_time":1540486800000, "end_time":1540501200000}
];

function foo(arr) {
  return [...arr.reduce((a, b) => {
    Object.entries(b).forEach(e => a.set(String(e), e));
    return a;
  }, new Map())].map(([_,e]) => ({
   [e[0]]: e[1]
  }))
}

console.log(foo(arr));
baao
  • 71,625
  • 17
  • 143
  • 203
2

If the properties you want are unknown, but appear in all objects, you can convert each object to pairs, transpose the resulting array, get the unique values of each array, unnest them to a single array, and then convert back to objects:

const { pipe, map, toPairs, transpose, uniqBy, last, unnest, objOf, apply } = R;

const data = [
  {"start_time":1540458000000, "end_time":1540472400000}, 
  {"start_time":1540458000000, "end_time":1540486800000}, 
  {"start_time":1540458000000, "end_time":1540501200000}, 
  {"start_time":1540472400000, "end_time":1540486800000}, 
  {"start_time":1540472400000, "end_time":1540501200000}, 
  {"start_time":1540486800000, "end_time":1540501200000}
];

const getUniqueProps =
  pipe(
    map(toPairs),
    transpose,
    map(uniqBy(last)),
    unnest,
    map(apply(objOf))
  );
  
console.log(getUniqueProps(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

If you know the properties you want, you can groupBy a property, get the first object from each group, and then pick the property you want from each object:

const { groupBy, prop, concat, pipe, map, head, pick, values } = R;

const data = [
  {"start_time":1540458000000, "end_time":1540472400000}, 
  {"start_time":1540458000000, "end_time":1540486800000}, 
  {"start_time":1540458000000, "end_time":1540501200000}, 
  {"start_time":1540472400000, "end_time":1540486800000}, 
  {"start_time":1540472400000, "end_time":1540501200000}, 
  {"start_time":1540486800000, "end_time":1540501200000}
];

const getUniqueProp = (propName) =>
  pipe(
    groupBy(prop(propName)),
    map(pipe(head, pick([propName]))),
    values,
  );
  
const getStartEnd = (data) => concat(
  getUniqueProp('start_time')(data),
  getUniqueProp('end_time')(data),
);

console.log(getStartEnd(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • This works great! How would you adapt it to work regardless of property names? – gosseti Oct 19 '18 at 17:45
  • The `getUniqueProp` method is agnostic to property names, so you can decide which props to take. However, if you want to iterate an object, and collect all properties, I'm not sure this is the solution. – Ori Drori Oct 19 '18 at 17:47
  • 1
    This is quite nice. I believe the `map(apply(objOf))` in my solution is slightly more appropriate than your `map(pipe(of, fromPairs))`, mostly because `fromPairs` is designed for multi-property output, whereas `objOf` is specific to creating a single-property object. I didn't think of `transpose`; that's a useful simplification from my solution. But it does have an issue. If you switch property order in some of the objects, you get different behavior. However that may not be a real-world problem for the OP. – Scott Sauyet Oct 19 '18 at 18:41
  • Thanks. I was breaking my head about how to convert a single pair to an object :) – Ori Drori Oct 19 '18 at 18:46