'Sort an array except one element in JavaScript

I have an array and I am sorting it but I need to sort everything except one element of my array.

My array is:

var Comparison = [
    {key: "None", value: "None"},
    {key:"Geographical Area", value:"Geographical_Area"},
    {key:"Forests", value:"Forests"},
    {key:"Barren Unculturable Land", value:"Barren_Unculturable_Land"},
    {key:"Land put to Non agricultural use", value:"Land_put_to_Non_agricultural_use"},
    {key:"Land Area", value:"Land_Area"},
    {key:"Water Area", value:"Water_Area"},
    {key:"Culturable Waste", value:"Culturable_Waste"},
    {key:"Permanent Pastures", value:"Permanent_Pastures"},
    {key:"Land under Tree Crops", value:"Land_under_Tree_Crops"},
    {key:"Fallow Land excl Current Fallow", value:"Fallow_Land_excl_Current_Fallow"},
    {key:"Current Fallow", value:"Current_Fallow"},
    {key:"Total Unculturable Land", value:"Total_Unculturable_Land"},
    {key:"Net Sown Area", value:"Net_Sown_Area"},
    {key:"Gross Sown Area", value:"Gross_Sown_Area"},
    {key:"Cropping Intensity", value:"Cropping_Intensity"} ];

I am sorting this array using this code:

var Comparison_sort = this.Comparison.sort(function (a, b) {
  if (a.key < b.key)
      return -1;
  if (a.key > b.key)
      return 1;
  return 0;
});

This is sorting my array perfectly, but I want one of my elements to be on top, meaning my element None should be on top and sort all other elements.

For example, I am getting this result:

   {key: "Barren Unculturable Land", value: "Barren_Unculturable_Land"}
   {key: "Cropping Intensity", value: "Cropping_Intensity"}
   {key: "Culturable Waste", value: "Culturable_Waste"}
    ....
   {key: "None", value: "None"}

But I want a result like this:

   {key: "None", value: "None"}
   {key: "Barren Unculturable Land", value: "Barren_Unculturable_Land"}
   {key: "Cropping Intensity", value: "Cropping_Intensity"}
   {key: "Culturable Waste", value: "Culturable_Waste"}
    ....

I saw an answer, Sort array in TypeScript, but I was unable to use this answer to my problem.



Solution 1:[1]

var Comparison_sort = this.Comparison.sort(function (a, b) {
  if(a.key == b.key) return 0;
  if (a.key == 'None') return -1;
  if (b.key == 'None') return 1;

  if (a.key < b.key)
      return -1;
  if (a.key > b.key)
      return 1;
  return 0;
});

tells "do a regular sort, except if the key is none which means it must go first."

Solution 2:[2]

Not fancy, but a pretty straightforward way of doing this is to just remove the special element, sort the array, and insert the special to whatever index you want.

var Comparison = [{ key: "None", value: "None" }, { key: "Geographical Area",value: "Geographical_Area" }, { key: "Forests", value: "Forests" }, { key: "Barren Unculturable Land", value: "Barren_Unculturable_Land" }, { key: "Land put to Non agricultural use", value: "Land_put_to_Non_agricultural_use" }, { key: "Land Area", value: "Land_Area" }, { key: "Water Area", value: "Water_Area" }, { key: "Culturable Waste", value: "Culturable_Waste" }, { key: "Permanent Pastures", value: "Permanent_Pastures" }, { key: "Land under Tree Crops", value: "Land_under_Tree_Crops" }, { key: "Fallow Land excl Current Fallow", value: "Fallow_Land_excl_Current_Fallow" }, { key: "Current Fallow", value: "Current_Fallow" }, { key: "Total Unculturable Land", value: "Total_Unculturable_Land" }, { key: "Net Sown Area", value: "Net_Sown_Area" }, { key: "Gross Sown Area", value: "Gross_Sown_Area" }, { key: "Cropping Intensity", value: "Cropping_Intensity" },];

const idx = Comparison.findIndex(a => a.key === 'None');
const none = Comparison.splice(idx, 1);
Comparison.sort((a, b) => a.key.localeCompare(b.key));
Comparison.splice(0,0, none[0]);

console.log(Comparison);

To avoid no special or multiple special element issues:

var Comparison = [{ key: "None", value: "None" }, { key: "Geographical Area",value: "Geographical_Area" }, { key: "Forests", value: "Forests" }, { key: "Barren Unculturable Land", value: "Barren_Unculturable_Land" }, { key: "Land put to Non agricultural use", value: "Land_put_to_Non_agricultural_use" }, { key: "Land Area", value: "Land_Area" }, { key: "Water Area", value: "Water_Area" }, { key: "Culturable Waste", value: "Culturable_Waste" }, { key: "Permanent Pastures", value: "Permanent_Pastures" }, { key: "Land under Tree Crops", value: "Land_under_Tree_Crops" }, { key: "Fallow Land excl Current Fallow", value: "Fallow_Land_excl_Current_Fallow" }, { key: "Current Fallow", value: "Current_Fallow" }, { key: "Total Unculturable Land", value: "Total_Unculturable_Land" }, { key: "Net Sown Area", value: "Net_Sown_Area" }, { key: "Gross Sown Area", value: "Gross_Sown_Area" }, { key: "Cropping Intensity", value: "Cropping_Intensity" },];

const obj = Comparison.reduce((acc, a) => {
  if (a.key === 'None') {
    acc.f.push(a);
  } else {
    const idx = acc.s.findIndex(b => b.key.localeCompare(a.key) > 0);
    acc.s.splice(idx === -1 ? acc.s.length : idx, 0, a);
  }
  return acc;
}, { f: [], s: [] });

const res = obj.f.concat(obj.s);

console.log(res);

Solution 3:[3]

Alternatively you can filter out the nones and sort the other elements. Then concatenate them back to each other at the end.

const comparison = [{key: "None", value: "None"}, {key: "Geographical Area", value: "Geographical_Area"}, {key: "Forests", value: "Forests"}, {key: "Barren Unculturable Land", value: "Barren_Unculturable_Land"}, {key: "Land put to Non agricultural use", value: "Land_put_to_Non_agricultural_use"}, {key: "Land Area", value: "Land_Area"}, {key: "Water Area", value: "Water_Area"}, {key: "Culturable Waste", value: "Culturable_Waste"}, {key: "Permanent Pastures", value: "Permanent_Pastures"}, {key: "Land under Tree Crops", value: "Land_under_Tree_Crops"}, {key: "Fallow Land excl Current Fallow", value: "Fallow_Land_excl_Current_Fallow"}, {key: "Current Fallow", value: "Current_Fallow"}, {key: "Total Unculturable Land", value: "Total_Unculturable_Land"}, {key: "Net Sown Area", value: "Net_Sown_Area"}, {key: "Gross Sown Area", value: "Gross_Sown_Area"}, {key: "Cropping Intensity", value: "Cropping_Intensity"}];

const result = comparison
  .filter(e => e.key === 'None')
  .concat(
    comparison
      .filter(e => e.key !== 'None')
      .sort((a, b) => a.key.localeCompare(b.key))
  );
               
console.log(result);

Explanation:

const comparison = [{key: "None", value: "None"}, {key: "Geographical Area", value: "Geographical_Area"}, {key: "Forests", value: "Forests"}, {key: "Barren Unculturable Land", value: "Barren_Unculturable_Land"}, {key: "Land put to Non agricultural use", value: "Land_put_to_Non_agricultural_use"}, {key: "Land Area", value: "Land_Area"}, {key: "Water Area", value: "Water_Area"}, {key: "Culturable Waste", value: "Culturable_Waste"}, {key: "Permanent Pastures", value: "Permanent_Pastures"}, {key: "Land under Tree Crops", value: "Land_under_Tree_Crops"}, {key: "Fallow Land excl Current Fallow", value: "Fallow_Land_excl_Current_Fallow"}, {key: "Current Fallow", value: "Current_Fallow"}, {key: "Total Unculturable Land", value: "Total_Unculturable_Land"}, {key: "Net Sown Area", value: "Net_Sown_Area"}, {key: "Gross Sown Area", value: "Gross_Sown_Area"}, {key: "Cropping Intensity", value: "Cropping_Intensity"}];

// get all elements with the key 'None'
const nones = comparison.filter(e => e.key === 'None');
// get all elements with the key not 'None'
const others = comparison.filter(e => e.key !== 'None')
// sort the elements in the array by key
others.sort((a, b) => a.key.localeCompare(b.key));
// concatenate the 2 arrays together
const result = nones.concat(others);

console.log(result);

A bit of credit to Pac0s answer. After writing my solution I saw that I basically made a working version of his explanation. I'm too late to add my example to his answer because this is currently the most upvoted of the two.


For larger arrays using filter() two times on the same array with the opposite predicate (check callback) might be a bit inefficient. You could opt to introduce a helper function like partition() to help reduce the amount of iteration that has to be done.

function partition(forEachable, callback) {
  const partitions = { "true": [], "false": [] };
  forEachable.forEach((...args) => partitions[!!callback(...args)].push(args[0]));
  return [partitions[true], partitions[false]];
}

let comparison = [{key: "None", value: "None"}, {key: "Geographical Area", value: "Geographical_Area"}, {key: "Forests", value: "Forests"}, {key: "Barren Unculturable Land", value: "Barren_Unculturable_Land"}, {key: "Land put to Non agricultural use", value: "Land_put_to_Non_agricultural_use"}, {key: "Land Area", value: "Land_Area"}, {key: "Water Area", value: "Water_Area"}, {key: "Culturable Waste", value: "Culturable_Waste"}, {key: "Permanent Pastures", value: "Permanent_Pastures"}, {key: "Land under Tree Crops", value: "Land_under_Tree_Crops"}, {key: "Fallow Land excl Current Fallow", value: "Fallow_Land_excl_Current_Fallow"}, {key: "Current Fallow", value: "Current_Fallow"}, {key: "Total Unculturable Land", value: "Total_Unculturable_Land"}, {key: "Net Sown Area", value: "Net_Sown_Area"}, {key: "Gross Sown Area", value: "Gross_Sown_Area"}, {key: "Cropping Intensity", value: "Cropping_Intensity"}];

const [nones, others] = partition(comparison, e => e.key === "None");
others.sort((a, b) => a.key.localeCompare(b.key));
const result = nones.concat(others);
console.log(result);

Solution 4:[4]

There might be a better approach, but this should work:

  1. Filter the special value out of your array.

  2. Sort your array without the special value.

  3. Insert the special value back in the array.

For a good working example, see @Johan Wentholt's answer!

Solution 5:[5]

You can use reduce to achieve the desired output:

var Comparison = [{key:"Geographical Area", value:"Geographical_Area"},   {key:"Forests", value:"Forests"},   {key:"Barren Unculturable Land", value:"Barren_Unculturable_Land"}, {key: "None", value: "None"},  {key:"Land put to Non agricultural use", value:"Land_put_to_Non_agricultural_use"}, {key:"Land Area", value:"Land_Area"},   {key:"Water Area", value:"Water_Area"}, {key:"Culturable Waste", value:"Culturable_Waste"}, {key:"Permanent Pastures", value:"Permanent_Pastures"}, {key:"Land under Tree Crops", value:"Land_under_Tree_Crops"},   {key:"Fallow Land excl Current Fallow", value:"Fallow_Land_excl_Current_Fallow"},   {key:"Current Fallow", value:"Current_Fallow"}, {key:"Total Unculturable Land", value:"Total_Unculturable_Land"},   {key:"Net Sown Area", value:"Net_Sown_Area"},   {key:"Gross Sown Area", value:"Gross_Sown_Area"},   {key:"Cropping Intensity", value:"Cropping_Intensity"},]

var Comparison_sort = Comparison
                      .sort((a, b) => a.key.localeCompare(b.key))
                      .reduce((acc, e) => {
                        e.key === 'None' ? acc.unshift(e) : acc.push(e);
                        return acc;
                      }, []);

console.log(Comparison_sort);

Sort using reduce version-2:

let comparison = [{key: "None", value: "None"}, {key: "Geographical Area", value: "Geographical_Area"}, {key: "Forests", value: "Forests"}, {key: "Barren Unculturable Land", value: "Barren_Unculturable_Land"}, {key: "Land put to Non agricultural use", value: "Land_put_to_Non_agricultural_use"}, {key: "Land Area", value: "Land_Area"}, {key: "Water Area", value: "Water_Area"}, {key: "Culturable Waste", value: "Culturable_Waste"}, {key: "Permanent Pastures", value: "Permanent_Pastures"}, {key: "Land under Tree Crops", value: "Land_under_Tree_Crops"}, {key: "Fallow Land excl Current Fallow", value: "Fallow_Land_excl_Current_Fallow"}, {key: "Current Fallow", value: "Current_Fallow"}, {key: "Total Unculturable Land", value: "Total_Unculturable_Land"}, {key: "Net Sown Area", value: "Net_Sown_Area"}, {key: "Gross Sown Area", value: "Gross_Sown_Area"}, {key: "Cropping Intensity", value: "Cropping_Intensity"}];

var {Comparison_sort} = comparison.reduce((acc, obj, idx, arr) => {
                                  obj.key === 'None' ? acc['first'].push(obj) : acc['last'].push(obj)
                                  if (idx === arr.length - 1) (acc['last'].sort((a, b) => a.key.localeCompare(b.key)), acc['Comparison_sort'] = [...acc['first'], ...acc['last']])
                                  return acc
                                }, {first: [], last: [], Comparison_sort: []})

console.log(Comparison_sort);

Solution 6:[6]

A simple one-liner: if any of the keys in Array.prototype.sort compare function is 'None', then always put it on top, otherwise do basic comparison of keys with String.prototype.localeCompare():

var comparison = [{key: "None", value: "None"}, {key: "Geographical Area", value: "Geographical_Area"}, {key: "Forests", value: "Forests"}, {key: "Barren Unculturable Land", value: "Barren_Unculturable_Land"}, {key: "Land put to Non agricultural use", value: "Land_put_to_Non_agricultural_use"}, {key: "Land Area", value: "Land_Area"}, {key: "Water Area", value: "Water_Area"}, {key: "Culturable Waste", value: "Culturable_Waste"}, {key: "Permanent Pastures", value: "Permanent_Pastures"}, {key: "Land under Tree Crops", value: "Land_under_Tree_Crops"}, {key: "Fallow Land excl Current Fallow", value: "Fallow_Land_excl_Current_Fallow"}, {key: "Current Fallow", value: "Current_Fallow"}, {key: "Total Unculturable Land", value: "Total_Unculturable_Land"}, {key: "Net Sown Area", value: "Net_Sown_Area"}, {key: "Gross Sown Area", value: "Gross_Sown_Area"}, {key: "Cropping Intensity", value: "Cropping_Intensity"}];

var sorted = comparison.sort((a,b) => a.key === 'None' ? -1 : b.key === 'None' ? 1 : a.key.localeCompare(b.key));

console.log(sorted);

Solution 7:[7]

The <Array>.sort function takes a callback as an argument. This callback will be passed two values. The job of the callback is to determine which one is bigger. It does this by returning a numeric value.

Let's say the arguments passed to your callback are called a and b. I have bolded the values your callback should return for each case

  • a < b Less than 0
  • a > b Greater than 0
  • a = b Equal to 0

This is easy to remember because, for numerical values, you can use a - b to get a desired return value.

Now, despite most callbacks passed to .sort are very small, it is possible to pass in very complicated functions to suit your need. In this case,

  • If a.key is None, a < b
  • If b.key is None, b < a
  • Else, use our current sort mechanism.

We could take advantage of the return statement exiting once it's called. So, let's implement this function bullet-by-bullet.

To make our code Super Good, let's return "0" when the two values are equal (even when those two values have keys of "None")

Comparison.sort(function(a, b) {
  // Our extra code
  if(a.key === b.key) return 0; // Zero (a = b)
  if(a.key === "None") return -1; // Negative (a < b)
  if(b.key === "None") return 1; // Positive (b < a)

  // Old sort
  if(a.key < b.key) return -1;
  if(b.key < a.key) return 1;  
})

Golfing that solution

There are ways to make that solution shorter (and, perhaps, more readable) -- which is important when code is doing simple tasks.

The first thing to note is that the final line, if(b.key < a.key) return -1 could be shortened to return -1;. This is because if a.key < b.key or b.key = a.key we would've returned on an earlier line.

The second thing to note is that using ES6 syntax (which might not be compatible with older browsers, particularly regarding Internet Explorer), we can use arrow function notation for the callback.

function(a, b) {} could become (a, b) => {}

The third thing to note is that we can convert the below block of code

if(a.key < b.key) return -1;
if(b.key < a.key) return 1;

into

return (b.key < a.key) - (a.key < b.key)

That's because true is treated as 1, and false as 0 when regarding subtraction. true - false is 1 - 0 is 1, false - true is 0 - 1 is -1, and 0 - 0 is 0. There will never be a situation where true - true occurs.

Solution 8:[8]

Just add a check at the beginning. If it's the none object then move it to the front without performing the checks.

var Comparison_sort = this.Comparison.sort(function (a, b) {
    if (a.key == "None" && a.value == "None")
        return -1;
    if (b.key == "None" && b.value == "None")
        return 1;
    if (a.key < b.key)
            return -1;
    if (a.key > b.key)
            return 1;
    return 0;
});

Solution 9:[9]

If you want it positioned at the top you could just return -1 or you could return 1 if you want your item at the bottom of the list.

You do not need multiple if's for the firstly or lastly positioned item which you want to exclude from the general sort.

this.Comparison.sort(function (a, b) {
  if(a.key == b.key) return 0;
  if (a.key == 'None' || b.key == 'None') return -1;

  if (a.key < b.key)
      return -1;
  if (a.key > b.key)
      return 1;
  return 0;
});

Solution 10:[10]

const array1 = this.Comparison.filter(e => e.key === 'None');
const array2 = this.Comparison
            .filter(e => e.key !== 'None')
            .sort((a, b) => a.key.localeCompare(b.key));

this.Comparison = [].concat(array1, array2);

Solution 11:[11]

The accepted answer is great if you only have one item that is to be put on the top of the list, but what if the list needs multiple items put at the start?

The below code takes an array of items in a particular order and makes sure they are always first in the sorted array.

var items = ["P", "Third", "First", "D", "A", "Z", "Second", "Fourth"];
var topItems = ["First", "Second", "Third", "Fourth"];
//var topItemsObj = { First: 1, Second: 2, Third: 3, Fourth: 4 };

var mySortFunc = function (a, b) {
    // Add 1 because normally -1 is not found & !0 == true.
    // This could also be replaced by object as !undefined == true. See commented code.
    var 
        isFirstItem = (v) => topItems.indexOf(v) + 1, 
        //isFirstItem = (v) => topItemsObj[v] || 0,
        t1 = isFirstItem(a),
        t2 = isFirstItem(b);


    if (t1 && t2) return t1 - t2; // Both items are first items. Sort by array index
    if (t1 || t2) return t2 - t1; // One item is first item. Prioritise first item
    return a.localeCompare(b); // Both items are not first items. Sort normally
}

items.sort(mySortFunc)
//  ['First', 'Second', 'Third', 'Fourth', 'A', 'D', 'P', 'Z']

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 spi
Solution 2 Peter Mortensen
Solution 3
Solution 4 Peter Mortensen
Solution 5
Solution 6
Solution 7
Solution 8 Aplet123
Solution 9 Eugen Sunic
Solution 10 Rulio Jones
Solution 11 Chicken