javascriptarrayssortingobject

What is wrong in this sorting array skipping some elements?


I have array of objects. I want sort it leave some elements in the same position (with b="NOT")

var a=[{a:1,b:"YES"},{a:2,b:"YES"},{a:5,b:"NOT"},{a:0,b:"NOT"},{a:0,b:"YES"}]

function sortc(x,y){

  if (x.b=="NOT" || y.b=="NOT")
    return Infinity ;
     
  return (Number(x.a)-Number(y.a))
                 
}

console.log(a.sort(sortc));

the result is :

0: {a: 1, b: "YES"}
1: {a: 2, b: "YES"}
2: {a: 5, b: "NOT"}
3: {a: 0, b: "NOT"}
4: {a: 0, b: "YES"}

The expected result was ( with sort components with b="YES".) :

{ "a": 0, "b": "YES" }
{ "a": 1, "b": "YES" }
{ "a": 5, "b": "NOT" }
{ "a": 0, "b": "NOT" }
{ "a": 2, "b": "YES" } 

Solution

  • You cannot only sort some items using the Array#sort() method - you either sort all or none. You don't define the position of the items, either - you only have to define their relationship to other items and the sorting algorithm will take care of the rest.

    What you can do as a workaround is

    1. Extract all items that should be sorted.
    2. Sort them.
    3. Go over the original array and only replace anything that should be sorted, leave the rest of the items in their place.

    var a = [
      { a: 1, b: "YES" }, 
      { a: 2, b: "YES" },
      { a: 5, b: "NOT" }, 
      { a: 0, b: "NOT" }, 
      { a: 0, b: "YES" }
    ]
    
    //get only `b: "YES"` items
    const dataToSort = a.filter(item => item.b === "YES");
    
    //sort them
    dataToSort.sort((x, y) => x.a - y.a);
    
    //replace only items that need to be sorted
    const it = dataToSort.values()
    for (let i = 0; i < a.length; i++) {
      if (a[i].b === "NOT")
        continue;
        
      a[i] = it.next().value;
    }
    
    console.log(a);

    For the record, the final loop can just be replaced with even shorter with more iterator usage, although it might be slightly more confusing:

    const it = dataToSort.values()
    for (const [key, item] of a.entries()) { //use the key-value iterator from the array
      if (item.b === "NOT")
        continue;
        
      [a[key]] = it; //array destructuring internally advances an iterator
    }
    

    var a = [
      { a: 1, b: "YES" }, 
      { a: 2, b: "YES" },
      { a: 5, b: "NOT" }, 
      { a: 0, b: "NOT" }, 
      { a: 0, b: "YES" }
    ]
    
    //get only `b: "YES"` items
    const dataToSort = a.filter(item => item.b === "YES");
    
    //sort them
    dataToSort.sort((x, y) => x.a - y.a);
    
    //replace only items that need to be sorted
    const it = dataToSort.values()
    for (const [key, item] of a.entries()) {
      if (item.b === "NOT")
        continue;
        
      [a[key]] = it;
    }
    
    console.log(a);


    Finally, this can be made somewhat more convenient with helper generator function and few small utility functions

    /*  library code  */
    
    const transformArg = transform => f => (...args) => f(transform(...args));
    
    function* filter(predicate, it) {
      for (const item of it) {
        if (predicate(item))
          yield item;
      }
    }
    
    /*  /library code  */
    
    var a = [
      { a: 1, b: "YES" }, 
      { a: 2, b: "YES" },
      { a: 5, b: "NOT" }, 
      { a: 0, b: "NOT" }, 
      { a: 0, b: "YES" }
    ]
    
    /* helpers */
    //extract the `b` key in this case so we don't need to repeat it. 
    const getSortableAttribute = transformArg(({b}) => b);
    //get the value from key-value pair
    const getValue = transformArg(([, value]) => value);
    //check if the attribute is "YES"
    const isSortable = getSortableAttribute(attr => attr === "YES");
    
    const dataToSort = a.filter(isSortable);
    
    dataToSort.sort((x, y) => x.a - y.a);
    
    const it = dataToSort.values()
    //iterate only over sortable key-value pairs by re-using the `isSortable` filter
    for (const [key, item] of filter(getValue(isSortable), a.entries())) {
      [a[key]] = it;
    }
    
    console.log(a);