JavaScript - sortuj tablicę na podstawie innej tablicy

168

Czy można posortować i uporządkować tablicę, która wygląda tak:

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

aby dopasować układ tej tablicy:

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

Niestety nie mam żadnych identyfikatorów do śledzenia. Musiałbym nadać priorytet tablicy items, aby dopasować sortingArr tak blisko, jak to możliwe.

Aktualizacja:

Oto wynik, którego szukam:

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

Masz jakiś pomysł, jak można to zrobić?

user1448892
źródło
Jeśli nie chcesz, aby zrobić wszystko ręcznie, warto zapoznać się z tablicą funkcji sin PHP.js .
Adi
Tylko przez zapętlenie tablicy sortingArray i przepisanie pozycji itemsArray
mplungjan
6
W przypadku, gdy wiele tablic ma tę samą wartość sortowania (tj. „B”), jak decydujesz, który element ma trafić w określone miejsce w posortowanej tablicy? W przypadku „Bob”, „Henry” i „Thomas”, które mają wartość „b” - jak zdecydujesz, który z nich zajmie pierwsze, trzecie i czwarte miejsce?
Mitch Satchwell
@MitchS czy możliwe byłoby ustalenie priorytetów czytania od lewej do prawej? To prawdziwy ból głowy, ponieważ nie mam żadnych identyfikatorów do porównania.
user1448892
Czy mówiąc od lewej do prawej, masz na myśli kolejność, w jakiej pojawiają się w oryginalnej tablicy elementów?
Mitch Satchwell

Odpowiedzi:

74

Coś jak:

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

Oto krótszy kod, ale niszczy sortingtablicę:

result = items.map(function(item) {
    var n = sorting.indexOf(item[1]);
    sorting[n] = '';
    return [n, item]
}).sort().map(function(j) { return j[1] })
Georg
źródło
27
Kwadratowa złożoność! Wypróbuj z dużą ilością danych…
Julien Royer
6
@ thg435: złożoność ma niewiele wspólnego z „optymalizacją”, chyba że gwarantuje się, że ilość danych będzie niewielka (co może mieć miejsce w tym przypadku).
Julien Royer
2
@georg Jeśli chodzi o złożoność algorytmów działających na strukturach danych, optymalizacja algorytmów o kwadratowej (lub gorszej) złożoności nigdy nie jest przedwczesna i zawsze jest konieczna (chyba że możesz zagwarantować, że rozmiar zbioru danych będzie mały) . Różnica w wydajności jest (całkiem dosłownie) wyrażona w rzędach wielkości.
Abion47
236

Jedna linia odpowiedzi.

itemsArray.sort(function(a, b){  
  return sortingArr.indexOf(a) - sortingArr.indexOf(b);
});
Durgpal Singh
źródło
10
To zmutuje itemsArray. W zależności od wymagań dotyczących wydajności byłoby to dużo bezpieczniejsze itemsArray.slice().sort(...).
Sawtaytoes
1
sort zwraca tablicę. zobacz developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Durgpal Singh
8
Zwraca tablicę, ale wykonuje również sortowanie na miejscu i mutuje oryginał.
mynameistechno
2
to powinna być prawdziwa odpowiedź
urmurmur
6
@Morvael, jest to spowodowane tym, że odpowiedź sortingArrmusi zawierać wszystkie wartości w itemsArray. Rozwiązaniem jest przesunięcie elementów na tył tablicy, jeśli nie istnieją w 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

Jeśli używasz natywnej funkcji sortowania tablicy, możesz przekazać niestandardowy komparator, który będzie używany podczas sortowania tablicy. Komparator powinien zwrócić liczbę ujemną, jeśli pierwsza wartość jest mniejsza od drugiej, zero, jeśli są równe, i liczbę dodatnią, jeśli pierwsza wartość jest większa.

Więc jeśli dobrze rozumiem przykład, który podajesz, możesz zrobić coś takiego:

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

itemsArray.sort(sortFunc);
David Lewis
źródło
3
To nie zadziała, wynikowa kolejność będzie b, b, b, c, c, d, ponieważ indexOfzwraca pierwszy indeks.
Mitch Satchwell
Dziękuję, ale chciałbym, aby dane wyjściowe itemsArray były zgodne z sortingArray.
user1448892
6
Wolę tę odpowiedź, jeśli "id" w sortingArr
tekście
3
Powinieneś zadeklarować sortingArrayzewnętrzną stronę funkcji, aby uniknąć ponownego deklarowania jej przy każdej iteracji sortowania
aurumpotestasest
26

Przypadek 1: Pytanie oryginalne (brak bibliotek)

Wiele innych odpowiedzi, które działają. :)

Przypadek 2: Oryginalne pytanie (Lodash.js lub Underscore.js)

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

Przypadek 3: Sortuj Array1 tak, jakby to był Array2

Zgaduję, że większość ludzi przyszła tutaj, szukając odpowiednika dla PHP's array_multisort (tak zrobiłem), więc pomyślałem, że również opublikuję tę odpowiedź. Jest kilka opcji:

1. Istnieje już implementacja funkcji array_multisort () w JS . Dzięki @Adnan za wskazanie tego w komentarzach. Jest jednak dość duży.

2. Napisz własną. ( Demo 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 lub Underscore.js (obie popularne, mniejsze biblioteki, które koncentrują się na wydajności) oferują funkcje pomocnicze, które pozwalają to zrobić:

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

... co spowoduje (1) pogrupowanie tablicy sortArray w [index, value]pary, (2) posortuje je według wartości (można tu również podać wywołanie zwrotne), (3) zastąpi każdą z par elementem z tablicy itemArray w indeksie para pochodzi z.

Don McCurdy
źródło
1
Doskonałe rozwiązanie, alternatywnie możesz użyć _.indexBy i usunąć zmianę, jeśli struktura danych jest nieco bardziej złożona
Frozenfire
20

to prawdopodobnie za późno, ale możesz również użyć zmodyfikowanej wersji kodu poniżej w stylu ES6. Ten kod dotyczy tablic takich jak:

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

Faktyczna operacja:

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

Rzeczywista operacja w ES5:

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

Powinno skutkować arrayToBeSorted = [3,5]

Nie niszczy tablicy referencyjnej.

Sushruth
źródło
4
Co się stanie, jeśli tablicaToBeSorted jest tablicą obiektów, tj .: {1: {…}, 2: {…}, 3: {…}, 4: {…}, 5: {…}}? ale arrayWithReferenceOrder jest zwykłą tablicą?
Crystal
3
@sushruth jak to posortuje tablicę?
hitautodestruct
@Crystal, to jest obiekt, a nie tablica obiektów. Elementy / elementy w obiekcie nie mają kolejności, to znaczy ich kolejność nie jest ustawiona. Tablica obiektów wyglądałaby podobnie [{name: "1"}, {name: "2"}, {name: "3"}, ...].
JohnK
8

Użyłbym obiektu pośredniego ( itemsMap), unikając w ten sposób złożoności kwadratowej:

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

Zobacz http://jsfiddle.net/eUskE/

Julien Royer
źródło
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/

Kolejność wynikowa: Bob, Jason, Henry, Thomas, Anne, Andrew

Mitch Satchwell
źródło
6

Dlaczego nie coś takiego

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

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

Funkcja mapy może nie być dostępna we wszystkich wersjach Javascript

Luca Di Liello
źródło
1
To najczystsze rozwiązanie i należy przyjąć odpowiedź. Dzięki!
newman
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)
Harshal Patil
źródło
2
Proszę rozważyć dodanie krótkiego wyjaśnienia / opisu wyjaśniającego dlaczego / jak ten kod odpowiada na pytanie.
Yannis
3

Oto, czego szukałem i zrobiłem, aby posortować tablicę tablic na podstawie innej tablicy:

Jest włączony ^ 3 i może nie być najlepszą praktyką (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));
To mogłoby komuś pomóc

El.
źródło
2

Musiałem to zrobić dla ładunku JSON otrzymanego z interfejsu API, ale nie było to w kolejności, w której chciałem.

Tablica jako tablica referencyjna, taka, według której ma być sortowana druga tablica:

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

Zrobiłem to jako obiekty, ponieważ te ostatecznie będą miały inne właściwości.

Utworzona tablica:

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

Używane z zestawem wyników z bazy danych. Nie wiem, jak wydajna jest, ale przy kilku kolumnach, których użyłem, działała dobrze.

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);
}); 
Jasio
źródło
2

Aby uzyskać nową uporządkowaną tablicę, możesz wziąć Mapi zebrać wszystkie elementy z poszukiwanym kluczem w tablicy i zmapować żądane uporządkowane klucze, biorąc przesiany element z poszukiwanej grupy.

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

Nina Scholz
źródło
👏 To moja ulubiona, robię to samo, ale używam {}zamiast 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;
})

To rozwiązanie doda obiekty na końcu, jeśli klucz sortowania nie jest obecny w tablicy referencyjnej

JD-V
źródło
0

to powinno działać:

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;
Luca Rainone
źródło
0

Możesz spróbować tej metody.

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
}
Holger Tidemand
źródło
0

ES6

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

Więcej przykładów z różnymi tablicami wejściowymi

Can Rau
źródło
0

W przypadku, gdy musisz to zrobić z tablicą obiektów, oto adaptacja niesamowitej odpowiedzi @Durgpal Singha:

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
źródło
-1

Użyj metody $ .inArray () z jQuery. Możesz wtedy zrobić coś takiego

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]);
}
toksykat20
źródło
-1

Użyj przecięcia dwóch tablic.

Dawny:

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

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

_.intersection(sortArray, arrayToBeSort) 

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

jeśli „z” i „s” są poza zakresem pierwszej tablicy, dołącz je na końcu wyniku

Joe.CK
źródło
-4

Możesz zrobić coś takiego:

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

Możesz to przetestować tutaj .

Uwaga: zakłada się, że przekazywane przez Ciebie tablice mają równoważny rozmiar. Jeśli tak nie jest, musisz dodać dodatkowe kontrole.

patrz odnośnik

odnosić się

Gadde
źródło
To bardzo wymaga edycji. nie tylko jfiddle zwraca nieprawidłowy wynik, nazwy argumentów funkcji nie pasują do wewnętrznej zawartości?
dwa dni