下線:複数の属性に基づくsortBy()


115

複数の属性に基づいてオブジェクトを含む配列をソートしようとしています。つまり、最初の属性が2つのオブジェクト間で同じである場合、2番目の属性を使用して2つのオブジェクトを比較する必要があります。たとえば、次の配列について考えてみます。

var patients = [
             [{name: 'John', roomNumber: 1, bedNumber: 1}],
             [{name: 'Lisa', roomNumber: 1, bedNumber: 2}],
             [{name: 'Chris', roomNumber: 2, bedNumber: 1}],
             [{name: 'Omar', roomNumber: 3, bedNumber: 1}]
               ];

これらをroomNumber属性でソートするには、次のコードを使用します。

var sortedArray = _.sortBy(patients, function(patient) {
    return patient[0].roomNumber;
});

これは正常に機能しますが、「ジョン」と「リサ」が正しくソートされるようにするにはどうすればよいですか?

回答:


250

sortBy これは安定したソートアルゴリズムであるため、最初に2番目のプロパティでソートし、次に次のように最初のプロパティで再度ソートできるはずです。

var sortedArray = _(patients).chain().sortBy(function(patient) {
    return patient[0].name;
}).sortBy(function(patient) {
    return patient[0].roomNumber;
}).value();

2番目sortByがジョンとリサが同じ部屋番号を持っていることを見つけると、最初のsortByものを「リサ、ジョン」に設定した順番でそれらを見つけます。


12
これを拡張し、昇順と降順のプロパティのソートに関する優れた情報を含むブログ投稿があります。
Alex C

4
連鎖ソートのより簡単な解決策はここにあります。公平を期すために、これらの回答が提供された後にブログ投稿が書かれたように見えますが、上記の回答でコードを使用しようとして失敗した後、これを理解するのに役立ちました。
Mike Devenney 2016

1
患者[0]。名前と患者[1]。部屋番号にインデックスがあるはずですよね?患者は配列ではありません...
StinkyCat 2018

[0]元の例では、インデクサために必要とされるpatients配列の配列です。これはまた、別のコメントで言及されたブログ投稿の「より簡単な解決策」がここで機能しない理由でもあります。
Rory MacLeod 2018

1
:ここでは@ac_fireは、今死んだリンクのアーカイブですarchive.is/tiatQ
ラスティグ

52

これが私がこれらのケースで時々使うハックトリックです:結果がソート可能になるようにプロパティを組み合わせます:

var sortedArray = _.sortBy(patients, function(patient) {
  return [patient[0].roomNumber, patient[0].name].join("_");
});

しかし、私が言ったように、それはかなりハックです。これを正しく行うには、おそらくコアJavaScript sortメソッドを実際に使用する必要があります

patients.sort(function(x, y) {
  var roomX = x[0].roomNumber;
  var roomY = y[0].roomNumber;
  if (roomX !== roomY) {
    return compare(roomX, roomY);
  }
  return compare(x[0].name, y[0].name);
});

// General comparison function for convenience
function compare(x, y) {
  if (x === y) {
    return 0;
  }
  return x > y ? 1 : -1;
}

もちろん、これは配列をその場でソートします。ソートされたコピーが必要な場合は(_.sortBy与えられるように)、まず配列のクローンを作成します。

function sortOutOfPlace(sequence, sorter) {
  var copy = _.clone(sequence);
  copy.sort(sorter);
  return copy;
}

退屈のうち、私はこのための一般的な解決策(任意の数のキーで並べ替える)も作成しました


私の属性は文字列と数値の両方になる可能性があるため、この解決策は2番目の解決策を使用することになりました。それで、配列をソートするための単純なネイティブな方法はないようですか?
Christian R

3
return [patient[0].roomNumber, patient[0].name];なしで十分ではないのはなぜjoinですか?
Csaba Toth 2014年

1
一般的なソリューションへのリンクが壊れているように見えます(または、プロキシサーバー経由でアクセスできない可能性があります)。ここに投稿していただけませんか?
Zev Spitz

また、compareプリミティブ値ではない値undefinednullまたはプレーンオブジェクトをどのように処理しますか?
Zev Spitz

ちなみに、このハックは、各値のstrの長さが配列内のすべての項目で同じであることを確認した場合にのみ機能します。
miex 2018

32

私はパーティーに遅れていることを知っていますが、すでに提案されているよりクリーンで迅速なソリューションを必要としている人のためにこれを追加したいと思いました。最も重要でないプロパティから最も重要なプロパティの順にsortBy呼び出しをチェーンできます。I以下のコードでは、患者の新しい配列を作成することにより、ソートされた名前の中にRoomNumberと呼ばれる元の配列からの患者

var sortedPatients = _.chain(patients)
  .sortBy('Name')
  .sortBy('RoomNumber')
  .value();

4
あなたが遅れていても、あなたはまだ正しいです:)ありがとう!
Allan Jikamu

3
とてもきれいです
Jason Turan 2017

11

ところで、患者さん向けのイニシャライザは少し変ですね。なぜこの変数をこのように初期化しないでください- オブジェクトの真の配列として - 単一オブジェクトの配列の配列としてではなく、_。flatten()を使用してそれを行うことができます。

var patients = [
        {name: 'Omar', roomNumber: 3, bedNumber: 1},
        {name: 'John', roomNumber: 1, bedNumber: 1},
        {name: 'Chris', roomNumber: 2, bedNumber: 1},
        {name: 'Lisa', roomNumber: 1, bedNumber: 2},
        {name: 'Kiko', roomNumber: 1, bedNumber: 2}
        ];

リストを別の方法でソートし、リサのベッドにキコを追加しました。楽しみのために、どのような変更が行われるかを確認してください...

var sorted = _(patients).sortBy( 
                    function(patient){
                       return [patient.roomNumber, patient.bedNumber, patient.name];
                    });

ソートして検査すると、これが表示されます

[
{bedNumber: 1, name: "John", roomNumber: 1}, 
{bedNumber: 2, name: "Kiko", roomNumber: 1}, 
{bedNumber: 2, name: "Lisa", roomNumber: 1}, 
{bedNumber: 1, name: "Chris", roomNumber: 2}, 
{bedNumber: 1, name: "Omar", roomNumber: 3}
]

だから私の答えは: コールバック関数で配列を使用する これはDan Taoの答えに非常に似ています、私は結合を忘れました(おそらく私は一意の項目の配列の配列を削除したためです:))
データ構造を使用して、それだろう :

var sorted = _(patients).chain()
                        .flatten()
                        .sortBy( function(patient){
                              return [patient.roomNumber, 
                                     patient.bedNumber, 
                                     patient.name];
                        })
                        .value();

そしてテストロードは興味深いでしょう...


真剣に、それが答えです
RadekDuchoň19年

7

これらの回答はいずれも、並べ替えで複数のフィールドを使用するための汎用的な方法として理想的ではありません。上記のアプローチはすべて、配列を複数回ソートする必要がある(十分に大きいリストでは処理速度が大幅に低下する可能性がある)か、VMがクリーンアップする必要がある大量のガベージオブジェクトを生成する(そして最終的には遅くなる)ため、非効率的ですプログラムダウン)。

高速、効率的、簡単に逆ソートが可能で、underscoreまたはlodash、またはで直接Array.sort

最も重要な部分はcompositeComparatorメソッドです。これは、コンパレーター関数の配列を取り、新しい複合コンパレーター関数を返します。

/**
 * Chains a comparator function to another comparator
 * and returns the result of the first comparator, unless
 * the first comparator returns 0, in which case the
 * result of the second comparator is used.
 */
function makeChainedComparator(first, next) {
  return function(a, b) {
    var result = first(a, b);
    if (result !== 0) return result;
    return next(a, b);
  }
}

/**
 * Given an array of comparators, returns a new comparator with
 * descending priority such that
 * the next comparator will only be used if the precending on returned
 * 0 (ie, found the two objects to be equal)
 *
 * Allows multiple sorts to be used simply. For example,
 * sort by column a, then sort by column b, then sort by column c
 */
function compositeComparator(comparators) {
  return comparators.reduceRight(function(memo, comparator) {
    return makeChainedComparator(comparator, memo);
  });
}

ソートしたいフィールドを比較するためのコンパレーター関数も必要です。このnaturalSort関数は、特定のフィールドを指定してコンパレータを作成します。逆ソート用のコンパレータを作成するのも簡単です。

function naturalSort(field) {
  return function(a, b) {
    var c1 = a[field];
    var c2 = b[field];
    if (c1 > c2) return 1;
    if (c1 < c2) return -1;
    return 0;
  }
}

(これまでのすべてのコードは再利用可能で、たとえばユーティリティモジュールに保持できます)

次に、複合コンパレーターを作成する必要があります。この例では、次のようになります。

var cmp = compositeComparator([naturalSort('roomNumber'), naturalSort('name')]);

部屋番号、名前の順に並べ替えられます。追加のソート基準を追加することは簡単であり、ソートのパフォーマンスには影響しません。

var patients = [
 {name: 'John', roomNumber: 3, bedNumber: 1},
 {name: 'Omar', roomNumber: 2, bedNumber: 1},
 {name: 'Lisa', roomNumber: 2, bedNumber: 2},
 {name: 'Chris', roomNumber: 1, bedNumber: 1},
];

// Sort using the composite
patients.sort(cmp);

console.log(patients);

以下を返します

[ { name: 'Chris', roomNumber: 1, bedNumber: 1 },
  { name: 'Lisa', roomNumber: 2, bedNumber: 2 },
  { name: 'Omar', roomNumber: 2, bedNumber: 1 },
  { name: 'John', roomNumber: 3, bedNumber: 1 } ]

私がこの方法を好む理由は、任意の数のフィールドで高速ソートを可能にし、大量のガベージを生成したり、ソート内で文字列連結を実行したりせず、一部の列が逆ソートされるように簡単に使用できるためです。ソート。



2

おそらくunderscore.jsまたは単なるJavaScriptエンジンは、これらの回答が書かれたときとは現在は異なりますが、並べ替えキーの配列を返すだけでこれを解決できました。

var input = [];

for (var i = 0; i < 20; ++i) {
  input.push({
    a: Math.round(100 * Math.random()),
    b: Math.round(3 * Math.random())
  })
}

var output = _.sortBy(input, function(o) {
  return [o.b, o.a];
});

// output is now sorted by b ascending, a ascending

実際には、次のフィドルをご覧ください:https : //jsfiddle.net/mikeular/xenu3u91/


2

プロパティの配列を返すだけですあなたとソートするに:

ES6構文

var sortedArray = _.sortBy(patients, patient => [patient[0].name, patient[1].roomNumber])

ES5構文

var sortedArray = _.sortBy(patients, function(patient) { 
    return [patient[0].name, patient[1].roomNumber]
})

これには、数値を文字列に変換するという副作用はありません。


1

イテレーターでソートしたいプロパティーを連結できます。

return [patient[0].roomNumber,patient[0].name].join('|');

または同等のもの。

注:数値属性roomNumberを文字列に変換しているため、部屋番号> 10の場合は何かを行う必要があります。そうでない場合、11が2の前に来ます。問題を解決するために先行ゼロを埋め込むことができます。 1。


1

私はあなたの_.orderBy代わりに使うほうがいいと思いますsortBy

_.orderBy(patients, ['name', 'roomNumber'], ['asc', 'desc'])

4
orderByがアンダースコアになっていますか?ドキュメントまたは.d.tsファイルに表示されません。
Zachary Dow

1
アンダースコアにはorderByはありません。
AfroMogli 2017年

1
_.orderBy動作しますが、これはアンダースコアではなく、lodashライブラリのメソッドです。lodash.com / docs / 4.17.4orderBy lodashは、主にアンダースコアのドロップイン置換なので、OPに適している場合があります。
Mike K

0

Angularを使用している場合は、JSまたはCSSハンドラーを追加するのではなく、htmlファイルでその番号フィルターを使用できます。例えば:

  No fractions: <span>{{val | number:0}}</span><br>

その例では、val = 1234567の場合、次のように表示されます。

  No fractions: 1,234,567

例と詳細なガイダンス:https : //docs.angularjs.org/api/ng/filter/number

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.