Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript Alphabetically sort and move all doubles to the end of array

I am trying to sort an array of objects by a property. I run:

array.sort(function(a, b){
  var textA = a.name.toUpperCase();
  var textB = b.name.toUpperCase();
  return (textA < textB) ? -1 : (textA > textB) ? 1: 0
});

To alphabetically sort the array objects first and then I run an array.sort with a custom compare function as below:

array.sort(function(a, b){
  if(a.name === b.name){
    return -1;
  }
  return 1;
});

It seems to work with anything object that does not have a duplicate, however, as soon as there are doubles it pushes them all to the end of the array instead of just the extras.

Example:

[
  {name: 'Amy'},
  {name: 'Amy'},
  {name: 'Clark'},
  {name: 'Clark'},
  {name: 'Dan'},
  {name: 'Dave'}
  {name: 'Joe'},
  {name: 'Joe'}
]

Expected Output:

  • Amy
  • Clark
  • Dan
  • Dave
  • Joe
  • Amy
  • Clark
  • Joe

Actual Result:

  • Dan
  • Dave
  • Amy
  • Amy
  • Clark
  • Clark
  • Joe
  • Joe

Sort Code To try and get Expected Result

array.sort(function(a,b){
  if(a.name === b.name){return -1}

  return 1;
});

I have a feeling the array.sort with a compare function can handle this however I keep playing with return values of 0, -1, 1 and cannot seem to get it to work fully as I would like.

Update

Expected Result Criteria:

If an object has the same name the duplicate should go to the end of the array. For example if there are two 'Amy' one stays at the begining of the array and the duplicate goes to the end. So that all first occurrences of the names wil be at the begining of the array and all the doubles, triples etc will will be reordered each time at the end of the array. So that it could potentially arrange alhpabetical multiple items.

Example:

[
  {name: 'Amy'},
  {name: 'Amy'},
  {name: 'Clark'},
  {name: 'Clark'},
  {name: 'Clark'},
  {name: 'Dan'},
  {name: 'Dave'},
  {name: 'Joe'},
  {name: 'Joe'},
  {name: 'Joe'},
]

Expected result:

Amy Clark Dan Dave Joe Amy - Duplicate Clark - Duplicate Joe - Duplicate Clark - Had a third Joe - Had a third

As you can see it orders the first occurrence of all names alphabetically. Then orders the second occurrence alphabetically, and then the third. Until all duplicates are resolved.

After talking in comments it has come to my understanding that it cannot be done in an array.sort function alone. Sort alone with a compare function seems to be great for single or grouping doubles but not for putting doubles at the end of the array.

like image 515
L1ghtk3ira Avatar asked Jan 03 '23 00:01

L1ghtk3ira


2 Answers

Your comparator function is incorrect. The function must:

  • Return a negative number when the first argument should sort before the second;
  • Return a positive number when the first argument should sort after the second;
  • Return zero when the two items have equivalent sort keys.

Because yours is not consistent, the sort process gets confused. For your case, the simplest thing to use is the .localeCompare() function, which returns exactly the sort of result you need:

array.sort(function(a, b) { return a.name.localeCompare(b.name); });

From your "expected output", your ordering criteria are unclear. In any case, the sort comparator, whatever it does, has to be consistent: when two items are passed to it in either order, the function should report the same ordering.

edit if the original ordering has some semantic meaning, and the "doubles" (I'd call them "duplicates") should sort further down in the array, you can add another property to each object that captures that original status:

var keyMap = {};
array.forEach(function(item) {
  if (item.name in keyMap)
    item.position = ++keyMap[item.name];
  else
    keyMap[item.name] = item.position = 1;
});

Now you can sort:

array.sort(function(a, b) {
  var c = a.position - b.position;
  if (c) return c;
  return a.name.localeCompare(b.name);
});

If the "position" values are the same, the items will be ordered by name. Items that were duplicates in the original array will be sorted after items that weren't (and triplicates will be sorted after those, etc).

like image 139
Pointy Avatar answered Jan 05 '23 15:01

Pointy


You could use sorting with map by using a temporary object with a hash table for the same group array. Take from it the length of the used array as group for sorting.

The sorting happens with the group and index.

The result is mapped with index of the sorted temporary array.

Tge first part generates an array with an index of the original array and their group which is taken from pushing a value into the same group. Actually we need oly the array length after pushing of the group. If more items are in the same group, the items will be sorted later.

[
    {
        index: 0, // Amy
        group: 1
    },
    {
        index: 1, // Amy
        group: 2
    },
    {
        index: 2, // Dan
        group: 1
    },
    {
        index: 3, // Joe
        group: 1
    },
    {
        index: 4, // Joe
        group: 2
    }
]

The above given array is then sorted by group and index, both ascending.

At the last part, a new array is mapped with the index value of the sorted array.

var array = [{ name: 'Amy' }, { name: 'Amy' }, { name: 'Dan' }, { name: 'Joe' }, { name: 'Joe' }],
    groups = Object.create(null),
    result = array
        // this part is only necessary if the names should be in ascending order
        // for keeping the given order, remove the part until next comment
        .sort(function (a, b) {
            return a.name.localeCompare(b.name);
        })
        // remove until here, if necessary
        .map(function (a, i) {
            return { index: i, group: (groups[a.name] = groups[a.name] || []).push(0) };
        })
        .sort(function (a, b) {
            return a.group - b.group || a.index - b.index;
        })
        .map(function (o) {
            return array[o.index];
        });

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Example for unsorted data.

var array = [{ name: 'Joe', i: 0 }, { name: 'Dan', i: 1 }, { name: 'Amy', i: 2 }, { name: 'Joe', i: 3 }, { name: 'Amy', i: 4 }],
    groups = Object.create(null),
    result = array
        .map(function (a, i) {
            return {
                index: i,
                group: (groups[a.name] = groups[a.name] || []).push(0),
                value: a.name
            };
        })
        .sort(function (a, b) {
            return a.group - b.group || a.value.localeCompare(b.value);
        })
        .map(function (o) {
            return array[o.index];
        });

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
like image 23
Nina Scholz Avatar answered Jan 05 '23 15:01

Nina Scholz