Сортировка массива объектов с приоритетом определенных элементов - PullRequest
1 голос
/ 31 января 2020

Вот пример данных:

[
  {
    "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;
    }
});

Ответы [ 2 ]

3 голосов
/ 31 января 2020

Вы можете просто сделать следующее:

  • Если свойство 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)

См. На игровой площадке TypeScript (включая типы)

Использование localeCompare с опцией сравнения числительных c гарантирует, что числа будут правильно отсортированы, например, 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)

Как видите, мы в основном повторяем весь код для сортировки по возрастанию / убыванию. Это усложняет поддержание этого, однако мы можем улучшить его. Мы можем извлечь каждую часть в отдельную функцию:

  • сортировать имена похожих элементов - по возрастанию
  • сортировать имена похожих элементов - по убыванию
  • сортировать папки перед файлами

К счастью, при переключении по возрастанию / убыванию порядок a и b равен умножению на -1, поскольку localeCompare возвращает число - положительное, отрицательное или ноль. Таким образом, мы можем иметь только один логик c и не повторять его дважды:

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;

Мы можем обобщить логи c, используемые в compareNameDesc - он просто запускает функцию с двумя параметрами и умножает его на -1, поэтому мы можем сделать обобщенную c reverse функцию, которая может изменить любой порядок сортировки:

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

Кроме того, мы можем немного изменить логи c каждое сравнение, чтобы сделать его полностью самодостаточным, так как прямо сейчас сортировка имени зависит от того, является ли что-то папкой или нет.

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;
};

Это даже короче до express с небольшим ... "творческим использованием" правил логического преобразования и преобразования чисел:

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);

У нас есть почти всех инструментов, которые нам нужны для любой сортировки Порядок, который мы хотим, добавив больше логи c один раз , а затем отменить или нет. Нам просто нужно уметь легко составлять разные компараторы. Для этого мы можем обобщить сортировку следующим образом: Мы получаем любое число функций сортировки. Мы создаем новую функцию, которая будет запускать их один за другим, пока один не вернет ненулевой результат, и в этот момент мы его вернем. Это можно сделать как

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));

См. На игровой площадке TypeScript (включая типы)

2 голосов
/ 31 января 2020

Вы можете заранее выполнить сортировку по папке, а затем отсортировать по имени в требуемом порядке.

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; }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...