Что не так в этом сортировочном массиве, пропускающем некоторые элементы?

#javascript #arrays #sorting #object

#javascript #массивы #сортировка #объект

Вопрос:

У меня есть массив объектов. Я хочу отсортировать его, оставив некоторые элементы в том же положении (с b =»NOT»)

 var a=[{a:1,b:"YES"},{a:2,b:"YES"},{a:5,b:"NOT"},{a:0,b:"NOT"},{a:0,b:"YES"}]

function sortc(x,y){

  if (x.b=="NOT" || y.b=="NOT")
    return Infinity ;
     
  return (Number(x.a)-Number(y.a))
                 
}

console.log(a.sort(sortc)); 

результат :

 0: {a: 1, b: "YES"}
1: {a: 2, b: "YES"}
2: {a: 5, b: "NOT"}
3: {a: 0, b: "NOT"}
4: {a: 0, b: "YES"}
 

Ожидаемый результат был (с компонентами сортировки с b =»YES».) :

 { "a": 0, "b": "YES" }
{ "a": 1, "b": "YES" }
{ "a": 5, "b": "NOT" }
{ "a": 0, "b": "NOT" }
{ "a": 2, "b": "YES" } 
 

Комментарии:

1. » Я хочу отсортировать его, оставив некоторые элементы в том же положении (с b =»NOT») » возврат Infinite на самом деле не оставляет их в том же месте. Просто означает, что y это должно быть отсортировано после x .

2. Number(x.a) не нужно, поскольку x.a это уже number значение.

3. Можете ли вы включить желаемый результат?

4. @VLAZ Раздел «результат», по-видимому, является неправильным результатом, а не желаемым.

5. Хм, я ответил до того, как заметил, что это было закрыто. Я думаю, это было ясно для меня, но я согласен, что желаемый результат должен быть включен. Я предполагаю, что вы ожидаете { "a": 0, "b": "YES" }, { "a": 1, "b": "YES" }, { "a": 5, "b": "NOT" }, { "a": 0, "b": "NOT" }, { "a": 2, "b": "YES" } , но я могу ошибаться. Не могли бы вы уточнить? Я бы с удовольствием проголосовал за повторное открытие, когда это произойдет. При необходимости я бы изменил свой ответ.

Ответ №1:

Вы не можете сортировать только некоторые элементы с помощью Array#sort() метода — вы либо сортируете все, либо ничего. Вы также не определяете положение элементов — вам нужно только определить их связь с другими элементами, а алгоритм сортировки позаботится обо всем остальном.

Что вы можете сделать в качестве обходного пути, так это

  1. Извлеките все элементы, которые должны быть отсортированы.
  2. Сортируйте их.
  3. Перейдите к исходному массиву и замените только то, что должно быть отсортировано, остальные элементы оставьте на своих местах.
 var a = [
  { a: 1, b: "YES" }, 
  { a: 2, b: "YES" },
  { a: 5, b: "NOT" }, 
  { a: 0, b: "NOT" }, 
  { a: 0, b: "YES" }
]

//get only `b: "YES"` items
const dataToSort = a.filter(item => item.b === "YES");

//sort them
dataToSort.sort((x, y) => x.a - y.a);

//replace only items that need to be sorted
const it = dataToSort.values()
for (let i = 0; i < a.length; i  ) {
  if (a[i].b === "NOT")
    continue;
    
  a[i] = it.next().value;
}

console.log(a); 

Для записи последний цикл можно просто заменить еще более коротким с большим использованием итератора, хотя это может быть немного более запутанным:

 const it = dataToSort.values()
for (const [key, item] of a.entries()) { //use the key-value iterator from the array
  if (item.b === "NOT")
    continue;
    
  [a[key]] = it; //array destructuring internally advances an iterator
}
 
 var a = [
  { a: 1, b: "YES" }, 
  { a: 2, b: "YES" },
  { a: 5, b: "NOT" }, 
  { a: 0, b: "NOT" }, 
  { a: 0, b: "YES" }
]

//get only `b: "YES"` items
const dataToSort = a.filter(item => item.b === "YES");

//sort them
dataToSort.sort((x, y) => x.a - y.a);

//replace only items that need to be sorted
const it = dataToSort.values()
for (const [key, item] of a.entries()) {
  if (item.b === "NOT")
    continue;
    
  [a[key]] = it;
}

console.log(a); 


Наконец, это можно сделать несколько более удобным с помощью вспомогательной функции генератора и нескольких небольших служебных функций

 /*  library code  */

const transformArg = transform => f => (...args) => f(transform(...args));

function* filter(predicate, it) {
  for (const item of it) {
    if (predicate(item))
      yield item;
  }
}

/*  /library code  */

var a = [
  { a: 1, b: "YES" }, 
  { a: 2, b: "YES" },
  { a: 5, b: "NOT" }, 
  { a: 0, b: "NOT" }, 
  { a: 0, b: "YES" }
]

/* helpers */
//extract the `b` key in this case so we don't need to repeat it. 
const getSortableAttribute = transformArg(({b}) => b);
//get the value from key-value pair
const getValue = transformArg(([, value]) => value);
//check if the attribute is "YES"
const isSortable = getSortableAttribute(attr => attr === "YES");

const dataToSort = a.filter(isSortable);

dataToSort.sort((x, y) => x.a - y.a);

const it = dataToSort.values()
//iterate only over sortable key-value pairs by re-using the `isSortable` filter
for (const [key, item] of filter(getValue(isSortable), a.entries())) {
  [a[key]] = it;
}

console.log(a); 

Комментарии:

1. Хорошее использование .values() итератора. Вы также можете использовать a[i] = dataToSort.shift() , если это слишком запутанно для OP

2. Я не фанат этого, поскольку он изменяет массив, а затем перемещает все индексы вниз. Для достаточно больших массивов это может быть проблемой с производительностью. Хотя .reverse() .pop() частично помогает.

3. reverse pop быстрее, чем shift?

4. Или sort в порядке убывания, а затем pop 🙂

5. Я беспокоюсь не о мутации, а об изменении всех индексов. Каждый .shift() O(n) из них должен перемещать все элементы вниз по индексу в массиве. Если нет исключений, о которых я не знаю, я ожидаю, что это произойдет. Итак, три .shift() операции над массивом из 10 элементов должны будут переместить 9, затем 8, затем 7 элементов вниз по слоту. Reverse — это single O(n) и .pop() then O(1) , так что в целом лучше.

Ответ №2:

Это подход sort , основанный на прямом использовании, но формирующий доступ с Proxy помощью for length и индексов.

 const
    sortOnly = (array, indices) => new Proxy(array, {
        get (target, prop) {
            if (isFinite(prop)) return target[indices[prop]];
            if (prop === 'length') return indices.length;
            return target[prop];
        },
        set (target, prop, receiver) {
            target[indices[prop]] = receiver;
            return true;
        }
    }),
    array = [{ a: 1, b: "YES" }, { a: 2, b: "YES" }, { a: 5, b: "NOT" }, { a: 0, b: "NOT" }, { a: 0, b: "YES" }];

sortOnly(array, [...array.keys()].filter(i => array[i].b !== 'NOT'))
    .sort((a, b) => a.a - b.a)

console.log(array); 
 .as-console-wrapper { max-height: 100% !important; top: 0; }