回答編集履歴

1

追記

2020/05/07 08:38

投稿

hoshi-takanori
hoshi-takanori

スコア7901

test CHANGED
@@ -108,4 +108,114 @@
108
108
 
109
109
 
110
110
 
111
- という動作になります。(厳密には、stableSort の中で comparator をさらにラップしてますが、説明はパス。)
111
+ という動作になります。
112
+
113
+
114
+
115
+ ---
116
+
117
+
118
+
119
+ 追記。ついでなので、stableSort 関数についても解説しておきます。
120
+
121
+
122
+
123
+ まず、JavaScript の [sort メソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) ですが、二つ問題があります。
124
+
125
+ a) 配列を書き換える
126
+
127
+ b) ソート順が安定でない
128
+
129
+
130
+
131
+ a ですが、例えば上記の data に対して data.sort(comparator) を実行すると、配列 data の中身が書き換わります。(ちなみに、data を const で宣言してますが、これはあくまでも data への参照が const なだけで、data の中身は const ではありません。)
132
+
133
+ 配列 data の中身が書き変わると元の順序が失われるだけでなく、React (Material UI って確か React でしたよね?) ではこのような破壊的なデータの変更は禁忌とされています。
134
+
135
+ 参考: [Reactのべからず集 - Qiita](https://qiita.com/jkr_2255/items/041f238a940f923e4dfc)
136
+
137
+
138
+
139
+ b ですが、ソート順が安定 (stable) というのは、例えば User の配列を name でソートした時に、name が同じユーザーが複数いれば元の順序を保証する、ということです。残念ながら JavaScript の sort メソッドは stable とは限らない (処理系やブラウザによって異なる) ため、
140
+
141
+
142
+
143
+ ```TypeScript
144
+
145
+ const data2: User[] = [
146
+
147
+ { id: 1, name: 'Taro' },
148
+
149
+ { id: 2, name: 'Taro' },
150
+
151
+ ];
152
+
153
+ ```
154
+
155
+
156
+
157
+ だった場合に data2.sort(comparator) の結果 id: 2 が先になる可能性があります。
158
+
159
+ 参考: [安定ソート - Wikipedia](https://ja.wikipedia.org/wiki/%E5%AE%89%E5%AE%9A%E3%82%BD%E3%83%BC%E3%83%88)
160
+
161
+
162
+
163
+ これらを解決するために定義されたのが stableSort 関数で、(微妙に書き換えました。)
164
+
165
+
166
+
167
+ ```TypeScript
168
+
169
+ function stableSort<T>(array: T[], comparator: (a: T, b: T) => number): T[] {
170
+
171
+ const stabilizedThis = array.map((el, index) => [el, index] as [T, number]);
172
+
173
+ const stableComparator = (a: [T, number], b: [T, number]): number => {
174
+
175
+ const order = comparator(a[0], b[0]);
176
+
177
+ if (order !== 0) return order;
178
+
179
+ return a[1] - b[1];
180
+
181
+ };
182
+
183
+ stabilizedThis.sort(stableComparator);
184
+
185
+ return stabilizedThis.map((el) => el[0]);
186
+
187
+ }
188
+
189
+ ```
190
+
191
+
192
+
193
+ まず stabilizedThis というのは stable 化するために作った新しい配列で、元の array は T の配列ですが、こちらは `[T, number]` というタプルの配列になってます。タプルというのは、意味的には要素名のない構造体のようなもので、最初の要素は T 型の値で、次の要素は number 型の値、ということです。実際には JavaScript の配列 (長さは固定で、要素の位置によって型が異なる!) として実装されています。
194
+
195
+ 参考: [[TypeScript] 配列型とタプル型 | HIROs.NET Blog](https://blog.hiros-dot.net/?p=8670)
196
+
197
+
198
+
199
+ 上記 data2 に対する stabilizedThis の値は、こんな感じになります。
200
+
201
+
202
+
203
+ ```TypeScript
204
+
205
+ const stabilizedThis: [User, number][] = [
206
+
207
+ [{ id: 1, name: 'Taro' }, 0],
208
+
209
+ [{ id: 2, name: 'Taro' }, 1],
210
+
211
+ ];
212
+
213
+ ```
214
+
215
+
216
+
217
+ 次に stableComparator は comparator をラップした関数で、`[T, number]` 型の引数 a, b を比較するものです。最初に comparator を使って a[0] と b[0] つまり元々の配列の要素を比較し、結果が 0 つまり目的のプロパティの値が同じならば、a[1] と b[1] つまり元の配列における要素のインデックスを比較します。これにより、二つの `[T, number]` が同じになることはないため、stable でない sort を使っても結果は stable になります。
218
+
219
+
220
+
221
+ そして、最終的に stabilizedThis のソート結果から T 型の値だけを取り出した新しい配列を作って返します。これによって元の配列はそのままで新しい配列を返すことになります。