简体   繁体   中英

Sorting Javascript array by comparing objects with different key-value pairs

I'm trying to sort bellow array:

var joins = [
  {
    "joinType": "INNER JOIN",
    "joinTableName": "country",
    "joinColumnName": "id",
    "foreignTableName": "state",
    "foreignColumnName": "country_id",
    "index": 1
  },
  {
    "joinType": "INNER JOIN",
    "joinTableName": "state",
    "joinColumnName": "id",
    "foreignTableName": "city",
    "foreignColumnName": "state_id",
    "index": 2
  },
  {
    "joinType": "INNER JOIN",
    "joinTableName": "city",
    "joinColumnName": "id",
    "foreignTableName": "address",
    "foreignColumnName": "city_id",
    "index": 3
  },
  {
    "joinType": "INNER JOIN",
    "joinTableName": "address",
    "joinColumnName": "id",
    "foreignTableName": "user",
    "foreignColumnName": "address_id",
    "index": 4
  },
  {
    "joinType": "INNER JOIN",
    "joinTableName": "user_status",
    "joinColumnName": "id",
    "foreignTableName": "user",
    "foreignColumnName": "status_id",
    "index": 5
  }
]

by using bellow code:

joins.sort((a, b) => {
  if (a.foreignTableName === b.joinTableName) return 1; //b comes first
  else if (a.joinTableName === b.foreignTableName) return -1; //a comes first
  else return 0; //no change
});

The result is:

[
  {
    "joinType": "INNER JOIN",
    "joinTableName": "state",
    "joinColumnName": "id",
    "foreignTableName": "city",
    "foreignColumnName": "state_id",
    "index": 2
  },
  {
    "joinType": "INNER JOIN",
    "joinTableName": "country",
    "joinColumnName": "id",
    "foreignTableName": "state",
    "foreignColumnName": "country_id",
    "index": 1
  },
  {
    "joinType": "INNER JOIN",
    "joinTableName": "address",
    "joinColumnName": "id",
    "foreignTableName": "user",
    "foreignColumnName": "address_id",
    "index": 4
  },
  {
    "joinType": "INNER JOIN",
    "joinTableName": "city",
    "joinColumnName": "id",
    "foreignTableName": "address",
    "foreignColumnName": "city_id",
    "index": 3
  },
  {
    "joinType": "INNER JOIN",
    "joinTableName": "user_status",
    "joinColumnName": "id",
    "foreignTableName": "user",
    "foreignColumnName": "status_id",
    "index": 5
  }
]

This is not what I expected -- I expect that the elements with indexies 2 and 1 come after the element with index 3. What is wrong?

To add some more details, this is for creating a MySql query statement from an object field definition table, which defines the fields of a business object by using data from the underlayer DB of another production system. The above part is for creating the JOIN subclause.

PS, Here is what I want:

[
  {
    "joinType": "INNER JOIN",
    "joinTableName": "address",
    "joinColumnName": "id",
    "foreignTableName": "user",
    "foreignColumnName": "address_id",
    "index": 4
  },
  {
    "joinType": "INNER JOIN",
    "joinTableName": "city",
    "joinColumnName": "id",
    "foreignTableName": "address",
    "foreignColumnName": "city_id",
    "index": 3
  },
  {
    "joinType": "INNER JOIN",
    "joinTableName": "state",
    "joinColumnName": "id",
    "foreignTableName": "city",
    "foreignColumnName": "state_id",
    "index": 2
  },
  {
    "joinType": "INNER JOIN",
    "joinTableName": "country",
    "joinColumnName": "id",
    "foreignTableName": "state",
    "foreignColumnName": "country_id",
    "index": 1
  },
  {
    "joinType": "INNER JOIN",
    "joinTableName": "user_status",
    "joinColumnName": "id",
    "foreignTableName": "user",
    "foreignColumnName": "status_id",
    "index": 5
  }
]

Here you have one approach that first assigns a level to every object, the level is defined by the index of the root ancestor of an object and the level of deep that the object is inside the linked list it belong to. After adding this new lvl property, we just can sort using this new property. I don't believe this approach will be so good on performance, but maybe could fit for your needs.

 var joins = [ { "joinType": "INNER JOIN", "joinTableName": "country", "joinColumnName": "id", "foreignTableName": "state", "foreignColumnName": "country_id", "index": 1 }, { "joinType": "INNER JOIN", "joinTableName": "state", "joinColumnName": "id", "foreignTableName": "city", "foreignColumnName": "state_id", "index": 2 }, { "joinType": "INNER JOIN", "joinTableName": "city", "joinColumnName": "id", "foreignTableName": "address", "foreignColumnName": "city_id", "index": 3 }, { "joinType": "INNER JOIN", "joinTableName": "address", "joinColumnName": "id", "foreignTableName": "user", "foreignColumnName": "address_id", "index": 4 }, { "joinType": "INNER JOIN", "joinTableName": "user_status", "joinColumnName": "id", "foreignTableName": "user", "foreignColumnName": "status_id", "index": 5 } ]; // Find the object with the table a foreign key is referencing. const findParent = (fTable) => joins.find(x => x.joinTableName === fTable); // Recursive method that assigns a level to an object based on the position // they have on the linked list they belong to. const getLevel = (fTable, index, lvl) => { let parent = findParent(fTable); return (fTable && parent) ? getLevel(parent.foreignTableName, parent.index, lvl + 1) : index + "-" + lvl; } // Maps the input data to adds the level property to each object. let newInput = joins.map(obj => { obj.lvl = getLevel(obj.foreignTableName, obj.index, 0); return obj; }); // Sorts the new generated data based on the level property. Since the // lvl property is a string, we use "localeCompare()" to compare. let sortedInput = newInput.sort((a, b) => a.lvl.localeCompare(b.lvl)); // Shows the sorted data. console.log(sortedInput);

As I don't know the mechanism of Array.prototype.sort , I tried the same logic with bubbleSort and print the steps and found the problem.

At the initial of each loop, the bubbleSort takes a leftmost element in the unsorted array as the "bubble", and tries to swap it to one step right at a time, if the element is smaller than it's right-neighbor, then takes the right-neighbor as the bubble --thus when the bubble goes to the rightmost it contains the biggest among the un-sorted elements.

My problem is, the element in my target set is not always comparable to another, so I cannot bubble the "biggest" to the rightmost --there is no such "biggest". the elements in my target set are "partially" orderable: I can set orders among some of them, but not all.

With this in mind, I come with an idea: I should sort those orderable elements into segments/chains and then merge them. Bellow, I call it mergeSort (I know there is a famous merge-sort but I cannot remember its mechanism so mine can be inconsistent with the typical merge-sort).

function mergeSort(arr, compFn) {
  let res = [];
  while (arr.length > 0) {
    res = res.concat(makeChain(arr.splice(0, 1)[0], compFn));
  }
  return res.filter(n => n);

  function makeChain(obj, compFn) {
    let res = [obj];
    for (let i = 0; i < arr.length; i++) {
      if (isEmpty(arr[i])) return;
      let flag = compFn(obj, arr[i]);
      if (flag < 0) {
        res = res.concat(makeChain(arr.splice(i, 1)[0], compFn));
      } else if (flag > 0) {
        res = makeChain(arr.splice(i, 1)[0], compFn).concat(res);
      }
    }
    return res;
  }

}

Then I can still use the same compareFunction:

joins = mergeSort(joins, (a, b) => {
  if (a.foreignTableName === b.joinTableName) return 1; //b comes first
  else if (a.joinTableName === b.foreignTableName) return -1; //a comes first
  else return 0; //no change
});

This generated the sorted array I expected.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM