1

I'm trying to sort only a subset of an array, the subset is a filtered version of that array, and I need the whole array (with the subset sorted ) as a result.

Here is the main array :

var data = [
  { name: "Cccc", sortCriteria: 1 },
  { name: "Bbbb", sortCriteria: 1 },
  { name: "Dddd", sortCriteria: 0 },
  { name: "Eeee", sortCriteria: 1 },
  { name: "Ffff", sortCriteria: 0 },
];

The sort criteria is used to filter the array :

var data2 = data.filter(function (attribute) {
  return attribute.sortCriteria == 1;
});

Finally i'm sorting the filtered array :

var data3 = data2.sort(function (a, b) {
  var aname = a.name.toLowerCase();
  var bname = b.name.toLowerCase();
  if (aname < bname) {
    return -1;
  }
  if (aname > bname) {
    return 1;
  }
});

Every step works but data2 & data3 are subset only (i need the wole array) and data is obviously not sorted.

My result should be :

Bbbb
Cccc
Dddd
Eeee
Ffff

Any idea how that can be done using ecmascript 5 (the order can be random, this is a example) ?

Edit : the following question doesnt address my issue because it's not ecma5 compliant.

Javascript - Sort the elements of the array without changing positions of certain elements using Array.sort()

3
  • 1
    If you want the whole array sorted why are you sorting on the filtered array? Commented Jun 25, 2023 at 16:25
  • no, the sorting applies to a subset only, i need the whole array in return Commented Jun 25, 2023 at 16:31
  • 1
    If you are sorting the filtered array shouldn't you be left with: Aaaa, Bbbb, Cccc, Eeee? Commented Jun 25, 2023 at 16:31

4 Answers 4

2

You could get an array of the items fro sorting and their indices and sort the indices an map a new array with either not sorted items or sorted items.

var data = [{ name: "Cccc", sortCriteria: 1 }, { name: "Bbbb", sortCriteria: 1 }, { name: "Dddd", sortCriteria: 0 }, { name: "Eeee", sortCriteria: 1 }, { name: "Ffff", sortCriteria: 0 }],
    data2 = data.filter(function (item) {
        return item.sortCriteria === 1;
    }),
    indices = data2
        .map(function (_, i) {
            return i;
        })
        .sort(function (a, b) {
            var aname = data2[a].name.toLowerCase(),
                bname = data2[b].name.toLowerCase();

            if (aname < bname) return -1;
            if (aname > bname) return 1;
            return 0;
        }),
    index = 0,
    sorted = data.map(function (item) {
        return item.sortCriteria === 1
            ? data2[indices[index++]]
            : item;
    });

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

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks all, i'm using this solution as it's easier to fit within my script !
1

First you have problem with your test data. If it's sorted without even sortCriteria in mind the result will be the same. So I've changed the test data to make it more obvious.

We remember indices of items with sortCriteria, sort them and push back to data at remembered indices.

NOTICE: changed Cccc's sortCriteria to 0 so it would be clear that it's not sorted and stays at index 0, also Bbbb moved to the end so it should appear before `Eeee' after the sorting:

var data = [{name : "Cccc", sortCriteria : 0},  
{name : "Dddd", sortCriteria : 0}, {name : "Eeee", sortCriteria : 1}, {name : "Ffff", sortCriteria : 0}, {name : "Bbbb", sortCriteria : 1}];

var items = [], idxs =[];
for(var j = 0; j < data.length; j++){
  const item = data[j];
  if(item.sortCriteria === 1){
    // colect item, its index and sort value with sortCriteria === 1
    items.push([item, item.name.toLowerCase()]);
    idxs.push(j);
  }
}
// sort by the sort value
items.sort(function(a, b){
  return a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0;
});
// put sorted items back into the data array using collected indices
let idx = 0;
for(var j = 0; j < items.length; j++){
  data[idxs[idx++]] = items[j][0];
}

//not es2015, just logging
console.log(data.map(({name})=>name));

And a benchmark with test data x 1000000.

enter image description here

<script benchmark data-count="1">

    var chunk = [{name : "Cccc", sortCriteria : 0},  
{name : "Dddd", sortCriteria : 0}, {name : "Eeee", sortCriteria : 1}, {name : "Ffff", sortCriteria : 0}, {name : "Bbbb", sortCriteria : 1}];
    var data = [];
    for (let j = 0; j < 1000000; j++) {
        data.push(...chunk);
    }
    
    // @benchmark Alexander's solution

    var items = [], idxs =[];
    for(var j = 0; j < data.length; j++){
      const item = data[j];
      if(item.sortCriteria === 1){
    // colect item, its index and sort value with sortCriteria === 1
    items.push([item, item.name.toLowerCase()]);
    idxs.push(j);
      }
    }
    // sort by the sort value
    items.sort(function(a, b){
      return a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0;
    });
    // put sorted items back into the data array using collected indices
    let idx = 0;
    for(var j = 0; j < items.length; j++){
      data[idxs[idx++]] = items[j][0];
    }

    data;    

    // @benchmark Nina's solution

    var data2 = data.filter(function (item) {
        return item.sortCriteria === 1;
    }),
        indices = data2
            .map(function (_, i) {
                return i;
            })
            .sort(function (a, b) {
                var aname = data2[a].name.toLowerCase(),
                    bname = data2[b].name.toLowerCase();

                if (aname < bname) return -1;
                if (aname > bname) return 1;
                return 0;
            }),
            index = 0,
        sorted = data.map(function (item) {
            return item.sortCriteria === 1
                ? data2[indices[index++]]
                : item;
        });
    sorted;    



</script>
<script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>

7 Comments

i replaced the shifting with an index in my solution. this should be faster now.
@NinaScholz i will update
@SebastienChemouny it's sad you preferred a less performant solution, anyway thanks
@AlexanderNenashev actually, something i'd like to understand is the following : data.forEach(function(item, idx){ item.sortCriteria && items.push(item) && idxs.push(idx); }); How does the filter work (the item.sortCriteria), like if my data was not 1 or 0 ?
@SebastienChemouny your data contains only sortCriteria either 0 or 1, so it's like false or true. that's why i used it as a boolean. can change it to item.sortCriteria === 1. i've fixed the code to make it more readable
|
0

Just add if (!a.sortCriteria || !b.sortCriteria) return 0; at the beginning of the sorting function to avoid sorting elements with sortCriteria 0. (Array.prototype.sort() sorts in-place, so there is no need to var data2 = data.sort(...) - data and data2 is the same array here.

var data = [
  { name: "Cccc", sortCriteria: 1 },
  { name: "Bbbb", sortCriteria: 1 },
  { name: "Dddd", sortCriteria: 0 },
  { name: "Eeee", sortCriteria: 1 },
  { name: "Ffff", sortCriteria: 0 },
];

data.sort(function(a, b) {
    if (!a.sortCriteria || !b.sortCriteria) return 0;
    var aname = a.name.toLowerCase();
    var bname = b.name.toLowerCase();
    if (aname < bname) return -1;
    if (aname > bname) return 1;
    return 0;
});

console.log(data);

Comments

-1

You can store the original index of each element while filtering manually, then use those indexes to update the positions of the objects back in the original array.

var data = [{name : "Cccc", sortCriteria : 1}, {name : "Bbbb", sortCriteria : 1}, {name : "Dddd", sortCriteria : 0}, {name : "Eeee", sortCriteria : 1}, {name : "Ffff", sortCriteria : 0}];
var filtered = [], origIdx = [];
for (var i = 0; i < data.length; i++)
  if (data[i].sortCriteria === 1) 
    filtered.push(data[i]), origIdx.push(i);
filtered.sort(function(a, b){return a.name.toLowerCase().localeCompare(b.name.toLowerCase())});
for (var i = 0; i < filtered.length; i++)
  data[origIdx[i]] = filtered[i];
console.log(data);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.