0

I have an index mapping like so:

var idxMapping = {
    abc: 1,
    foo: 2,
    bar: 0
};

And an array of objects :

var data = [{
    id: 'abc',
    info1: 'random info 1',
    info2: 'random info 2',
}, {
    id: 'foo',
    info1: 'random info 1',
    info2: 'random info 2',
}]

Using functional programming paradigms, how can I create a new array so that the data gets transformed according to the index specified in the idxMapping.

The result should look something like:

[
null, 
{ 
    id: 'abc',
    info1: 'random abc info 1',
    info2: 'random abc info 2'
},
{ 
    id: 'foo',
    info1: 'random foo info 1',
    info2: 'random foo info 2'
}
]

The first element is null because the data array does not contain any data that mapped to index 0 (i.e No bar Object in this example).

This is how I implemented using forEach loops :

var result = [];

for (var key in idxMapping) {
    result.append(null);
}

data.forEach(function(item) {
    result[idxMapping[item.id]] = item;
});

I need a solution strictly following functional programming paradigms.

4
  • Show what you're trying to do, and if you know how to do that with loops, show that as well. Commented Oct 9, 2016 at 19:14
  • 1
    Do you realise that the answers you got are much worse than your existing code (in terms of both readability and efficiency)? Commented Oct 9, 2016 at 20:15
  • @georg I was getting started with FP and wanted to refactor my existing project to be as functional as possible. However, I'm still a bit uncertain about when FP is an overkill. The question is a stripped down part of my actual problem. I need this part of code inside another two level nested map function. I am a beginner in FP and probably will be prone to over using FP. Can you please give some pointers where I should choose imperative over functional programming? Commented Oct 9, 2016 at 20:28
  • @jgr0: JS built-in "functional tools" are quite poor, I'd recommend to study ramda if you want to "taste" real functional programming. In the meantime, just use the simplest thing that works - in this case, a simple loop will do. Commented Oct 10, 2016 at 9:43

3 Answers 3

4

There is one of possible approaches in functional programming style:

var idxMapping = {
    abc: 1,
    foo: 2,
    bar: 0
};
var data = [{
    id: 'abc',
    info1: 'random info 1',
    info2: 'random info 2',
}, {
    id: 'foo',
    info1: 'random info 1',
    info2: 'random info 2',
}]

Object.keys(idxMapping)
.sort((fstKey, scndKey) => idxMapping[fstKey] - idxMapping[scndKey])
.map(key => data.find(el => el.id === key))
.map(el => !!el ? el : null)

or

Object.keys(idxMapping)
.sort((fstKey, scndKey) => idxMapping[fstKey] - idxMapping[scndKey])
.map(key => { var obj = data.find(el => el.id === key); return !!obj ? obj : null })
Sign up to request clarification or add additional context in comments.

2 Comments

That's close, but not accurate. Did you try?
I think you actually just need to sort afterwards based on idxMapping
2

It could be done like this:

var result = data.reduce ( 
    (acc, obj) => Object.assign(acc, { [idxMapping[obj.id]]: obj }), 
    Object.keys(idxMapping).map( _ => null) );

var idxMapping = {
    abc: 1,
    foo: 2,
    bar: 0
};

var data = [{
    id: 'abc',
    info1: 'random info 1',
    info2: 'random info 2',
}, {
    id: 'foo',
    info1: 'random info 1',
    info2: 'random info 2',
}];

var result = data.reduce ( 
    (acc, obj) => Object.assign(acc, { [idxMapping[obj.id]]: obj }), 
    Object.keys(idxMapping).map( _ => null) );

console.log(result);

This assumes that the indices mentioned in idxMapping are in the range 0..n-1, where n is the number of elements in that array.

If it were OK to have undefined instead of null in the result array, then the Object.keys() part could be replaced by just [].

Explanation

First an array is created with as many null entries as there are keys in idxMapping:

Object.keys(idxMapping).map( _ => null)

This becomes the initial accumulator value for reduce, which then iterates over each element of data. In each iteration the corresponding index is retrieved from idxMapping:

idxMapping[obj.id]

With the ES6 syntax for computed property names, this value (a number) is used as a key of the (accumulator) array object (which really is an index of the array, but indexes of an array are essentially object properties):

[idxMapping[obj.id]]

... and the data element is assigned to it within an object literal:

{ [idxMapping[obj.id]]: obj }

For example, the above may resolve to this:

{ '1': obj }

...and further:

{ '1': {
    id: 'abc',
    info1: 'random info 1',
    info2: 'random info 2',
} }

This object is then merged with the already existing accumulator object, using Object.assign:

Object.assign(acc, { [idxMapping[obj.id]]: obj })

... which comes down to setting one of the entries of the (accumulating) result array to the correct value. Continuing the above example, you actually get this assignment:

acc[1] = {
    id: 'abc',
    info1: 'random info 1',
    info2: 'random info 2',
}

This array is also the return value of Object.assign, which is nice, as reduce expects the inner callback function to return the new value of the accumulator, and passes it in the next iteration back to the callback function.

At the last iteration this return value becomes the return value of reduce itself, and thus the value of result.

3 Comments

Can you please explain how { [idxMapping[obj.id]]: obj } part works?
I added some explanations to my answer. Hope it clarifies things for you.
Thanks for the clarification! Was confused with the ES6 computed property name syntax.
0

I might do it with a single liner as follows;

var idxMapping = {
                  abc: 1,
                  foo: 2,
                  bar: 0
                 },
          data = [{
          	       id: 'abc',
                info1: 'random info 1',
                info2: 'random info 2',
                  },
                  {
                   id: 'foo',
                info1: 'random info 1',
                info2: 'random info 2',
                  }],
        result = data.reduce((p,c) => (p[idxMapping[c.id]]=c,p),Array(Math.max(...Object.values(idxMapping))+1).fill(null));
console.log(result);

Object.values() should be OK with FF v50 and Chrone v53

The initial part of reduce Array(Math.max(...Object.values(idxMapping))+1).fill(null) just creates an array full of nulls in the size of max value in idxMapping + 1 and the rest is just reducing.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.