teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

1

順序や重複を維持しないというパターンも追加

2017/06/11 02:34

投稿

raccy
raccy

スコア21767

answer CHANGED
@@ -1,19 +1,45 @@
1
- Listからの削除は**大変重い処理**です。これはListが「配列(array)」と言われるデータ構造であるため、インデックスアクセスがO(1)になる代わりに、削除がO(n)になってしまうからです。
1
+ Listからの削除は**大変重い処理**です。これはListが「配列(array)」と言われるデータ構造であるため、インデックスアクセスがO(1)になる代わりに、削除がO(n)になってしまうからです。ましてや、Removeで添字ではなくオブジェクトを指定する場合は、検索もO(n)になるため、さらに遅い原因になります。
2
2
 
3
- 解決策は二ほどです。
3
+ まり、オリジナルコードは削除のリスト分繰り返しているためO(n × m)です。(nはmainのサイズ、mはdeleteのサイズ)
4
4
 
5
- ###連結リストを使う
5
+ 解決策は二つ(順序重複維持あり)+二つ(順序重複維持なし)ほどです。
6
6
 
7
+ ###順序と重複を維持する場合
8
+
9
+ 順序を維持し、また、重複している場合は重複したままにする場合です。
10
+
11
+ ####連結リストを使う
12
+
7
13
  「連結リスト(linked list)」といわれるデータ構造の場合は、削除はO(1)になります(代わりに、インデックスアクセスがO(n)です)。C#では連結リストとしてLinkedListが用意されていますので、こちらを使用すれば、速度アップになります。
8
14
 
9
15
  **と言いたいところですが、この方法ではそれほど速くなりません。**
10
16
 
11
- なぜなら、Removeで該当の項目を探す時点でO(n)の計算量が必要になるからです。削除が速いだけで見つけるのが速くなるわけではありません。
17
+ なぜなら、Removeで該当のオブジェクトを探す検索がO(n)の計算量が必要になるからです。削除が速いだけで見つけるのが速くなるわけではありません。結局O(n × m)になってしまいます。
12
18
 
13
- ###新たに配列を作る
19
+ ####新たに配列を作る
14
20
 
15
21
  削除するのではなく、削除対象ではない物だけ抜き出して、新しい配列にします。処理はLINQを使うとコードがすっきりするでしょう。また、削除のリストにHashSetを使うと削除対象かどうかのチェックも高速に行うことができます。
16
22
 
23
+ HashSetの作成はO(n)ですが、検索はO(1)です。配列作成も内部がO(1)ならO(n)になりますので、計算量はO(n + m)とかなり改善されます。
24
+
25
+ ###順序と重複を維持しない場合
26
+
27
+ 順序を維持し、また、重複している場合は重複したままにする場合です。
28
+
29
+ ####並び替え済みリストを使う
30
+
31
+ 並び替え済みリストの場合、検索がO(1)と高速になります。C#には並び替え済みのSortedListがありますのでこれが使用できます。ただし、削除はO(n)であり、オリジナルより速い程度です。結局O(n × m)になってしまいます。
32
+
33
+ 重複は維持されますが、順序はソートされるために維持できません。
34
+
35
+ ####ハッシュ化された集合を使う
36
+
37
+ 順序や重複を維持しなければいいのであれば、ハッシュ化された集合が使用できます。検索も削除もほぼO(1)になるため、極めて高速です。
38
+
39
+ ハッシュ化された集合としてHashSetが使用できます。なお、HashSetでは順序もそのまま維持されるようです(正式ドキュメントが見つけられなかったので、知っている人は教えて欲しい)。ただし、重複は維持されません。
40
+
41
+ ただし、ハッシュ化された集合自体の作成にO(n)必要になりますので、そこは注意してください。ということで、計算量はO(n + m)とかなり改善されます。。
42
+
17
43
  ###検証コード
18
44
 
19
45
  ```C#
@@ -55,9 +81,9 @@
55
81
  deleteStopwatch.Stop();
56
82
  Console.WriteLine("delete 作成完了: {0} ms",
57
83
  deleteStopwatch.ElapsedMilliseconds);
58
-
84
+
59
- Console.WriteLine();
85
+ Console.WriteLine();
60
-
86
+
61
87
  List<string> list;
62
88
 
63
89
  list = Original(new List<String>(mainList),
@@ -74,10 +100,24 @@
74
100
 
75
101
  Console.WriteLine();
76
102
 
77
- list = UseHashset(new List<String>(mainList),
103
+ list = CreateListWithHashset(new List<String>(mainList),
78
104
  new List<String>(deleteList));
79
105
  Console.WriteLine("サイズ: {0}", list.Count);
80
106
  Console.WriteLine("最初: {0}", list[0]);
107
+
108
+ Console.WriteLine();
109
+
110
+ list = UseSortedList(new List<String>(mainList),
111
+ new List<String>(deleteList));
112
+ Console.WriteLine("サイズ: {0}", list.Count);
113
+ Console.WriteLine("最初: {0}", list[0]);
114
+
115
+ Console.WriteLine();
116
+
117
+ list = UseHashSet(new List<String>(mainList),
118
+ new List<String>(deleteList));
119
+ Console.WriteLine("サイズ: {0}", list.Count);
120
+ Console.WriteLine("最初: {0}", list[0]);
81
121
  }
82
122
 
83
123
  static List<String> Original(List<String> mainList,
@@ -120,10 +160,11 @@
120
160
  runStopwatch.ElapsedMilliseconds);
121
161
  return newList;
122
162
  }
163
+
123
- static List<String> UseHashset(List<String> mainList,
164
+ static List<String> CreateListWithHashset(List<String> mainList,
124
165
  List<String> deleteList)
125
166
  {
126
- Console.WriteLine("start UseHashset");
167
+ Console.WriteLine("start CreateListWithHashset");
127
168
  var runStopwatch = new Stopwatch();
128
169
  runStopwatch.Start();
129
170
 
@@ -137,6 +178,51 @@
137
178
  runStopwatch.ElapsedMilliseconds);
138
179
  return newList;
139
180
  }
181
+
182
+ static List<String> UseSortedList(List<String> mainList,
183
+ List<String> deleteList)
184
+ {
185
+ Console.WriteLine("start UseSortedList");
186
+ var runStopwatch = new Stopwatch();
187
+ runStopwatch.Start();
188
+
189
+ // SortedListに変換して処理をする。
190
+ var mainSortedList = new SortedList<String, String>(
191
+ mainList.ToDictionary(s => s));
192
+ foreach (string deleteStr in deleteList)
193
+ {
194
+ mainSortedList.Remove(deleteStr);
195
+ }
196
+ var newList = mainSortedList.Select(pair => pair.Value)
197
+ .ToList();
198
+
199
+ runStopwatch.Stop();
200
+ Console.WriteLine("run 完了: {0} ms",
201
+ runStopwatch.ElapsedMilliseconds);
202
+ return newList;
203
+ }
204
+
205
+ static List<String> UseHashSet(List<String> mainList,
206
+ List<String> deleteList)
207
+ {
208
+ Console.WriteLine("start UseHashSet");
209
+ var runStopwatch = new Stopwatch();
210
+ runStopwatch.Start();
211
+
212
+ // HashSetに変換して処理をする。
213
+ var mainHashSet = new HashSet<String>(mainList);
214
+ foreach (string deleteStr in deleteList)
215
+ {
216
+ mainHashSet.Remove(deleteStr);
217
+ }
218
+ var newList = mainHashSet.ToList();
219
+
220
+ runStopwatch.Stop();
221
+ Console.WriteLine("run 完了: {0} ms",
222
+ runStopwatch.ElapsedMilliseconds);
223
+ return newList;
224
+ }
225
+
140
226
  }
141
227
  ```
142
228
 
@@ -146,21 +232,33 @@
146
232
 
147
233
  ```
148
234
  60000個中5000個を削除
149
- main 作成完了: 330 ms
235
+ main 作成完了: 368 ms
150
- delete 作成完了: 23 ms
236
+ delete 作成完了: 26 ms
151
237
 
152
238
  start Original
153
- run 完了: 3780 ms
239
+ run 完了: 4572 ms
154
240
  サイズ: 55000
155
- 最初: 52512
241
+ 最初: 57539
156
242
 
157
243
  start UseLinkedList
158
- run 完了: 2846 ms
244
+ run 完了: 3258 ms
159
245
  サイズ: 55000
160
- 最初: 52512
246
+ 最初: 57539
161
247
 
162
- start UseHashset
248
+ start CreateListWithHashset
163
249
  run 完了: 13 ms
164
250
  サイズ: 55000
165
- 最初: 52512
251
+ 最初: 57539
252
+
253
+ start UseSortedList
254
+ run 完了: 2628 ms
255
+ サイズ: 55000
256
+ 最初: 10000
257
+
258
+ start UseHashSet
259
+ run 完了: 16 ms
260
+ サイズ: 55000
261
+ 最初: 57539
166
- ```
262
+ ```
263
+
264
+ UseSortedListの最初の要素は異なります。UseHashSetの場合は重複が維持されないことに注意です。なお、順序づけしておきたい場合はSortedSetを使うという手段もあります。