Javascript - сортировать массив на основе другого массива

169

Можно ли отсортировать и переставить массив, который выглядит следующим образом:

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

чтобы соответствовать расположению этого массива:

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

К сожалению, у меня нет никаких идентификаторов для отслеживания. Мне нужно было бы расставить приоритеты для массива items, чтобы максимально приблизить sortingArr.

Обновить:

Вот результат, который я ищу:

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

Есть идеи, как это можно сделать?

user1448892
источник
Если вы не хотите , чтобы делать все вручную, посмотрите на функцию массива грех PHP.js .
Ади
Только перебирая массив sortingArray и перезаписывая itemsArray
mplungjan,
6
Если несколько массивов имеют одинаковое значение сортировки (т. Е. «B»), как вы решаете, какой элемент находится в отсортированном массиве? С «Бобом», «Генри» и «Томасом», которые все имеют значение «b» - как вы решаете, что идет первым, третьим и четвертым?
Митч Satchwell
@ MitchS можно ли расставить приоритеты чтения слева направо? Это настоящая головная боль, поскольку у меня нет идентификаторов для сравнения.
user1448892
Под слева направо вы подразумеваете порядок, в котором они появляются в оригинальных элементах Array?
Митч Satchwell

Ответы:

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] })
Georg
источник
28
Квадратичная сложность! Попробуйте это с большим количеством данных ...
Жюльен Ройер
6
@ thg435: сложность не имеет ничего общего с «оптимизацией», если только объем данных не будет гарантированно мал (что может быть в данном случае).
Жюльен Ройер
2
@georg Когда речь идет о сложности алгоритмов, действующих на структуры данных, оптимизация алгоритмов с квадратичной (или еще более сложной) сложностью никогда не бывает преждевременной и всегда необходима (если только вы не можете гарантировать, что размер набора данных будет небольшим). , Разница в производительности (в буквальном смысле) выражается в порядках.
Abion47
238

Ответ в одну строку.

itemsArray.sort(function(a, b){  
  return sortingArr.indexOf(a) - sortingArr.indexOf(b);
});
Дургпал Сингх
источник
11
Это будет мутировать itemsArray. В зависимости от требований к производительности это будет намного безопаснее itemsArray.slice().sort(...).
Sawtaytoes
1
Метод сортировки возвращает массив. см. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Дургпал Сингх
8
Он возвращает массив, но также выполняет сортировку на месте и изменяет оригинал.
mynameistechno
2
это должен быть реальный ответ
urmurmur
6
@ Morvael, это связано с тем, что ответ sortingArrдолжен содержать все значения в itemsArray. Исправление заключается в том, чтобы sortingArrallProducts.sort((product1, product2) => { const index1 = manualSort.indexOf(product1.id); const index2 = manualSort.indexOf(product2.id); return ( (index1 > -1 ? index1 : Infinity) - (index2 > -1 ? index2 : Infinity) ); });
помещать
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возвращает первый индекс.
Митч Satchwell
Спасибо, но я бы хотел, чтобы выходные данные itemsArray совпадали с sortingArray.
user1448892
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

Я предполагаю, что большинство людей пришли сюда в поисках эквивалента PHP для array_multisort (я так и сделал), поэтому я решил опубликовать этот ответ. Есть пара вариантов:

1. Существует реализация JS для array_multisort () . Спасибо @Adnan за указание на это в комментариях. Это довольно большой, хотя.

2. Напишите свой собственный. ( JSFiddle demo )

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]

Не уничтожает ссылочный массив.

Sushruth
источник
4
Что делать, если arrayToBeSorted - это массив объектов, то есть: {1: {…}, 2: {…}, 3: {…}, 4: {…}, 5: {…}}? но arrayWithReferenceOrder это просто обычный массив?
Кристалл
3
@sushruth как это отсортировать массив?
hitautodestruct
@ Кристалл, это объект, а не массив объектов. Элементы / элементы в объекте не имеют порядка, то есть их порядок не установлен. Массив объектов будет выглядеть примерно так [{name: "1"}, {name: "2"}, {name: "3"}, ...].
JohnK
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

Для получения нового упорядоченного массива вы можете взять 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);

Нина Шольц
источник
👏That это мои избр я сделать то же самое еще использовать {}вместо Map🤷♂️
Can Рау
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;
})

Это решение добавит объекты в конце, если сортировочный ключ отсутствует в ссылочном массиве.

JD-V
источник
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

ES6

const arrayMap = itemsArray.reduce(
  (accumulator, currentValue) => ({
    ...accumulator,
    [currentValue[1]]: currentValue,
  }),
  {}
);
const result = sortingArr.map(key => arrayMap[key]);

Еще примеры с разными входными массивами

Кан Рау
источник
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);
})
user2521295
источник
-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]);
}
toxicate20
источник
-1

Используйте пересечение двух массивов.

Пример:

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

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

_.intersection(sortArray, arrayToBeSort) 

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

если z и s находятся вне диапазона первого массива, добавьте его в конец результата

Joe.CK
источник
-4

Вы можете сделать что-то вроде этого:

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

Вы можете проверить это здесь .

Примечание: это предполагает, что массивы, которые вы передаете, имеют эквивалентный размер, вам нужно добавить некоторые дополнительные проверки, если это не так.

ссылка ссылка

обращаться

Gadde
источник
Это очень нуждается в редактировании. не только jfiddle возвращает неправильный результат, имена аргументов функции не соответствуют внутреннему содержанию?
два боба