【问题标题】:Sorting an array of objects but prioritising certain items对一组对象进行排序,但对某些项目进行优先排序
【发布时间】:2020-01-31 11:19:13
【问题描述】:

这是一个示例数据:

[
  {
    "name": "test",
    "is_folder": "Y",
    "is_file": "N",
    "sort": 0,
    "sort_reverse": 1
  },
  {
    "name": "1.jpg",
    "is_folder": "N",
    "is_file": "Y",
    "sort": 1,
    "sort_reverse": 0
  }
]

这个想法是文件夹将始终位于列表的顶部,然后是文件,但我也希望对文件夹和文件进行排序。

示例结果:

升序(默认):

  • 文件夹 1
  • 文件夹 2
  • 文件夹 3
  • 1.jpg
  • 2.jpg
  • 3.jpg

降序(反向):

  • 文件夹 3
  • 文件夹 2
  • 文件夹 1
  • 3.jpg
  • 2.jpg
  • 1.jpg

如何使用我的示例数据实现这一目标?

这是迄今为止我对名称进行排序但没有考虑文件夹/文件顺序的内容:

items.sort((a: any, b: any) => {
    if (a.name > b.name) {
        return 1;
    } else if (a.name < b.name) {
        return -1;
    } else {
        return 0;
    }
});

【问题讨论】:

  • 你需要考虑:如果两个项目都是文件夹或者两个项目都不是文件夹,比较它们的名字; else 如果 a 是文件夹,则更高,否则 b 更高
  • sort_reverse 属性是什么?排序时应该考虑到这一点吗?您可以拥有具有不同 sort_reverse 属性的项目吗?在那种情况下会发生什么?此外,sort 属性也有同样的问题。
  • 为什么forloop可以单独使用?
  • @VLAZ 我可能会在排序中使用它
  • @J.你必须使用,还是只使用nameis_folder

标签: javascript typescript sorting


【解决方案1】:

您可以简单地执行以下操作:

  • 如果is_folder 属性相等,则它们都是文件或都是文件夹。比较他们的名字
  • 否则排序is_folder === "Y"更高

升序:

const arr = [
  { "name": "Folder 3",  "is_folder": "Y" },
  { "name": "2.jpg",     "is_folder": "N" },
  { "name": "Folder 2",  "is_folder": "Y" },
  { "name": "Folder 10", "is_folder": "Y" },
  { "name": "Folder 1",  "is_folder": "Y" },
  { "name": "15.jpg",    "is_folder": "N" },
  { "name": "3.jpg",     "is_folder": "N" },
  { "name": "abc.txt",   "is_folder": "N" },
  { "name": "1.jpg",     "is_folder": "N" },
];

arr.sort((a, b) => {
  if (a.is_folder === b.is_folder)
    return a.name.localeCompare(b.name, undefined, {numeric: true});
    
  if (a.is_folder === "Y")
    return -1;
    
  if (b.is_folder === "Y")
    return 1;
    
  return 0;
});

console.log(arr)

降序:

const arr = [
  { "name": "Folder 3",  "is_folder": "Y" },
  { "name": "2.jpg",     "is_folder": "N" },
  { "name": "Folder 2",  "is_folder": "Y" },
  { "name": "Folder 10", "is_folder": "Y" },
  { "name": "Folder 1",  "is_folder": "Y" },
  { "name": "15.jpg",    "is_folder": "N" },
  { "name": "3.jpg",     "is_folder": "N" },
  { "name": "abc.txt",   "is_folder": "N" },
  { "name": "1.jpg",     "is_folder": "N" },
];

arr.sort((a, b) => {
  if (a.is_folder === b.is_folder)
    return b.name.localeCompare(a.name, undefined, {numeric: true}); //<-- flip `a` and `b`
    
  if (a.is_folder === "Y")
    return -1;
    
  if (b.is_folder === "Y")
    return 1;
    
  return 0;
});

console.log(arr)

See on TypeScript Playground (including types)

使用localeCompare with the numeric collation option 可确保正确排序数字,例如10 2 之后。如果您不使用它会发生以下情况:

const arr = [
  { "name": "Folder 3",  "is_folder": "Y" },
  { "name": "2.jpg",     "is_folder": "N" },
  { "name": "Folder 2",  "is_folder": "Y" },
  { "name": "Folder 10", "is_folder": "Y" },
  { "name": "Folder 1",  "is_folder": "Y" },
  { "name": "15.jpg",    "is_folder": "N" },
  { "name": "3.jpg",     "is_folder": "N" },
  { "name": "abc.txt",   "is_folder": "N" },
  { "name": "1.jpg",     "is_folder": "N" },
];

arr.sort((a, b) => {
  if (a.is_folder === b.is_folder)
    return a.name.localeCompare(b.name); //<-- no numeric collation
    
  if (a.is_folder === "Y")
    return -1;
    
  if (b.is_folder === "Y")
    return 1;
});

console.log(arr)

如您所见,我们基本上重复所有代码来进行升序/降序排序。这使得维护它变得更加困难,但是我们可以改进它。我们可以将每个部分提取到一个单独的函数中:

  • 对相似项目的名称进行排序 - 升序
  • 对相似项目的名称进行排序 - 降序
  • 在文件之前对文件夹进行排序

幸运的是,对于升序/降序翻转ab 的顺序与乘以-1 相同,因为localeCompare 返回一个数字 - 正数、负数或零。所以,我们只能有一次逻辑,不能重复两次:

const compareFoldersFirst = (a, b) => {
  if (a.is_folder === "Y")
    return -1;

  if (b.is_folder === "Y")
    return 1;

  return 0;
}

const compareNameAsc = (a, b) => {
  if (a.is_folder === b.is_folder)
    return a.name.localeCompare(b.name, undefined, {numeric: true});

  return 0;
}

const compareNameDesc = (a, b) => compareNameAsc(a, b) * -1;

我们实际上可以概括compareNameDesc 中使用的逻辑——它只是运行一个带有两个参数的函数并将其乘以-1,因此我们可以创建一个通用的reverse 函数,它可以反转任何排序顺序:

const reverse = compareFn => (a, b) => compareFn(a, b) * -1;
const compareNameDesc = reverse(compareNameAsc);

此外,我们可以稍微改变每个比较的逻辑以使其完全自给自足,因为现在名称排序取决于某物是否为文件夹。

const compareFoldersFirst = (a, b) => {
  if (a.is_folder === b.is_folder)
    return 0;

  if (a.is_folder === "Y")
    return -1;

  if (b.is_folder === "Y")
    return 1;
};

这甚至更短,用布尔和数字转换规则的轻微......“创造性用法”来表达:

const compareFoldersFirst = (a, b) => Number(b.is_folder === "Y") - Number(a.is_folder === "Y");

无论如何,这允许我们从名称比较中删除is_folder 检查,我们只剩下以下比较器:

const compareFoldersFirst = (a, b) => Number(b.is_folder === "Y") - Number(a.is_folder === "Y");
const compareNameAsc = (a, b) => a.name.localeCompare(b.name, undefined, {numeric: true});
const compareNameDesc = reverse(compareNameAsc);

我们几乎拥有所有我们需要的工具,通过添加更多逻辑一次然后将其反转或不反转来生成我们想要的任何排序顺序。我们只需要能够轻松组合不同的比较器。为此,我们可以将排序概括如下:我们得到任意数量的排序函数。我们生成一个新函数,它将一个一个地运行它们,直到一个返回非零结果,此时我们返回它。这可以这样做

const comparer = (...comparers) => 
  (a, b) => {
    for(let compareFn of comparers){
       const result = compareFn(a, b);
       if (result !== 0) 
         return result;
    }
  }

但可以使用Array#reduce 使其更紧凑。最后代码更容易维护和组合,使用帮助器compare函数:

const arr = [
  { "name": "Folder 3",  "is_folder": "Y" },
  { "name": "2.jpg",     "is_folder": "N" },
  { "name": "Folder 2",  "is_folder": "Y" },
  { "name": "Folder 10", "is_folder": "Y" },
  { "name": "Folder 1",  "is_folder": "Y" },
  { "name": "15.jpg",    "is_folder": "N" },
  { "name": "3.jpg",     "is_folder": "N" },
  { "name": "abc.txt",   "is_folder": "N" },
  { "name": "1.jpg",     "is_folder": "N" },
];

//helper function that takes any amount of compare functions 
//produces a function that runs each until a non-zero result 
const compare = (...comparers) => 
  (a, b) => comparers.reduce(
    (result, compareFn) => result || compareFn(a, b),
    0
  );

//reverse the result of any compare function after it runs:
const reverse = compareFn  => (a, b) => compareFn (a, b) * -1;

//the basic comparer functions:
const compareFoldersFirst = (a, b) => Number(b.is_folder === "Y") - Number(a.is_folder === "Y");
const compareNameAsc = (a, b) => a.name.localeCompare(b.name, undefined, {numeric: true});
const compareNameDesc = reverse(compareNameAsc);

//final comparison function derived form the basic ones
const asc = compare(
  compareFoldersFirst,
  compareNameAsc
);

const desc = compare(
  compareFoldersFirst,
  compareNameDesc
);

console.log("--- Ascending sort ---\n",  arr.sort(asc));
console.log("--- Descending sort ---\n", arr.sort(desc));

See on TypeScript Playground (including types)

【讨论】:

  • 我已经应用了您的原始答案(请参阅更新的问题),但这似乎没有对文件进行排序。它只对文件夹进行排序
  • @J.你有错字吗a.is_foler -> a.is_folder
  • 确实是一个错字。我的错。 掌心
  • @J.Do 我添加了指向使用类型进行比较的 TS 操场的链接。这很有帮助,因为(a: any, b: any) 允许出现轻微的拼写错误,例如您必须通过的内容,如果您限制类型,您会看到一个错误,因为该属性不存在。
【解决方案2】:

您可以提前按文件夹排序,然后按名称排序。

var data = [{ name: "test", is_folder: "Y", is_file: "N" }, { name: "1.jpg", is_folder: "N", is_file: "Y" }, { name: "abc", is_folder: "Y", is_file: "N" }, { name: "2.jpg", is_folder: "N", is_file: "Y" }];

// asc
data.sort((a, b) =>
    (b.is_folder === 'Y') - (a.is_folder === 'Y') || // static
    a.name.localeCompare(b.name)                     // dynamic
);
console.log(data);

// desc
data.sort((a, b) =>
    (b.is_folder === 'Y') - (a.is_folder === 'Y') ||
    b.name.localeCompare(a.name)
);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-01
    • 1970-01-01
    • 2021-04-14
    • 1970-01-01
    • 2011-08-18
    相关资源
    最近更新 更多