I have a system, where I would like to show a helpful message to a user when they perform certain actions. However, depending on the user, we might choose not to show the message if we know they are already familiar with the particular action.
I have two multidimensional arrays, which I need to pare, to determine whether or not the message should be shown. One containing user classification, and one containing all the messages.
My issue is, that the coding style heavily prefers using Lodash helpers when possible, and I am not too versed with it.
The code below does what I want, but it's obviously not pretty or performant.
// user classification for each type of action
const classifications = [
{
status: 'basic',
actionTypes: [
'copy', 'paste'
],
},
{
status: 'basic',
actionTypes: [
'buy', 'sell'
],
},
{
status: 'professional',
actionTypes: [
'drag', 'drop'
],
}
];
// array of messages for each set of actions
const messages = [
{
message: 'foo',
actionTypes: [
'copy', 'paste'
],
},
{
message: 'bar',
actionTypes: [
'buy', 'sell'
],
},
{
message: 'baz',
actionTypes: [
'drag', 'drop'
],
}
];
// returns only messages for which the user is not professional
function getMappedMessages() {
const basicClassifications = _.filter(classifications, ['status', 'basic']);
const filteredMessages = [];
for (let i = 0; i < basicClassifications.length; i++) {
for (let j = 0; j < basicClassifications[i].actionTypes.length; j++) {
for (let k = 0; k < messages.length; k++) {
if (_.includes(messages[k].actionTypes, basicClassifications[i].actionTypes[j])) {
filteredMessages.push(messages[k]);
break;
}
}
}
}
return filteredMessages;
}
console.log(getMappedMessages());
<script src=".min.js"></script>
I have a system, where I would like to show a helpful message to a user when they perform certain actions. However, depending on the user, we might choose not to show the message if we know they are already familiar with the particular action.
I have two multidimensional arrays, which I need to pare, to determine whether or not the message should be shown. One containing user classification, and one containing all the messages.
My issue is, that the coding style heavily prefers using Lodash helpers when possible, and I am not too versed with it.
The code below does what I want, but it's obviously not pretty or performant.
// user classification for each type of action
const classifications = [
{
status: 'basic',
actionTypes: [
'copy', 'paste'
],
},
{
status: 'basic',
actionTypes: [
'buy', 'sell'
],
},
{
status: 'professional',
actionTypes: [
'drag', 'drop'
],
}
];
// array of messages for each set of actions
const messages = [
{
message: 'foo',
actionTypes: [
'copy', 'paste'
],
},
{
message: 'bar',
actionTypes: [
'buy', 'sell'
],
},
{
message: 'baz',
actionTypes: [
'drag', 'drop'
],
}
];
// returns only messages for which the user is not professional
function getMappedMessages() {
const basicClassifications = _.filter(classifications, ['status', 'basic']);
const filteredMessages = [];
for (let i = 0; i < basicClassifications.length; i++) {
for (let j = 0; j < basicClassifications[i].actionTypes.length; j++) {
for (let k = 0; k < messages.length; k++) {
if (_.includes(messages[k].actionTypes, basicClassifications[i].actionTypes[j])) {
filteredMessages.push(messages[k]);
break;
}
}
}
}
return filteredMessages;
}
console.log(getMappedMessages());
<script src="https://cdn.jsdelivr/lodash/4/lodash.min.js"></script>
(Also available on JSBin)
As you can see, each set of actions (e.g. "copy" and "paste") have a message, and the user has a corresponding action set together with a classification. If the classification is not professional, I am matching the two inner arrays (for each object in the outer arrays), and return the objects containing only the "basic" messages.
It is likely that the two arrays are the same (so I can do a direct parison, maybe with _.difference
?), but I am not certain.
How do I make this monstrosity more readable and performant? Should I do some flattening?
The data is ing from two separate endpoint, and I can not modify the structure.
Share Improve this question edited Jun 20, 2017 at 0:28 styfle 24.7k30 gold badges92 silver badges139 bronze badges asked Jun 19, 2017 at 23:51 NixNix 6,0284 gold badges33 silver badges53 bronze badges4 Answers
Reset to default 4_.isEqual()
will do what you want.
As for cleaning it up, I'd remend either flattening it out and/or break it into functions so you can at least understand better what is going on.
You can index all the classification by their actionTypes
as keys using lodash#keyBy
. Use the indexed classification as a way to use lodash#filter
to retain messages that do not belong to the professional
status.
// index all classifications by their actionTypes
var indexes = _.keyBy(classifications, 'actionTypes');
// get all non-profiessional messages
var nonProMessages = _.filter(messages, function(message) {
// path to get the status matching the
// actionType of this message
var key = [message.actionTypes, 'status'];
// pare the status represented by the message
return _.get(indexes, key) !== 'professional';
});
// user classification for each type of action
const classifications = [{
status: 'basic',
actionTypes: [
'copy', 'paste'
],
},
{
status: 'basic',
actionTypes: [
'buy', 'sell'
],
},
{
status: 'professional',
actionTypes: [
'drag', 'drop'
],
}
];
// array of messages for each set of actions
const messages = [{
message: 'foo',
actionTypes: [
'copy', 'paste'
],
},
{
message: 'bar',
actionTypes: [
'buy', 'sell'
],
},
{
message: 'baz',
actionTypes: [
'drag', 'drop'
],
}
];
// index all classifications by their actionTypes
var indexes = _.keyBy(classifications, 'actionTypes');
// get all non-profiessional messages
var nonProMessages = _.filter(messages, function(message) {
// path to get the status matching the
// actionType of this message
var key = [message.actionTypes, 'status'];
// pare the status represented by the message
return _.get(indexes, key) !== 'professional';
});
console.log(nonProMessages);
body > div { min-height: 100%; top: 0; }
<script src="https://cdnjs.cloudflare./ajax/libs/lodash.js/4.17.4/lodash.js"></script>
There are lots of ways to improve the code. The first thing I did was perform a filter, map, reduce.
- filter: shortens the array to only the elements we need
- map: transforms each object in the array to be only ActionTypes
- reduce: concatenates all the ActionTypes into a single array
const classifications = [{
status: 'basic',
actionTypes: [ 'copy', 'paste'],
},
{
status: 'basic',
actionTypes: [ 'buy', 'sell' ],
},
{
status: 'professional',
actionTypes: [ 'drag', 'drop' ],
}
];
const messages = [{
message: 'foo',
actionTypes: [ 'copy', 'paste' ],
},
{
message: 'bar',
actionTypes: ['buy', 'sell'],
},
{
message: 'baz',
actionTypes: [ 'drag', 'drop' ],
}
];
function getBasicActionTypes() {
return classifications
.filter(c => c.status === 'basic')
.map(c => c.actionTypes)
.reduce((a,b) => a.concat(b), []);
}
function getMessages(actionTypes) {
return messages
.filter(m => m.actionTypes.some(a => actionTypes.indexOf(a) > -1));
}
function getMessagesFromSet(actionTypeSet) {
return messages
.filter(m => m.actionTypes.some(a => actionTypeSet.has(a)));
}
const arr = getBasicActionTypes();
const m1 = getMessages(arr);
const s = new Set(arr);
const m2 = getMessagesFromSet(s);
console.log(m2);
<script src="https://cdn.jsdelivr/lodash/4/lodash.min.js"></script>
I have to examples result arrays, m1
and m2
which both contain the same elements. The difference is that m2
was derived from an ES6 Set but should be faster.
Sure, most of this can be implemented in pure ES6/ES7. But you asked for lodash
, so here is some idiomatic lodash
.
Chaining is really cool because it basically gives you a wrapper around your value and that's why you have to call value()
at the end when you use chain()
.
It allows you to chain any lodash
function you want sequentially, you're not limited by the output being an array or object etc.
const getMappedMessages = (classifications, messages) => {
const classActionsMap = _.chain(classifications)
.filter(['status', 'basic'])
.map(_.get('actionTypes')))
.reduce((prev, curr) => {
const key = JSON.stringify(curr);
Object.assign(prev, {[key]: true})
}, {})
.value();
return _.filter(messages, msg => classActionsMap[msg.actionTypes]);
}