web-dev-qa-db-ja.com

アンダースコア:複数の属性に基づくsortBy()

複数の属性に基づいたオブジェクトで配列を並べ替えようとしています。つまり、最初の属性が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;
});

これは正常に機能しますが、「John」と「Lisa」が適切にソートされるようにするにはどうすればよいですか?

111
Christian R

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

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

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

244
Rory MacLeod

これらの場合に時々使用するハックなトリックは次のとおりです。結果がソート可能になるようにプロパティを組み合わせます。

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

もちろん、thisは配列を所定の位置にソートします。ソートされたコピーが必要な場合(_.sortByが与えるような)、最初に配列のクローンを作成します:

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

退屈から、これについても一般的な解決策(任意の数のキーで並べ替える)を書きました: have a look

50
Dan Tao

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

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

ところで、患者用のイニシャライザは少しおかしいですね。なぜオブジェクトの真の配列としてこの変数を初期化する-の配列の配列としてではなく、_。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}
        ];

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

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

テストロードは面白いでしょう...

10
zobidafly

これらの回答はいずれも、ソートで複数のフィールドを使用するための汎用的な方法として理想的ではありません。上記のアプローチはすべて、配列を複数回並べ替える必要があるため(リストが大きいと処理速度が大幅に低下する可能性がある)、または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 } ]

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

6

簡単な例from http://janetriley.net/2014/12/sort-on-multiple-keys-with-underscores-sortby .html (@MikeDevenney提供)

コード

var FullySortedArray = _.sortBy(( _.sortBy(array, 'second')), 'first');

あなたのデータで

var FullySortedArray = _.sortBy(( _.sortBy(patients, 'roomNumber')), 'name');
3

ただプロパティの配列を返すでソートしたい:

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

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

2
Lucky Soni

おそらく、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
Mike K

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

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

または同等のもの。

注:数値属性roomNumberを文字列に変換しているため、部屋番号が10を超える場合は何かを行う必要があります。それ以外の場合は、11が2の前になります。 1。

1
Mark Sherretta

sortByの代わりに_.orderByを使用した方が良いと思います:

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

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

0
junktrunk