First I tried passing my own function to Array.sort
, but it doesn\'t sort correctly. Notice how \'c\'
comes before \'a\'
in the resul
Maybe something like this
const sorted = ['a', 'b', 'c', 'd']; // I do NOT have access to this
const unsorted = ['c', 'd', 'a', 'b'];
const a_before_b = (a, b) => {
if (a == 'a' && b == 'd') return true;
if (a == 'b' && b == 'c') return true;
if (a == 'a' && b == 'c') return true;
}
const b_before_a = (a, b) => {
if (b == 'a' && a == 'c') return true;
if (b == 'b' && a == 'c') return true;
}
const mySortingFunction = (a, b) => {
if (a_before_b(a, b)) return -1;
if (b_before_a(a, b)) return 1;
return 0;
}
// doesn't produce correct sorting
console.log(unsorted.sort(mySortingFunction));
The algorithm in the answer I gave earlier on, and which you (first) accepted, is really based on a heuristic.
For a sorted output to be guaranteed to not have any violations, you could treat this problem as a graph problem. Whenever two values can make a comparison that gives true
(with either comparator function), then that pair represents an edge in the graph.
If the order is consistent, then there must be one value that is the least among the others, otherwise you would have a cycle.
So with that knowledge we can determine for each node in the graph how long the longest path is to such a least node. When you find the longest distance to such a least node, you can use the length of that path as an absolute order indication.
Here is an implementation:
class Node {
constructor(value) {
this.value = value;
this.prev = new Set;
this.order = 0; // No order yet
}
orderWith(other) {
if (other === this) return;
if (a_before_b(this.value, other.value) || b_before_a(other.value, this.value)) {
other.prev.add(this);
} else if (a_before_b(other.value, this.value) || b_before_a(this.value, other.value)) {
this.prev.add(other);
}
}
setOrder(path = new Set) {
// Use recursion to find length of longest path to "least" node.
if (this.order) return; // already done
if (path.has(this)) throw "cycle detected";
let order = 1;
for (let prev of this.prev) {
prev.setOrder(path.add(this));
order = Math.max(order, prev.order + 1);
}
this.order = order; // If order is 1, it is a "least" node
}
}
const a_before_b = (a, b) => {
if (a == 'a' && b == 'd') return true;
if (a == 'b' && b == 'c') return true;
}
const b_before_a = (a, b) => {
if (b == 'a' && a == 'c') return true;
if (b == 'b' && a == 'c') return true;
}
function mySort(arr) {
// Create a graph: first the nodes
let nodes = {}; // keyed by values in arr
for (let value of arr) nodes[value] = nodes[value] || new Node(value);
// Then the edges...
for (let i = 0; i < arr.length; i++) {
for (let j = i+1; j < arr.length; j++) {
nodes[arr[i]].orderWith(nodes[arr[j]]);
}
}
// Set absolute order, using the longest path from a node to a "least" node.
for (let node of Object.values(nodes)) node.setOrder();
// Sort array by order:
return arr.sort((a, b) => nodes[a].order - nodes[b].order);
}
const sorted = ['a', 'b', 'c', 'd'];
const unsorted = ['c', 'd', 'a', 'b'];
console.log(mySort(unsorted));
const unsorted = ['c', 'd', 'a', 'b'];
const sorted = unsorted.sort();
It should work I'm not sure what's your issue.