最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Deep comparison of object arrays with Lodash - Stack Overflow

programmeradmin1浏览0评论

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 badges
Add a ment  | 

4 Answers 4

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]);
 }
发布评论

评论列表(0)

  1. 暂无评论