1
var array  = [
    {
        "checked": true,
        "name":"Name",
        "type": {
            "id": 1,
            "tag": "tag"
        }
    },
    {
        "checked": true,
        "name":"Name",
        "type": {
            "id": 3,
            "tag": "tag"
        }
    },
    {
        "checked": false,
        "name":"Name",
        "type": {
            "id": 2,
            "tag": "tag"
        }
    },
];

I want to sort the array by checked and type.id. Im using the following sorting code but it seems to have trouble with "type.id" since my list is not grouped by those if checked are.

sortByPriority(array, ['checked', 'type.id']);

sortByPriority(data, priorities) {
    if (priorities.length == 0 || data.length == 0) {
        return data;
    }
    const nextPriority = priorities[0];
    const remainingPriorities = priorities.slice(1);
    const matched = data.filter(item => item.hasOwnProperty(nextPriority));
    const remainingData = data.filter(item => !item.hasOwnProperty(nextPriority));

    return this.sortByPriority(matched, remainingPriorities)
        .sort((a, b) => (a[nextPriority] > b[nextPriority]) ? 1 : -1)
        .concat(this.sortByPriority(remainingData, remainingPriorities));
}

Any ideas on how to sort on the type object as well?

(I haven't been able to find another question with an answer with a generic sorter able to sort based on objects in the array)

Christoffer
  • 482
  • 7
  • 32
  • should `checked = true` to be first? – Nina Scholz Jun 26 '18 at 20:25
  • Separate the array based on their `checked`. Then just sort the two arrays based on their `type.id`. When done, just append the sorted arrays to each other in the order of sort for `checked`. – Jimenemex Jun 26 '18 at 20:26
  • Just an idea: Wouldn't it be possible to do prioritised sorting with a stable sorting algorithm? Sort on the lowest priority first, then work up to the highest? Lodash might be helpful – Distjubo Jun 26 '18 at 20:27
  • Possible duplicate of [Sort array of objects by string property value in JavaScript](https://stackoverflow.com/questions/1129216/sort-array-of-objects-by-string-property-value-in-javascript) – Hyyan Abo Fakher Jun 26 '18 at 20:29
  • @Distjubo the JavaScript `.sort()` method is explicitly *not* required to be stable, and because it's often a Quicksort implementation it generally is not stable. – Pointy Jun 26 '18 at 20:34
  • And the general way to do such a sort is with a `.sort()` callback function that proceeds key-by-key from the most significant to the least. If a more significant key is different in one element than another, the comparison function can stop comparing. – Pointy Jun 26 '18 at 20:35
  • @Pointy that's why i said that Lodash might be helpful, because it provides a stable sorting function. EDIT: I just noticed that Lodash also provides prioritised sorting – Distjubo Jun 26 '18 at 20:39

3 Answers3

2

You could use a nested approach for the nested properties and take an array of function for comparing the values.

function sortByPriority(array, keys, fn) {

    function getValue(o, k) {
        return k.split('.').reduce((p, l) => (p || {})[l], o);
    }

    return array.sort((a, b) => {
        var d;
        keys.some((k, i) => d = fn[i](getValue(a, k), getValue(b, k)));
        return d;
    });
}

var array = [{ checked: false, name: "Name", type: { id: 2, tag: "tag" } }, { checked: true, name: "Name", type: { id: 3, tag: "tag" } }, { checked: true, name: "Name", type: { id: 1, tag: "tag" } }];

sortByPriority(array, ['checked', 'type.id'], [(a, b) => b - a, (a, b) => a - b]);

console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
David Conrad
  • 15,432
  • 2
  • 42
  • 54
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • An array of 'sunction'? – David Conrad Jun 26 '18 at 20:59
  • Note: You can use `array.sort((a, b) => keys.map((k, i) => fn[i](getValue(a, k), getValue(b, k))).find(d => d != 0))` to avoid the temporary variable and side effect. – David Conrad Jun 26 '18 at 21:48
  • Scratch that, `array.sort((a, b) => keys.reduce((d, k, i) => d || fn[i](getValue(a, k), getValue(b, k)), 0))` is better as it preserves short circuiting (we still have to loop over all the keys, but it's a no-op once we've found a value). – David Conrad Jun 26 '18 at 21:54
1

Overload the sort method on the array and then you can just switch the < and > based on your sort conditions. Note I removed the return 0 if they are equal on the first sort in order to move onto the second sort if the checked values are equal.

var array  = [
    {
        "checked": true,
        "name":"Name",
        "type": {
            "id": 1,
            "tag": "tag"
        }
    },
    {
        "checked": true,
        "name":"Name",
        "type": {
            "id": 3,
            "tag": "tag"
        }
    },
    {
        "checked": false,
        "name":"Name",
        "type": {
            "id": 2,
            "tag": "tag"
        }
    },
];

function customSort(ob1, ob2) {
    if (ob1.checked > ob2.checked) {
        return 1;
    } else if (ob1.checked < ob2.checked) { 
        return -1;
    }

    // Else sort on the second item
    if (ob1.type.id < ob2.type.id) { 
        return -1;
    } else if (ob1.type.id > ob2.type.id) {
        return 1
    } else {
        return 0;
    }
}

console.log(array);
console.log(array.sort(customSort));
Jimenemex
  • 3,104
  • 3
  • 24
  • 56
0

Here is a generic solution using composed sorts, in which you can combine an arbitrary number of sorting conditions into one large sort function:

    function to_tuples(array_of_elements) {
      return array_of_elements.map((element, index) => [element, index]);
    }
    function to_elements(array_of_tuples) {
      return array_of_tuples.map((element) => element[0]);
    }
    
    function compose_sorts(sort_functions) {
      const default_sort = (l, r) => l[1] < r[1];
    
      return sort_functions.reduceRight((master_sort, sort_function) => {

        const mixin = (next_sort, left, right) => {
          const result = sort_function(left, right);
          return result === 0 ? next_sort(left, right) : result;
        }
        return mixin.bind(null, master_sort); 
      }, default_sort);
    }

    function sort_checked_to_top(left_tuple, right_tuple) {
      const left = left_tuple[0];
      const right = right_tuple[0];
      if (left.checked !== right.checked) {
        return left.checked ? -1 : 1;
      }
      return 0;
    }
    
    function sort_lower_id_to_top(left_tuple, right_tuple) {
      const left = left_tuple[0];
      const right = right_tuple[0];
      if (left.type.id !== right.type.id) {
        return left.type.id > right.type.id ? 1 : -1;
      }
      return 0;
    }
    
    const master_sort = compose_sorts([
      sort_checked_to_top,
      sort_lower_id_to_top,      
    ]);
    
    var array  = [
    {
        "checked": true,
        "name":"Name",
        "type": {
            "id": 1,
            "tag": "tag"
        }
    },
    {
        "checked": false,
        "name":"Name",
        "type": {
            "id": 25,
            "tag": "tag"
        }
    },
    {
        "checked": true,
        "name":"Name",
        "type": {
            "id": 3,
            "tag": "tag"
        }
    },
    {
        "checked": false,
        "name":"Name",
        "type": {
            "id": 2,
            "tag": "tag"
        }
    },
];

const result = to_elements(to_tuples(array).sort(master_sort));
console.log(result);
    
    
    
ouni
  • 3,233
  • 3
  • 15
  • 21