4

given:

interface Dict {
  [key: string]: any
}

const data: Dict[] = [
  { id: 'a' },
  { id: 'b', b: 'something' },
  { id: 'c', b: 'else' },  
  { id: 'd', extra: 'hello world' },
  { id: 'e' },  
];

where the keys of these Dict objects aren't specified...

How can I get this result?

const result = {
  id: ['a', 'b', 'c', 'd', 'e'],
  b: ['something', 'else'],
  extra: ['hello world'],
  // ... and any other possible key
}
Hitmands
  • 13,491
  • 4
  • 34
  • 69
  • Is there any reason for using `ramda` here? I mean, would you accept other solutions without ramda? This can be done in a single reduce loop: https://jsfiddle.net/go6fabnu/ – briosheje Jun 18 '19 at 10:17
  • Ramda is because we want a functional solution, you can assume we are able to imperatively solve this task. Your solution is not *done in a single loop*, this problem's complexity cannot be less than squared... – Hitmands Jun 18 '19 at 10:47
  • 1
    @Hitmands: Note that a minor variation of the answer from briosheje is as functional as any of the Ramda solutions, just replacing the `forEach` with an appropriate second `reduce`. I do prefer the more elegant solution from georg, but Ramda is not the only way to write functional code. – Scott Sauyet Jun 18 '19 at 11:25
  • By *functional* I meant the *declarative fashion*, with ramda you can solve this problem in a point free manner... as opposed, the @briosheje's required a lot of procedural operations. – Hitmands Jun 18 '19 at 11:28
  • @Hitmands okay, I was just wondering "why" such approach was needed when using reduce was barely enough, I was just curious about that :) – briosheje Jun 18 '19 at 11:41

3 Answers3

4

You can flatten the object into a list of pairs, group it, and convert the pairs back to values:

const data = [
  { id: 'a' },
  { id: 'b', b: 'something' },
  { id: 'c', b: 'else' },  
  { id: 'd', extra: 'hello world' },
  { id: 'e' },  
];


let z = R.pipe(
  R.chain(R.toPairs),
  R.groupBy(R.head),
  R.map(R.map(R.last))
)

console.log(z(data))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
georg
  • 211,518
  • 52
  • 313
  • 390
3

Slight variation from Ori Drori answer: (assuming that no properties in your objects are contained in arrays already)

const data = [
  { id: 'a' },
  { id: 'b', b: 'something' },
  { id: 'c', b: 'else' },  
  { id: 'd', extra: 'hello world' },
  { id: 'e' }
];

const run = reduce(useWith(mergeWith(concat), [identity, map(of)]), {});

console.log(
  run(data)
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {reduce, useWith, mergeWith, concat, identity, map, of} = R;</script>
customcommander
  • 17,580
  • 5
  • 58
  • 84
2

Use R.reduce with R.mergeWith and concat all items:

const { mergeWith, reduce } = R

const fn = reduce(mergeWith((a, b) => [].concat(a, b)), {})


const data = [
  { id: 'a' },
  { id: 'b', b: 'something' },
  { id: 'c', b: 'else' },  
  { id: 'd', extra: 'hello world' },
  { id: 'e' },  
];

const result = fn(data)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

If you need the single value (extra) in array as well, map the items, and wrap with an array, just the values that are not an array already:

const { pipe, mergeWith, reduce, map, unless, is, of } = R

const fn = pipe(
  reduce(mergeWith((a, b) => [].concat(a, b)), {}),
  map(unless(is(Array), of))
)


const data = [
  { id: 'a' },
  { id: 'b', b: 'something' },
  { id: 'c', b: 'else' },  
  { id: 'd', extra: 'hello world' },
  { id: 'e' },
];

const result = fn(data)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Ori Drori
  • 183,571
  • 29
  • 224
  • 209