Javascript - сортування масиву на основі іншого масиву


168

Чи можна сортувати та переставляти масив, який виглядає приблизно так:

itemsArray = [ 
    ['Anne', 'a'],
    ['Bob', 'b'],
    ['Henry', 'b'],
    ['Andrew', 'd'],
    ['Jason', 'c'],
    ['Thomas', 'b']
]

щоб відповідати розташуванню цього масиву:

sortingArr = [ 'b', 'c', 'b', 'b', 'a', 'd' ]

На жаль, у мене немає жодних посвідчень, які б слідкувати. Мені потрібно визначити пріоритет масиву елементів, щоб максимально наблизитись до сортуванняArr.

Оновлення:

Ось результат, який я шукаю:

itemsArray = [    
    ['Bob', 'b'],
    ['Jason', 'c'],
    ['Henry', 'b'],
    ['Thomas', 'b']
    ['Anne', 'a'],
    ['Andrew', 'd'],
]

Будь-яка ідея, як це можна зробити?


Якщо ви не хочете , щоб робити все вручну, подивіться на функцію масиву гріх PHP.js .
Аді

Лише переглянувши сортувальний масив і переписати itemsArray
mplungjan

6
Якщо декілька масивів мають одне і те ж значення сортування (тобто 'b'), як ви вирішите, який елемент іде куди в відсортованому масиві? З "Боб", "Генрі" та "Томас", які мають значення "b" - як ви вирішите, що йде перше, третє та четверте?
Мітч Сатвелл

@MitchS чи можна було б розставити пріоритет читання зліва направо? Це справжній головний біль, оскільки у мене немає ніяких посвідчень для порівняння.
користувач1448892

Зліва направо, ви маєте на увазі порядок їх відображення в оригінальній позиціїArray?
Мітч Сатвелл

Відповіді:


74

Щось на зразок:

items = [ 
    ['Anne', 'a'],
    ['Bob', 'b'],
    ['Henry', 'b'],
    ['Andrew', 'd'],
    ['Jason', 'c'],
    ['Thomas', 'b']
]

sorting = [ 'b', 'c', 'b', 'b', 'c', 'd' ];
result = []

sorting.forEach(function(key) {
    var found = false;
    items = items.filter(function(item) {
        if(!found && item[1] == key) {
            result.push(item);
            found = true;
            return false;
        } else 
            return true;
    })
})

result.forEach(function(item) {
    document.writeln(item[0]) /// Bob Jason Henry Thomas Andrew
})

Ось коротший код, але він знищує sortingмасив:

result = items.map(function(item) {
    var n = sorting.indexOf(item[1]);
    sorting[n] = '';
    return [n, item]
}).sort().map(function(j) { return j[1] })

27
Квадратична складність! Спробуйте з великою кількістю даних…
Жульєн Ройер

6
@ thg435: складність мало пов'язана з "оптимізацією", якщо тільки обсяг даних гарантується невеликим (що може бути тут).
Жульєн Ройер

2
@georg Якщо мова йде про складність алгоритмів, що діють на структури даних, оптимізація алгоритмів з квадратичними (або гіршими) складностями ніколи не передчасна і завжди необхідна (якщо ви не можете гарантувати, що розмір набору даних буде невеликим) . Різниця в продуктивності (цілком буквально) виражається в порядках.
Авіон47

236

Відповідь однієї лінії

itemsArray.sort(function(a, b){  
  return sortingArr.indexOf(a) - sortingArr.indexOf(b);
});

10
Це буде вимкнено itemsArray. Залежно від вимог до продуктивності, це було б набагато безпечніше зробити itemsArray.slice().sort(...).
Sawtaytoes

1
метод сортування повертає масив. дивіться developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Durgpal Singh

8
Він повертає масив, але також робить сортування на місці та мутує оригінал.
mynameistechno

2
це має бути реальною відповіддю
урмурмур

6
@Morvael, це пов'язано з цією відповіддю, яка вимагає sortingArrмістити всі значення в itemsArray. Виправлення полягає в тому, щоб перемістити елементи на задню частину масиву, якщо їх немає в sortingArr:allProducts.sort((product1, product2) => { const index1 = manualSort.indexOf(product1.id); const index2 = manualSort.indexOf(product2.id); return ( (index1 > -1 ? index1 : Infinity) - (index2 > -1 ? index2 : Infinity) ); });
Freshollie

35

Якщо ви використовуєте функцію сортування нативного масиву, ви можете передати спеціальний компаратор, який буде використовуватися при сортуванні масиву. Порівняльник повинен повернути від’ємне число, якщо перше значення менше другого, нульове, якщо вони рівні, і додатне число, якщо перше значення більше.

Тож якщо я зрозумів приклад, який ви даєте правильно, ви можете зробити щось на кшталт:

function sortFunc(a, b) {
  var sortingArr = [ 'b', 'c', 'b', 'b', 'c', 'd' ];
  return sortingArr.indexOf(a[1]) - sortingArr.indexOf(b[1]);
}

itemsArray.sort(sortFunc);

3
Це не спрацює, отриманий порядок буде b, b, b, c, c, d, оскільки indexOfповертає перший індекс.
Мітч Сатвелл

Дякую, але я хотів би, щоб результат itemsArray відповідав сортуванняArray.
користувач1448892

6
Я вважаю за краще цю відповідь, якщо "ідентифікатори" в sortingArrунікальних - які, на щастя, вони є у моєму випадку :)
dillondrenzek

3
Вам слід оголосити sortingArrayзовнішню функцію, щоб уникнути її повторного оголошення при кожній ітерації сортування
aurumpotestasest

26

Випадок 1: Оригінальне запитання (Без бібліотек)

Безліч інших відповідей, які працюють. :)

Випадок 2: Оригінальне запитання (Lodash.js або Underscore.js)

var groups = _.groupBy(itemArray, 1);
var result = _.map(sortArray, function (i) { return groups[i].shift(); });

Випадок 3: Сортувати Array1 так, ніби це Array2

Я здогадуюсь, що більшість людей приїхали сюди шукають еквівалент array_multisort PHP (я це зробив), тому я подумав, що також опублікую цю відповідь. Є кілька варіантів:

1. Існує реалізація JS array_multisort () . Дякуємо @Adnan за те, що він це вказав у коментарях. Він досить великий, хоча.

2. Напишіть своє. ( Демонстрація JSFiddle )

function refSort (targetData, refData) {
  // Create an array of indices [0, 1, 2, ...N].
  var indices = Object.keys(refData);

  // Sort array of indices according to the reference data.
  indices.sort(function(indexA, indexB) {
    if (refData[indexA] < refData[indexB]) {
      return -1;
    } else if (refData[indexA] > refData[indexB]) {
      return 1;
    }
    return 0;
  });

  // Map array of indices to corresponding values of the target array.
  return indices.map(function(index) {
    return targetData[index];
  });
}

3. Lodash.js або Underscore.js (обидві популярні, менші бібліотеки, орієнтовані на продуктивність) пропонують допоміжні функції, які дозволяють вам це робити:

    var result = _.chain(sortArray)
      .pairs()
      .sortBy(1)
      .map(function (i) { return itemArray[i[0]]; })
      .value();

... Що (1) згрупує sortArray в [index, value]пари, (2) сортувати їх за значенням (ви також можете надати тут зворотний виклик), (3) замінити кожну з пар на елемент з itemArray на індекс пара виникла з.


1
Відмінне рішення, або ви можете використовувати _.indexBy та усунути зсув, якщо ваша структура даних трохи складніша
Frozenfire

20

це, мабуть, пізно, але ви також можете використовувати деякі модифіковані версії коду нижче в стилі ES6. Цей код призначений для масивів типу:

var arrayToBeSorted = [1,2,3,4,5];
var arrayWithReferenceOrder = [3,5,8,9];

Фактична операція:

arrayToBeSorted = arrayWithReferenceOrder.filter(v => arrayToBeSorted.includes(v));

Фактична операція в ES5:

arrayToBeSorted = arrayWithReferenceOrder.filter(function(v) {
    return arrayToBeSorted.includes(v);
});

Повинно бути результатом arrayToBeSorted = [3,5]

Не знищує масив опор.


4
Що робити, якщо я arrayToBeSorted - це масив об'єктів, тобто: {1: {...}, 2: {...}, 3: {...}, 4: {...}, 5: {...}}? але arrayWithReferenceOrder - це просто звичайний масив?
Кристал

3
@sushruth як це сортувати масив?
hitautodestruct

@Crystal, це об’єкт, а не масив об'єктів. Елементи / елементи в об'єкті не мають порядку, тобто їх порядок не встановлений. Масив об’єктів виглядав би приблизно так [{name: "1"}, {name: "2"}, {name: "3"}, ...].
ДжонК

8

Я б використовував об'єкт-посередник ( itemsMap), таким чином уникаючи квадратичної складності:

function createItemsMap(itemsArray) { // {"a": ["Anne"], "b": ["Bob", "Henry"], …}
  var itemsMap = {};
  for (var i = 0, item; (item = itemsArray[i]); ++i) {
    (itemsMap[item[1]] || (itemsMap[item[1]] = [])).push(item[0]);
  }
  return itemsMap;
}

function sortByKeys(itemsArray, sortingArr) {
  var itemsMap = createItemsMap(itemsArray), result = [];
  for (var i = 0; i < sortingArr.length; ++i) {
    var key = sortingArr[i];
    result.push([itemsMap[key].shift(), key]);
  }
  return result;
}

Дивіться http://jsfiddle.net/eUskE/


6
var sortedArray = [];
for(var i=0; i < sortingArr.length; i++) {
    var found = false;
    for(var j=0; j < itemsArray.length && !found; j++) {
        if(itemsArray[j][1] == sortingArr[i]) {
            sortedArray.push(itemsArray[j]);
            itemsArray.splice(j,1);
            found = true;
        }
    }
}

http://jsfiddle.net/s7b2P/

Результат замовлення: Боб, Джейсон, Генрі, Томас, Енн, Ендрю


6

Чому б не щось подібне

//array1: array of elements to be sorted
//array2: array with the indexes

array1 = array2.map((object, i) => array1[object]);

Функція карти може бути недоступною для всіх версій Javascript


1
Це найчистіше рішення, і на нього слід прийняти відповідь. Дякую!
новачок

3
let a = ['A', 'B', 'C' ]

let b = [3, 2, 1]

let c = [1.0, 5.0, 2.0]

// these array can be sorted by sorting order of b

const zip = rows => rows[0].map((_, c) => rows.map(row => row[c]))

const sortBy = (a, b, c) => {
  const zippedArray = zip([a, b, c])
  const sortedZipped = zippedArray.sort((x, y) => x[1] - y[1])

  return zip(sortedZipped)
}

sortBy(a, b, c)

2
Будь ласка, розгляньте, як додати коротке пояснення / опис, що пояснює, чому / як цей код відповідає на питання.
Янніс

3

Це те, що я шукав, і робив для сортування масиву масивів на основі іншого масиву:

Увімкнено ^ 3 і може бути не найкращою практикою (ES6)

function sortArray(arr, arr1){
      return arr.map(item => {
        let a = [];
        for(let i=0; i< arr1.length; i++){
          for (const el of item) {
            if(el == arr1[i]){
              a.push(el);
            }   
            }
          }
          return a;
      });
    }
    
    const arr1 = ['fname', 'city', 'name'];
  const arr = [['fname', 'city', 'name'],
  ['fname', 'city', 'name', 'name', 'city','fname']];
  console.log(sortArray(arr,arr1));
Це може комусь допомогти


2

Мені довелося це зробити для корисного навантаження JSON, отриманого від API, але це було не в тому порядку, як я цього хотів.

Масив - це опорний масив, той, для якого потрібно вибрати другий масив, відсортований за:

var columns = [
    {last_name: "last_name"},
    {first_name: "first_name"},
    {book_description: "book_description"},
    {book_id: "book_id"},
    {book_number: "book_number"},
    {due_date: "due_date"},
    {loaned_out: "loaned_out"}
];

Я робив це як об’єкти, оскільки з часом вони матимуть інші властивості.

Створений масив:

 var referenceArray= [];
 for (var key in columns) {
     for (var j in columns[key]){
         referenceArray.push(j);
     }
  }

Використовується це з набором результатів із бази даних. Я не знаю, наскільки це ефективно, але з нечисленною кількістю стовпців, які я використав, він працював чудово.

result.forEach((element, index, array) => {                            
    var tr = document.createElement('tr');
    for (var i = 0; i < referenceArray.length - 1; i++) {
        var td = document.createElement('td');
        td.innerHTML = element[referenceArray[i]];
        tr.appendChild(td);

    }
    tableBody.appendChild(tr);
}); 

2

Для отримання нового упорядкованого масиву ви можете взяти a Mapі зібрати всі елементи за допомогою шуканого ключа в масиві та зіставити потрібні впорядковані ключі, взявши просіяний елемент шуканої групи.

var itemsArray = [['Anne', 'a'], ['Bob', 'b'], ['Henry', 'b'], ['Andrew', 'd'], ['Jason', 'c'], ['Thomas', 'b']],
    sortingArr = [ 'b', 'c', 'b', 'b', 'a', 'd' ],
    map = itemsArray.reduce((m, a) => m.set(a[1], (m.get(a[1]) || []).concat([a])), new Map),
    result = sortingArr.map(k => (map.get(k) || []).shift());

console.log(result);


Це моя фаворит, я те ж саме використовую {}замість Map🤷‍♂️
Can Rau

2
let sortedOrder = [ 'b', 'c', 'b', 'b' ]
let itemsArray = [ 
    ['Anne', 'a'],
    ['Bob', 'b'],
    ['Henry', 'b'],
    ['Andrew', 'd'],
    ['Jason', 'c'],
    ['Thomas', 'b']
]
a.itemsArray(function (a, b) {
    let A = a[1]
    let B = b[1]

    if(A != undefined)
        A = A.toLowerCase()

    if(B != undefined)
        B = B.toLowerCase()

    let indA = sortedOrder.indexOf(A)
    let indB = sortedOrder.indexOf(B)

    if (indA == -1 )
        indA = sortedOrder.length-1
    if( indB == -1)
        indB = sortedOrder.length-1

    if (indA < indB ) {
        return -1;
    } else if (indA > indB) {
        return 1;
    }
    return 0;
})

Це рішення додасть об'єкти в кінці, якщо ключ сортування відсутній у масиві посилань


0

це має працювати:

var i,search, itemsArraySorted = [];
while(sortingArr.length) {
    search = sortingArr.shift();
    for(i = 0; i<itemsArray.length; i++) {
        if(itemsArray[i][1] == search) {
            itemsArraySorted.push(itemsArray[i]);
            break;
        }
    } 
}

itemsArray = itemsArraySorted;

0

Ви можете спробувати цей метод.

const sortListByRanking = (rankingList, listToSort) => {
  let result = []

  for (let id of rankingList) {
    for (let item of listToSort) {
      if (item && item[1] === id) {
        result.push(item)
      }
    }
  }

  return result
}


0

Якщо вам сюди потрібно зробити це з масивом об'єктів, ось адаптація дивовижної відповіді @Durgpal Singh:

const itemsArray = [
  { name: 'Anne', id: 'a' },
  { name: 'Bob', id: 'b' },
  { name: 'Henry', id: 'b' },
  { name: 'Andrew', id: 'd' },
  { name: 'Jason', id: 'c' },
  { name: 'Thomas', id: 'b' }
]

const sortingArr = [ 'b', 'c', 'b', 'b', 'a', 'd' ]

Object.keys(itemsArray).sort((a, b) => {
  return sortingArr.indexOf(itemsArray[a].id) - sortingArr.indexOf(itemsArray[b].id);
})

-1

Використовуйте метод $ .inArray () від jQuery. Тоді ви могли б зробити щось подібне

var sortingArr = [ 'b', 'c', 'b', 'b', 'c', 'd' ];
var newSortedArray = new Array();

for(var i=sortingArr.length; i--;) {
 var foundIn = $.inArray(sortingArr[i], itemsArray);
 newSortedArray.push(itemsArray[foundIn]);
}

-1

Використовуйте перетин двох масивів.

Наприклад:

var sortArray = ['a', 'b', 'c',  'd', 'e'];

var arrayToBeSort = ['z', 's', 'b',  'e', 'a'];

_.intersection(sortArray, arrayToBeSort) 

=> ['a', 'b', 'e']

якщо 'z і' s 'поза діапазоном першого масиву, додайте його в кінці результату


-4

Ви можете зробити щось подібне:

function getSorted(itemsArray , sortingArr ) {
  var result = [];
  for(var i=0; i<arr.length; i++) {
    result[i] = arr[sortArr[i]];
  }
  return result;
}

Ви можете перевірити це тут .

Примітка. Це передбачає, що масиви, які ви передаєте, мають еквівалентний розмір, вам потрібно буде додати деякі додаткові перевірки, якщо це не так.

посилання на посилання

посилатися


Це погано потребує редагування. jfiddle не тільки повертає неправильний результат, імена аргументів функції не відповідають внутрішньому змісту?
twobob
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.