对包含负数、零、空字符串和正数的数组进行排序



我有一个包含负数、空字符串''、零0和正数的数组。我想对数组进行排序,以便空字符串''始终显示在末尾。

[-1, 2, '', -2, 0, 4, '', 1,5,0, -5, 7, 11, '',3]

和预期输出

[-5, -2, -1, 0, 0, 1, 2, 3, 4, 5, 7, 11, '', '', '']

您可以在排序条件中优先比较空字符串。

let arr = [-1, 2, '', -2, 0, 4, '', 1,5,0, -5, 7, 11, '',3];
arr.sort((a, b) => (a === '') - (b === '') || a - b);
console.log(arr);

您可以编写一个自定义排序函数,以便将空字符串移动到数组的后面
[...arr]如果您不想改变原始数组,因为排序会改变原始数组。

const arr = [-1, 2, '', -2, 0, 4, '', 1,5,0, -5, 7, 11, '',3];
const sorted = [...arr].sort((a, b) => {
if (a === '') return 1;   //a after b
if (b === '') return -1;  //b after a
return a - b;             //number sorting
});
//or shorten using ternary operator
//const sorted = [...arr].sort((a, b) => a === '' ? 1 : b === '' ? -1 : a - b);
console.log(sorted);

如果b是空的,把它放在前面;但如果a是空的,把它放在最后。

如果ab都为非空,则执行标准a - b返回。

const arr = [-1, 2, '', -2, 0, 4, '', 1, 5, 0, -5, 7, 11, '', 3];
arr.sort((a, b) => {
if (b === '') return -1;
if (a === '') return 1;
return a - b;
});
console.log(JSON.stringify(arr));

如果要始终将空值保留在末尾,则需要跟踪排序方向:

const
DIRECTION_ASC = 1,
DIRECTION_DESC = -1,
customSort = (direction = DIRECTION_ASC) => {
const dir = direction === DIRECTION_DESC ? -1 : 1;
return (a, b) => {
if (b === '') return -1;
if (a === '') return 1;
return (a - b) * dir;
};
};
const
custSortAsc = customSort(),
custSortDesc = customSort(DIRECTION_DESC),
sortTextJsonArr = (textarea, sorter) => {
textarea.value = JSON.stringify(
JSON.parse(textarea.value.replace(/'/g, '"'))
.sort(sorter))
.replace(/"/g, "'")
.replace(/,/g, ', ');
},
sortAsc = (selector) =>
sortTextJsonArr(document.querySelector(selector), custSortAsc),
sortDesc = (selector) =>
sortTextJsonArr(document.querySelector(selector), custSortDesc);
body, textarea { text-align: center; }
<textarea id="data" rows="2" cols="60">
[-1, 2, '', -2, 0, 4, '', 1, 5, 0, -5, 7, 11, '', 3]
</textarea>
<br />
<button onclick="sortAsc('#data')">Sort Ascending</button>
<button onclick="sortDesc('#data')">Sort Descending</button>

我个人会定义一些通用的排序帮助程序,这些帮助程序在各种sort()场景中都很有用。

让我们先看看答案,不知道助手是做什么的:

import { fallThrough, by, asc, desc, front, back } from './lib/sort-helpers';
const items = [-1, 2, '', -2, 0, 4, '', 1,5,0, -5, 7, 11, '',3];
items.sort(fallThrough(
back(item => typeof item === "string"),
asc,
));
console.log(items);

function solution({ fallThrough, by, asc, desc, front, back, itself }) {
const items = [-1, 2, '', -2, 0, 4, '', 1,5,0, -5, 7, 11, '',3];
items.sort(fallThrough(
back(item => typeof item === "string"),
asc,
));
console.log(items);
}
const SortHelpers = (function () {
const fallThrough = (...comparators) => (a, b) => (
comparators.reduce((diff, comparator) => diff || comparator(a, b), 0)
);
const by = (fn, comparator = asc) => (a, b) => comparator(fn(a), fn(b));
const asc = (a, b) => -(a < b) || +(a > b);
const desc = (a, b) => asc(b, a);
const bool = (fn) => (item) => !!fn(item);
const front = (fn) => by(bool(fn), desc);
const back = (fn) => by(bool(fn), asc);

return { fallThrough, by, asc, desc, front, back };
})();
solution(SortHelpers);

如您所见,此解决方案看起来相对简单。请注意,asc的第二个排序标准将同时应用于数字和字符串。因此,如果字符串为非空,它们也将按升序排序。

助手

现在,让我们解释一下通用帮助程序(请参阅折叠的代码段)。

我将经常使用比较一词。比较器是一个接受 2 个参数的函数,然后返回一个数字来告诉sort()应该如何对 2 个参数进行排序。

负值表示第一个参数应放在第二个参数之前。正值表示第一个参数应放在第二个参数之后。零表示两个参数可以被视为相等。

比较器的一个基本示例可以在asc的解释中看到。

fallThrough

fallThrough允许我们传递多个比较器函数。如果第一个比较器说值相等,则调用下一个比较器。如果第二个比较器也说值相等,我们将继续第三个比较器。

这种情况一直持续到其中一个比较器说出哪个值先出现。如果没有更多可用的比较器,则认为这些值相等。

下面是一个不使用其他帮助程序的示例:

const people = [
{ firstName: "John", lastName: "Doe" },
{ firstName: "Jane", lastName: "Doe" },
{ firstName: "Dave", lastName: "Dex" },
];
people.sort(fallThrough(
(personA, personB) => personA.lastName.localeCompare(personA.lastName),
(personA, personB) => personB.firstName.localeCompare(personB.firstName),
));

以上将首先尝试根据姓氏进行排序。如果它们相等,我们按名字排序。

by

by是一个简单的帮助程序,它接受映射函数和比较器。它将映射函数应用于两个比较器参数。目的主要是减少代码重复。

在上面的例子中,我们可以看到我们访问personA.lastNamepersonB.lastName,这是两个比较项目的相同映射。通过使用by我们可以简化此示例:

const localeCompare = (a, b) => a.localeCompare(b);
people.sort(fallThrough(
by(person => person.LastName, localeCompare),
by(person => person.firstName, localeCompare),
));

请注意,您不必使用fallThrough。如果只有一个排序标准,那么以下内容就足够了:

people.sort(by(person => person.LastName, localeCompare));

asc&desc

ascdesc是基于<(小于)和>(大于)的比较器。

asc的定义是:

const asc = (a, b) => -(a < b) || +(a > b);

如果你还是 JavaScript 的新手,这可能很难阅读。它也可以写成:

function asc(a, b) {
if (a < b) return -1; // a before b
if (a > b) return  1; // a after b
return 0; // equal
}

更神秘的版本使用-(a < b),其计算结果为-(true),而又计算为-1。由于-1是真值||短路并返回-1。如果第一个表达式不为 true,则它的计算结果为-(false),这将产生-0。由于-0是一个虚假值,我们将转到||的第二个操作数。在这里,如果a > b为 true,我们将执行相同的操作,则+(true)将导致返回值1。如果a > b为假(即值相等),则+(false)将返回0

请注意,将字符串与<>进行比较时,将根据字符的代码点值进行比较。

descasc相同,但通过交换比较器参数产生相反的顺序。

frontback

frontback用于简化排序中的布尔逻辑。

by(item => typeof item === "string", asc)

有点难以阅读。琴弦放在前面还是后面?让我们考虑一下。typeof item === "string"生成truefalse值。true会胁迫1false0。我们按升序排序,因此我们从小到高排序。1大于0,意味着字符串放在后面。

在阅读代码时,您不想进行这种思考。因此,我提供了frontback帮助者。这些期望您通过测试并使上述内容更具可读性。

back(item => typeof item === "string")

在此版本中,您可以立即看到字符串位于数组的后面。