やりたいこと
オブジェクトの配列で、特定のキーを参照して、'-1'
だったら最後に並び替えたい。
それ以外の順序は保持。
いろいろ調べたけど、全体的に並べ替えて、昇順降順にするというのばかりで、
特定の値を場合だけ抽出して先頭や最後に回す
という処理をsortでやっているものが見当たりませんでした。
一回抽出して、push とか unshiftとかすればいいかもしれませんが、sortでもできるだろうと思っていろいろ試して一応できました。
いろんなパターンで試したりしていないので、動作保証はできません。
とりあえず以下のような例ではうまく行きました。
逆にいい方法をご存知の方がいましたら教えてください!!
実際のコード
// 真ん中にある、-1だけ最後にしたい
const fix = [
{id: 'A00', name: '名前1'},
{id: '-1', name: '最後にしたい'},
{id: 'A01', name: '名前2'},
];
fix.sort((a,b) => {
if(b.id === '-1') return -1;
if(a.id === '-1') return 1;
return 0;
});
console.log(fix)
[
{ id: 'A00', name: '名前1' },
{ id: 'A01', name: '名前2' },
{ id: '-1', name: '最後にしたい' } // こんな感じで、これだけ最後に回したかった
]
詰まった箇所
fix.sort((a,b) => {
if(b.id === '-1') return -1;
if(a.id === '-1') return 1;
return 0;
});
if(b.id === '-1') return -1;
で、ずっとif(a.id === '-1') return -1;
というふうにしていましたが、これではダメ。
理由はわかりません!!
いまでも、aの方じゃないのかと思っていますが、アルゴリズムをちゃんと理解したらわかる・・・?
わかる方教えてください。。。
a,bは隣接する要素だとは限らない
生成される配列はエンジンによって異なります。例えば、V8(Chrome、Node.js などで使用)や JavaScriptCore(Safari で使用)は配列を全くソートせず、 [3, 1, 4, 1, 5, 9] を返しますが、SpiderMonkey(Firefox で使用)は [1, 1, 3, 4, 5, 9] のように昇順に並べた配列を返すことになります。
要はブラウザとかのjavascriptの動作環境によって、ソートアルゴリズムが異なる。
・バブルソート
・クイックソート
・マージソート
などの種類があるが、エンジンによって違うようです。
なので、a,bは、配列のある位置の前の要素、後ろの要素という考えていると一生正しく理解できない。
押さえるべきこと
- a,bは比較したい要素(隣接と限らない、前後関係もない)
- -1を返す場合は、 a,bにする
- 1を返す場合は、b,aにする
- 0を返す場合はそのまま
ここで、a,bが配列のどの要素の位置はアルゴリズムに依存するので、前後関係も配列要素順とは限らないし、隣接するとも限らない。とにかく、比較対象として抽出された二つの要素同士の順序をどう並べ替えるかの指定を、正負0で指定してやる必要がある。
より詳しくはソートアルゴリズム自体の理解をする必要があるが、そこまでは触れないでおく。
(奥が深い...)
実装上の注意
守るべき5つの制約
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#%E8%A7%A3%E8%AA%AC
こちらの解説
の部分から引用↓↓
- 無害: 比較関数は比較されるオブジェクトや外部の状態を変更しません。(これは重要です。比較関数がいつ、どのように呼び出されるかは保証されていないので、特定の呼び出しが外部に見える影響を及ぼしてはいけません)。
- 安定的: 比較関数は、同じ組の入力に対して常に同じ結果を返します。
- 反射的: compareFn(a, a) === 0 となります。
- 反対称的: compareFn(a, b) と compareFn(b, a) はともに 0 であるか、逆の符号でなければなりません。
- 推移的: compareFn(a, b) と compareFn(b, c) がともに正、0、負のいずれかであれば、 compareFn(a, c) は前の 2 つと同じ符号になります。
特に
return a > b ? -1 : 0
という実装はやりたくなるけど気をつけないといけない。
これが反対照的
になっていないため、安定性が保証されない。
なので、-1, 1を返すパターンはちゃんと対照的にいずれの記述も漏れてはいけない。
sortはミュータブル
sortは元の配列を変更してしまうのでミュータブル。
基本的にミュータブルな実装は避けるべきなので、コピーしてから使うとよい。
[...array].sort() //シャローコピー
structuredClone(array).sort() // ディープコピー
など。