Mistake in SortTable - javascript

I have problem with sort column "wins" in javascript. I don't know why 19 is in the middle, it should be last or first. Every number from 1 to 9 are sorted correct. Below are screen after sort and my code.
/**
* Sorts a HTML table.
*
* #param {HTMLTableElement} table The table to sort
* #param {number} column The index of the column to sort
* #param {boolean} asc Determines if the sorting will be in ascending
*/
function sortTableByColumn(table, column, asc = true) {
const dirModifier = asc ? 1 : -1
const tBody = table.tBodies[0]
const rows = Array.from(tBody.querySelectorAll('tr'))
// Sort each row
const sortedRows = rows.sort((a, b) => {
const aColText = a.querySelector(`td:nth-child(${column + 1})`).textContent.trim()
const bColText = b.querySelector(`td:nth-child(${column + 1})`).textContent.trim()
console.log(aColText)
console.log(bColText)
return aColText > bColText ? 1 * dirModifier : -1 * dirModifier
})
// Remove all existing TRs from the table
while (tBody.firstChild) {
tBody.removeChild(tBody.firstChild)
}
// Re-add the newly sorted rows
tBody.append(...sortedRows)
// Remember how the column is currently sorted
table.querySelectorAll('th').forEach(th => th.classList.remove('th-sort-asc', 'th-sort-desc'))
table.querySelector(`th:nth-child(${column + 1})`).classList.toggle('th-sort-asc', asc)
table.querySelector(`th:nth-child(${column + 1})`).classList.toggle('th-sort-desc', !asc)
}
export function activeSort() {
document.querySelectorAll('.toSort').forEach(headerCell => {
headerCell.addEventListener('click', () => {
const tableElement = headerCell.parentElement.parentElement.parentElement
const headerIndex = Array.prototype.indexOf.call(headerCell.parentElement.children, headerCell)
const currentIsAscending = headerCell.classList.contains('th-sort-asc')
sortTableByColumn(tableElement, headerIndex, !currentIsAscending)
})
})
}

Your sorting stringss which are sorted lexicographically. If you want to sort by numbers parse your string to a number first e.g. using Number() or Number.parseInt().
const numberStrings = ["2", "19", "1"]
const numbers = [2, 19, 1];
const sortArray = (array) => {
array.sort((a, b) => a > b ? 1: -1); // here you're working with strings
return array;
};
console.log(sortArray(numberStrings));
console.log(sortArray(numbers));
By the way: one of the interesting bits of working with JavaScript will make the following work.
const numberStrings = ["2", "19", "1"]
const numbers = [2, 19, 1];
const sortArray = (array) => {
array.sort((a, b) => a - b); // here JS will convert your strings to numbers and do the calculation
return array;
};
console.log(sortArray(numberStrings));
console.log(sortArray(numbers));
JavaScript will automatically do the conversion for you when using - as this is the only way the - operator can be interpreted in that case.
<, > or === however have different semantic meaning (lexicographic ordering vs. natural order of numbers) for numbers and strings, therefore your solution won't work.
const resultStrings = "1" - "2";
console.log(resultStrings);
const resultNumbers = 1 - 2;
console.log(resultNumbers);
When we exchange the - with a plus however, this won't work, as + for strings means concatenation.
const resultStrings = "1" + "2";
console.log(resultStrings);
const resultNumbers = 1 + 2;
console.log(resultNumbers);

Related

How can I create a numeric array based on the number of array objects in React?

I have an array object called members?.admins. I want to create an array of numbers based on the length of members?.admins.
As an exception, if the length of members?.admins is 1 to 5, [1] should be given as the default value. That is, the default value must be unconditionally [1].
If the number of members?.admins is 5, I want to make [1] in numbers, and if there are 10, I want to make [1,2]. Also, if there are 11, it should be [1,2]
At this time, I want to use useEffect to setNumbers on the first render to create an array of numbers in numbers.
How can I do that?
const members?.admins = [{memberId:"21",name:"jack21"},{memberId:"20",name:"jack20"},{memberId:"14",name:"jack14"},{memberId:"13",name:"jack13"},{memberId:"11",name:"jack11"},{memberId:"10",name:"jack10"},{memberId:"7",name:"jack7"},{memberId:"4",name:"jack4"},{memberId:"3",name:"jack3"},{memberId:"2",name:"jack2"},{memberId:"1",name:"jack1"}];
const [numbers, setNumbers] = useState([1]);
useEffect(() => {
setNumbers();
}, []);
Expected output:
if members?.admins.length = 1~5
it shuld be
number = [1]
if members?.admins.length = 6~10
it shuld be
number = [1,2]
if members?.admins.length = 11~15
it shuld be
number = [1,2,3]
let members = {admins: [1,2,3,4,5,6,7,8,9,10]}
console.log(Array(Math.ceil((members?.admins.length || 0) / 5)).fill(undefined).map((el, i) => i + 1))
let admins = ["a","b","c","d","e","f","g"];
let result = Array.from({length: Math.ceil(admins.length / 5)}, (_, i) => i + 1);
console.log(result);
You can use Math.ceil to get the required length and then fill the array later.
const members = {
admins: Array(6)
}
const reqLength = Math.ceil(members?.admins?.length / 5);
const reqArray = Array(reqLength).fill().map((v, i) => i + 1);
console.log(reqArray);
Here you can create array using length
useEffect(() => {
const array = [...Array(Math.ceil( members?.admins.length / 5))].map((_, i)=> i+1)
setNumbers(array);
}, []);
const members?.admins = [{memberId:"21",name:"jack21"},{memberId:"20",name:"jack20"},{memberId:"14",name:"jack14"},{memberId:"13",name:"jack13"},{memberId:"11",name:"jack11"},{memberId:"10",name:"jack10"},{memberId:"7",name:"jack7"},{memberId:"4",name:"jack4"},{memberId:"3",name:"jack3"},{memberId:"2",name:"jack2"},{memberId:"1",name:"jack1"}];
const [numbers, setNumbers] = useState([1]);
useEffect(() => {
let number = [];
let mb_length = members?.admins.length;
if(mb_length%5 === 0){
let mbb = (mb_length - mb_length%5)/5;
number = Array.from({length: mbb}, (_, i) => i + 1)
}else{
let mbb = (mb_length - mb_length%5)/5;
number = Array.from({length: mbb+1}, (_, i) => i + 1)
}
setNumbers(number);
}, []);

Calculate median for an array of objects

I have an array of objects:
const bookDetails = [{"author":"john","readingTime":12123},
{"author":"romero","readingTime":908},
{"author":"romero","readingTime":1212},
{"author":"romero","readingTime":50},
{"author":"buck","readingTime":1902},
{"author":"buck","readingTime":12125},
{"author":"romero","readingTime":500},
{"author":"john","readingTime":10},
{"author":"romero","readingTime":230},
{"author":"romero","readingTime":189},
{"author":"legend","readingTime":12}
{"author":"john","readingTime":1890}]
I tried calculating the median for each author. Here is my function that calculates median for a given array:
//To calculate the median for a given array
function medianof2Arr(arr1) {
var concat = arr1;
concat = concat.sort(function (a, b) { return a - b });
var length = concat.length;
if (length % 2 == 1) {
// If length is odd
return concat[(length / 2) - .5]
} else {
return (concat[length / 2] + concat[(length / 2) - 1]) / 2;
}
}
But I want to calculate the median for each author separately. How can I do that?
Expected output
{"john": 1890, "romero": 365, "buck": 7014, "legend": 12}
Can you please try this
let bookDetails = [
{"author":"john","readingTime":12123},
{"author":"romero","readingTime":908},
{"author":"romero","readingTime":1212},
{"author":"romero","readingTime":50},
{"author":"buck","readingTime":1902},
{"author":"buck","readingTime":12125},
{"author":"romero","readingTime":500},
{"author":"john","readingTime":10},
{"author":"romero","readingTime":230},
{"author":"romero","readingTime":189},
{"author":"legend","readingTime":12},
{"author":"john","readingTime":1890}
];
const authorMap = bookDetails.reduce((acc, book) => {
acc[book.author] ? acc[book.author].push(book.readingTime) : acc[book.author] = [book.readingTime]
return acc;
}, {})
calculateMedian = (list) => {
const sortedList = list.sort((a, b) => a - b);
console.log(sortedList[Math.floor(sortedList.length / 2)]); // You might need to tweak this
}
for (let author in authorMap) {
calculateMedian(authorMap[author])
}
You could first group your input by author, to get this data structure:
{
"john": [12123, 10, 1890],
"romero": [908, 1212, 50, 500, 230, 189],
"buck": [1902, 12125],
"legend": [12]
}
And then you could call the median function on all those arrays and replace those arrays with the values you get back from the calls:
function median(arr) {
arr = [...arr].sort((a, b) => a - b);
let mid = arr.length >> 1;
return arr.length % 2 ? arr[mid] : (arr[mid-1] + arr[mid]) / 2;
}
const bookDetails = [{"author":"john","readingTime":12123}, {"author":"romero","readingTime":908}, {"author":"romero","readingTime":1212}, {"author":"romero","readingTime":50}, {"author":"buck","readingTime":1902}, {"author":"buck","readingTime":12125}, {"author":"romero","readingTime":500},{"author":"john","readingTime":10},{"author":"romero","readingTime":230}, {"author":"romero","readingTime":189}, {"author":"legend","readingTime":12},{"author":"john","readingTime":1890}];
// Create a key for each author, and link them with an empty array
let result = Object.fromEntries(bookDetails.map(({author}) => [author, []]));
// Populate those arrays with the relevant reading times
for (let {author, readingTime} of bookDetails) result[author].push(readingTime);
// Replace those arrays with their medians:
for (let author in result) result[author] = median(result[author]);
console.log(result);
Note that the median for buck is not an integer 7014 as in your expected output, but 7013.5

An algorithm to find the closest values in javascript array

I'm working on a small algorithm to find the closest values of a given number in an random array of numbers. In my case I'm trying to detect connected machines identified by a 6-digit number ID ("123456", "0078965", ...) but it can be useful for example to find the closest geolocated users around me.
What I need is to list the 5 closest machines, no matter if their IDs are higher or lower. This code works perfectly but I'm looking for a smarter and better way to proceed, amha I got to much loops and arrays.
let n = 0; // counter
let m = 5; // number of final machines to find
// list of IDs founded (unordered: we can't decide)
const arr = ["087965","258369","885974","0078965","457896","998120","698745","399710","357984","698745","789456"]
let NUM = "176789" // the random NUM to test
const temp = [];
const diff = {};
let result = null;
// list the [m] highest founded (5 IDs)
for(i=0 ; i<arr.length; i++) {
if(arr[i] > NUM) {
for(j=0 ; j<m; j++) {
temp.push(arr[i+j]);
} break;
}
}
// list the [m] lowest founded (5 IDs)
for(i=arr.length ; i>=0; i--) {
if(arr[i] < NUM) {
for(j=m ; j>=0; j--) {
temp.push(arr[i-j]);
} break;
}
}
// now we are certain to get at least 5 IDs even if NUM is 999999 or 000000
temp.sort(function(a, b){return a - b}); // increase order
for(i=0 ; i<(m*2); i++) {
let difference = Math.abs(NUM - temp[i]);
diff[difference] = temp[i]; // [ 20519 : "964223" ]
}
// we now get a 10-values "temp" array ordered by difference
// list the [m] first IDs:
for(key in diff){
if(n < m){
let add = 6-diff[key].toString().length;
let zer = '0'.repeat(add);
let id = zer+diff[key]; // "5802" -> "005802"
result += (n+1)+":"+ id +" ";
n+=1;
}
}
alert(result);
-> "1:0078965 2:087965 3:258369 4:357984 5:399710" for "176789"
You actually don't need to have so many different iterations. All you need is to loop twice:
The first iteration attempt is to use .map() to create an array of objects that stores the ID and the absolute difference between the ID and num
The second iteration attempt is simply to use .sort() through the array of objects created in step 1, ranking them from lowest to highest difference
Once the second iteration is done, you simply use .slice(0, 5) to get the first 5 objects in the array, which now contains the smallest 5 diffs. Iterate through it again if you want to simply extract the ID:
const arr = ["087965","258369","885974","078965","457896","998120","698745","399710","357984","698745","789456"];
let num = "176789";
let m = 5; // number of final machines to find
// Create an array of objects storing the original arr + diff from `num`
const diff = arr.map(item => {
return { id: item, diff: Math.abs(+item - +num) };
});
// Sort by difference from `num` (lowest to highest)
diff.sort((a, b) => a.diff - b.diff);
// Get the first m entries
const filteredArr = diff.slice(0, m).map(item => item.id).sort();
// Log
console.log(filteredArr);
// Completely optional, if you want to format it the way you have in your question
console.log(`"${filteredArr.map((v, i) => i + ": " + v).join(', ')}" for "${num}"`);
You could take an array as result set, fill it with the first n elements and sort it by the delta of the wanted value.
For all other elements check if the absolute delta of the actual item and the value is smaller then the last value of the result set and replace this value with the actual item. Sort again. Repeat until all elements are processed.
The result set is ordered by the smallest delta to the greatest by using the target value.
const
absDelta = (a, b) => Math.abs(a - b),
sortDelta = v => (a, b) => absDelta(a, v) - absDelta(b, v),
array = [087965, 258369, 885974, 0078965, 457896, 998120, 698745, 399710, 357984, 698745, 789456],
value = 176789,
n = 5,
result = array.reduce((r, v) => {
if (r.length < n) {
r.push(v);
r.sort(sortDelta(value));
return r;
}
if (absDelta(v, value) < absDelta(r[n - 1], value)) {
r[n - 1] = v;
r.sort(sortDelta(value));
}
return r;
}, []);
console.log(result); // sorted by closest value
A few good approaches so far, but I can't resist throwing in another.
This tests a sliding window of n elements in a sorted version of the array, and returns the one whose midpoint is closest to the value you're looking for. This is a pretty efficient approach (one sort of the array, and then a single pass through that) -- though it does not catch cases where there's more than one correct answer (see the last test case below).
const closestN = function(n, target, arr) {
// make sure we're not comparing strings, then sort:
let sorted = arr.map(Number).sort((a, b) => a - b);
target = Number(target);
let bestDiff = Infinity; // actual diff can be assumed to be lower than this
let bestSlice = 0; // until proven otherwise
for (var i = 0; i <= sorted.length - n; i++) {
let median = medianOf(sorted[i], sorted[i+n-1]) // midpoint of the group
let diff = Math.abs(target - median); // distance to the target
if (diff < bestDiff) { // we improved on the previous attempt
bestDiff = diff; // capture this for later comparisons
bestSlice = i;
}
// TODO handle diff == bestDiff? i.e. more than one possible correct answer
}
return sorted.slice(bestSlice, bestSlice + n)
}
// I cheated a bit here; this won't work if a > b:
const medianOf = function(a, b) {
return (Math.abs(b-a) / 2) + a
}
console.log(closestN(5, 176789, ["087965", "258369", "885974", "0078965", "457896", "998120", "698745", "399710", "357984", "698745", "789456"]))
// more test cases
console.log(closestN(3, 5, [1,2,5,8,9])) // should be 2,5,8
console.log(closestN(3, 4, [1,2,5,8,9])) // should be 1,2,5
console.log(closestN(1, 4, [1,2,5,8,9])) // should be 5
console.log(closestN(3, 99, [1,2,5,8,9])) // should be 5,8,9
console.log(closestN(3, -99, [1,2,5,8,9])) // should be 1,2,5
console.log(closestN(3, -2, [-10, -5, 0, 4])) // should be -5, 0, 4
console.log(closestN(1, 2, [1,3])) // either 1 or 3 would be correct...

Fallback value required for if array returns 0 values

I'm calculating a minimum value for the base of a graph output. One of my arrays are outputting a zero value thus not meeting the requirement and returning undefined.
const data = [
{value:0},
{value:0},
{value:0} ];
const defaultMinThreshold = 0;
const minThreshold = _.min([parseFloat(_.minBy(_(data).filter(d => d.value > 0).value(), 'value').value) - 0.5, defaultMinThreshold]);
If the data had any value larger than 0 e.g.
const data = [
{value:1},
{value:0},
{value:0} ];
This returns minThreshold as 0 - which is expected. I need it to return 0 instead of returning undefined when/if all the values are 0.
I need to put a conditional in that searches data for values that at least has 1 value larger than 0 else return 0 by default.
JS Fiddle - https://jsfiddle.net/0cps7q9j/
n.b As you can see I do filter the values to be > 0, I could do a >= 0 but this lowers the base to 0 for arrays that could have a zero value in that data set so can't change this.
Reason for the filter is if the data had values of
const data = [
{value:4},
{value:4},
{value:3} ];
With above the minimum value would return 3, so the threshold would be 3 and the graph would start from 3 rather than zero.
If you don't have to completely use Lodash, here is something that is functional and readable. You could probably transform this into a pure Lodash solution as well if you like.
https://jsfiddle.net/0cps7q9j/11/
const data = [
{value:-1},
{value:'0'},
{value:1.234} ];
const defaultMinThreshold = 0;
const filteredDataValueArray = data.filter(obj => !isNaN(parseFloat(obj.value)) && parseFloat(obj.value) >= 0).map(obj => obj.value);
const minThreshold = _.min(filteredDataValueArray) || defaultMinThreshold;
console.log('filteredDataValueArray', filteredDataValueArray);
console.log('minThreshold', minThreshold);
In my opinion, your code would be easier to understand written like this:
const values = data.map(d => d.value);
const hasNonZero = values.some(v => v > 0);
const minThreshold = hasNonZero ? _.min(values) : 0;
I'm only using lodash for the _.min which is convenient here. You could also replace this with Math.min(...values) or Math.min.apply(null, values)
Testing
const dataSets = [
[
{ value: 0 },
{ value: 0 },
{ value: 0 }
],
[
{ value: 1 },
{ value: 0 },
{ value: 0 }
],
[
{ value: 4 },
{ value: 4 },
{ value: 3 }
]
];
function getMinThreshold(data) {
const values = data.map(d => d.value);
const hasNonZero = values.some(v => v > 0);
const minThreshold = hasNonZero ? Math.min(...values) : 0;
return minThreshold;
}
dataSets.forEach(data => console.log(getMinThreshold(data)))
A concise way with lodash by using _.minBy:
const data = [ [ { value: 0 }, { value: 0 }, { value: 0 } ], [ { value: 1 }, { value: 0 }, { value: 0 } ], [ { value: 4 }, { value: 4 }, { value: 3 } ] ];
const result = _.map(data, x => _.minBy(x, 'value').value)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
Let's break up the logic to present better what's happening. I've extracted the code and broken up each operation on a separate line so it's easier to see:
const data = [
{value:0},
{value:0},
{value:0} ];
const defaultMinThreshold = 0;
const filterResult = _(data).filter(d => d.value > 0).value();
const minByResult = _.minBy(filterResult, 'value');
const minValue = minByResult.value;
const parsedValue = parseFloat(minValue);
const processedValue = parsedValue - 0.5;
const pair = [processedValue, defaultMinThreshold]
const minThreshold = _.min(pair);
First, I am going to address some problems and talk about some simplifications that can be done.
_(data).filter(d => d.value > 0).value()
This is a bit needless and also inconsistent with the other invocations of lodash. It's better to do _.filter(data, d => d.value > 0) - it's both shorter and consistent.
const filterResult = _(data).filter(d => d.value > 0).value();
const minByResult = _.minBy(filterResult, 'value');
const minValue = minByResult.value;
All three lines only work with the value property and finally extract the number from it. Since there is no need to keep working on objects, you can extract value and work with it directly. The entire section turns into
const mapResult = _.map(data, 'value');
const filterResult = _.filter(mapResult, d => d > 0);
const minValue = _.min(filterResult);
In addition the entire problem is with minValue - if you have an empty array and you try to use _.min or _.minBy on it, you would get undefined. I will come back to that.
const parsedValue = parseFloat(minValue);
This is unneeded. minValue would already be a number. JS has a single number type an it's already float - there is no "casting" from integer to float or vice versa. So, there is no need for parsing. The entire line can be removed.
const processedValue = minValue - 0.5;
So, knowing that minValue can be undefined, there are some things that can be done here to amend that. You can check for undefined perhaps by using a ternary minValue ? minValue : 0 - 0.5 but I find it easier to always ensure you return a number from _.min, even if it tries to process an empty array.
const minValue = _.min(filterResult) || 0; //if the first value is falsey (undefined), return zero
const processedValue = minValue - 0.5; //you might end up with a result of -0.5
const clampedValue = _.clamp(processedValue, 0, Infinity); //ensures the value is [0,) - zero (inclusive) to unbound maxiumum
Putting all together you get
const data = [
{value:0},
{value:0},
{value:0} ];
const defaultMinThreshold = 0;
const mapResult = _.map(data, 'value');
const filterResult = _.filter(mapResult, d => d > 0);
const minValue = _.min(filterResult) || 0;
const processedValue = minValue - 0.5;
const clampedValue = _.clamp(processedValue, 0, Infinity);
const pair = [clampedValue, defaultMinThreshold];
const minThreshold = _.min(pair);
console.log(minThreshold)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>
This is a bit verbose and uses a lot of variables only to pass them to the next function. You can cut them out in a couple of ways. First option is by using chaining
const data = [
{value:0},
{value:0},
{value:0} ];
const defaultMinThreshold = 0;
var processedValue = _.chain(data)
.map('value')
.filter(d => d > 0)
.sort()
.take(1) //min() breaks the array chaining, so doing sort() -> take(1) ensures we can continue
.map(x => x - 0.5) //not checking for "undefined" because an empty array won't be further processed
.map(_.clamp) //the second and third argument to _clamp are optional and omitting them is the same as passing 0 and Infinity
.value()[0];
//this will be undefined because we're getting index zero of an empty array
console.log("processedValue", processedValue);
const pair = [processedValue, defaultMinThreshold];
//the minimum of undefined and zero is zero. So you don't need extra checks
const minThreshold = _.min(pair);
console.log("minThreshold", minThreshold)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>
I left the clamp in this method but it might be unneeded. Since an array of zeroes is taken care of automatically, the only way you'd get a result of less than zero is if you have a minimum value of, say 0.3 so when you subtract 0.5 from it, you will end up with a negative. If that's not a possible input, you can omit the clamp.
The above will be operating on an array at all times until it finishes and gets the value. An alternative is to actually use .min() which, as I mentioned, breaks the array chaining, since it returns a single value. That means that you cannot use .map() any more which I personally find a bit inconsistent. Just to illustrate what happens then, here how to operate on a single value.
const data = [
{value:0},
{value:0},
{value:0} ];
const defaultMinThreshold = 0;
var processedValue = _.chain(data)
.map('value')
.filter(d => d > 0)
.min()
.thru(x => x - 0.5) //if using .min() then you can use .thru to manipulate the single value
.thru(_.clamp)
.value();
//in this case, we get NaN because of undefined - 0.5
console.log("processedValue", processedValue);
const pair = [processedValue, defaultMinThreshold];
//the minimum of NaN and zero is still zero
const minThreshold = _.min(pair);
console.log("minThreshold", minThreshold)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>
Another way to achieve the same is doing a processing pipe of functions instead of operating on the value each time like with _.chain. This uses the functional form of Lodash for brevity but you can use normal Lodash if you pass functions through _.rearg and _.curry. At any rate, here is what this looks like:
const data = [
{value:0},
{value:0},
{value:0} ];
const defaultMinThreshold = 0;
const processChain = _.flow( //compose all functions together
_.map('value'),
_.filter(d => d > 0),
_.min,
x => x || 0,
x => x - 0.5,
_.clamp(0)(Infinity)
);
const processedValue = processChain(data);
console.log("processedValue", processedValue)
const pair = [processedValue, defaultMinThreshold];
const minThreshold = _.min(pair);
console.log("minThreshold", minThreshold)
<script src="https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)"></script>
This is actually very much closer in intent to how both your code worked and my initial breakdown. The way _.flow works it to make essentially the following
aResult = a(input);
bResult = b(aResult)
cResult = c(bResult)
and so on, with the exception that it defines the process, you can then later supply input which will, in turn, be subsequently passed around.

How do I sort taking greater than and less than into consideration?

I need to have a sort on two strings take the > and < symbols into consideration. So, for example, the sort might look like
<20
<40
<100
0.1
10
1,000,000.75
>100
>1,000
So basically all the strings with < are first, followed by a normal sort, followed by all numbers with a > symbol. I'd also like the sort to respect the exact order shown (e.g. >100 appears before >1,000 when sorted low to high)
Here is my code that works without the symbols (sorting all rows in a table):
if ($this.hasClass('sort-mixed')) {
sort_func = sort_mixed;
}
$rows.sort(sort_func);
function sort_mixed(a, b) {
var val_a = $(a).children().eq(column_index).text();
var val_b = $(b).children().eq(column_index).text();
val_a = Number(val_a.toString().replace(/,/g, ""));
val_b = Number(val_b.toString().replace(/,/g, ""));
if(val_a > val_b) {
return 1 * sort_direction;
}
if(val_a < val_b) {
return -1 * sort_direction;
}
return 0;
}
Here is not a complete solution but enough to get you started. We'll break the array into multiple parts, sort each part, then put the array back together.
function toNumber(s) {
return +s.replace(/[^0-9.]/g, '')
}
var arr = [
'<20',
'>1,000',
'1,000,000.75',
'<40',
'0.1',
'10',
'<100',
'>100'
];
var lt = arr
.filter(s => s.startsWith('<'))
.map(s => s.slice(1))
.map(toNumber)
.sort((a, b) => a - b)
.map(n => '<' + n);
var eq = arr
.filter(s => !s.startsWith('>') && !s.startsWith('<'))
.map(toNumber)
.sort((a, b) => a - b)
.map(n => '' + n);
var gt = arr.filter(s => s.startsWith('>'))
.map(s => s.slice(1))
.map(toNumber)
.sort((a, b) => a - b)
.map(n => '>' + n);
console.log([].concat(lt, eq, gt));
Outputs:
["<20", "<40", "<100", "0.1", "10", "1000000.75", ">100", ">1000"]
Sort with a single comparator function:
const order = { regular: 1, reverse: -1 };
let sortOrder = order.regular;
let strings = ['0.1', '1,000,000.75', '10', '<100', '<20', '<40', '>1,000', '>100'];
function compare(a, b) {
function toNum(str) { return +str.replace(/[^0-9.-]/g, ''); }
function range(str) { return { '<': -1, '>': 1 }[str[0]] || 0; }
const rangeA = range(a);
const rangeB = range(b);
const score = rangeA === rangeB ? toNum(a) - toNum(b) : rangeA - rangeB;
return score * sortOrder;
}
strings.sort(compare);
The function range() checks if the string starts with a '<' or '>' and sets a value that is used for sorting if the strings have different ranges. Otherwise, the strings are converted to numbers and simply sorted as numbers.
With the example input data, the resulting strings array is:
["<20", "<40", "<100", "0.1", "10", "1,000,000.75", ">100", ">1,000"]
Fiddle with the code:
https://jsfiddle.net/fxgt4uzm
Edited:
Added sortOrder.
Composition approach
// Sort function maker
// - create a sort fn based on two compare fns
// {f}= primary
// {g}= secondary
const sort = (f, g) => (a, b) => f(a,b) || g(a,b)
// Compare function maker
// - create a compare fn based on a weighting fn, {w}
const cmp_asc_of = (w) => (a, b) => w(a) - w(b)
const cmp_desc_of = (w) => (a, b) => w(b) - w(a)
// Weighting function
// - weight a given string, {string}, returns a number
const weight_op = (string) => ..
const weight_num = (string) => ..
// Then, create sort functions
const asc = sort(cmp_asc_of(weight_op), cmp_asc_of(weight_num))
const desc = sort(cmp_asc_of(weight_op), cmp_desc_of(weight_num))
// Usage
array.sort(asc)
array.sort(desc)
Demo
For your case..
..
function sort_mixed(a, b) {
var val_a = ..
var val_b = ..
return isHighToLow ? desc(val_a, val_b) : asc(val_a, val_b)

Categories

Resources