More performant way of sorting by selected first

- 1 answer

Ad

I have a table and a map above that contains markers that relate to the table rows below. When a user selects items on the map above it highlights the matching rows then moves those rows to the top of the table but sorts by the user's sorting setting.

For example, if you had this raw data (not displayed data):

[
  {id: 1, name: 'Z'},
  {id: 2, name: 'Y'},
  {id: 3, name: 'X'},
  {id: 4, name: 'W'}
]

Then the user selects id: 1 and id: 3 on the map and has it sorted by name. The data becomes:

[
  {id: 3, name: 'X'}, // selected
  {id: 1, name: 'Z'}, // selected
  {id: 4, name: 'W'}, // not selected
  {id: 2, name: 'Y'}  // not selected
]

There's some data sets with 10,000+ rows and I'm worried this is locking up too long when they select the items because of the 2 filters + concat. Is there a way to do this in one pass or just a way to prevent the browser from locking while it's doing this?

  var selectedRows = rows.filter(function (row) {
    return selectedMarkerIds.indexOf(row.id) !== -1
  });

  var nonSelectedRows = rows.filter(function (row) {
    return selectedMarkerIds.indexOf(row.id) == -1
  });

  var allRows = selectedRows.concat(nonSelectedRows);

  myTable.updateRowSort(allRows);
Ad

Answer

Ad

Try reduce. In the callback function, it checks for whether the row was selected or not and pushes it to the appropriate array in the result object. You do this in one pass and then concat the arrays in the last step.

var result = rows.reduce((result, row) => {
  selectedMarkerIds.indexOf(row.id) !== -1 ? result.selected.push(row) : result.notSelected.push(row);
  return result;
}, {
  selected: [],
  notSelected: []
});

result.selected.concat(result.notSelected);

I'm also a big fan of lodash and here's the lodash implementation. I first pass the array to the chain function which basically wraps it around a lodash wrapper which enables chaining of lodash functions. I then pass the array through my reduce function, similar to what I did above with Array.prototype.reduce, but this time the result's initial value is an array of two arrays: the first array is meant for selected rows while the second is for non-selected rows. I then use _.flatten to flatten the nested array so now we have an array of objects instead of an array of arrays. I call value to unwrap the lodash wrapper and get my finally value which is an array of rows.

/**
 * in the reduce function, the initial value of the 
 * result is an array of two arrays. 
 * the first array is for selected items.
 * the second array is for non-selected items.
 */
var newArray = _.chain(rows)
  .reduce((result, row) => {
    selectedMarkerIds.indexOf(row.id) !== -1 ? result[0].push(row) : result[1].push(row);
    return result;
  }, [ [], [] ])
  .flatten()
  .value();
Ad
source: stackoverflow.com
Ad