回答編集履歴

4

ソース修正

2021/09/04 03:23

投稿

退会済みユーザー
test CHANGED
@@ -335,3 +335,255 @@
335
335
 
336
336
 
337
337
  (※上記のコード、だいぶ無駄なことをやってそうな気がするので、いつか直せるといいかなと思うてます。)
338
+
339
+
340
+
341
+
342
+
343
+ ### 追記(2021/9/4)
344
+
345
+
346
+
347
+ 上記の、領域の始点、終点が整数とは限らない場合のコードを以下の点で見直しました。(修正後のコードは、[replitにも上げています。](https://replit.com/@suwmn50799/357204teratail) 実行するとテスト結果が出力されます。)
348
+
349
+
350
+
351
+ - get_merged_spans(spans) の中で、走査対象の領域リストを、リストの先頭要素の領域と、2番目以降の残りの領域リストに分けて、先頭の領域と、残りの領域リストの各領域とを統合するときに、残りの領域リストのすべてに対して統合を試みていたのは無駄だったので、ひとつでも統合できる領域がみつかったら置き換えて、その後は統合を試みないでループを抜けるように修正
352
+
353
+
354
+
355
+ - get_merged_spans(spans)で与えられる引数のspansを、各領域の中点の昇順にソートしてから、統合のループに入るようにした。
356
+
357
+
358
+
359
+ - merge(a, b) は、統合可否のフラグも返していたが、統合した領域のみを返し、統合できない場合はNoneを返すようにした。
360
+
361
+
362
+
363
+ 上記の3点で修正コードが以下です。
364
+
365
+
366
+
367
+
368
+
369
+ ```python
370
+
371
+ #
372
+
373
+ # merge(a, b)
374
+
375
+ #
376
+
377
+ # 二つの領域 a, b を受け取り、 それらが統合できれば統合した領域を返す。
378
+
379
+ # 統合できなければ、Noneを返す。
380
+
381
+ #
382
+
383
+ def merge(a, b):
384
+
385
+
386
+
387
+ # a が b を含む場合は a を返す
388
+
389
+ if a[0] <= b[0] and b[1] <= a[1]:
390
+
391
+ return a
392
+
393
+
394
+
395
+ # b が a を含む場合は、b を返す
396
+
397
+ if b[0] <= a[0] and a[1] <= b[1]:
398
+
399
+ return b
400
+
401
+
402
+
403
+ # 上記以外の場合で、共通部分を持つ場合
404
+
405
+ if a[0] <= b[1] and b[0] <= a[1]:
406
+
407
+ c0 = min([a[0], b[0]])
408
+
409
+ c1 = max([a[1], b[1]])
410
+
411
+ return [c0, c1]
412
+
413
+
414
+
415
+ # aとbが共通部分を持たない場合
416
+
417
+ return None
418
+
419
+
420
+
421
+
422
+
423
+ #
424
+
425
+ # get_merged_spans(spans)
426
+
427
+ #
428
+
429
+ # 領域のリストを受け取り、統合できる領域を統合し尽くした領域リストを返す。
430
+
431
+ # (※引数の spans を変更しない)
432
+
433
+ #
434
+
435
+ def get_merged_spans(spans):
436
+
437
+
438
+
439
+ # 引数の領域リストを各領域の中点で昇順ソートしたリストを作成し、走査対象のリストとする。
440
+
441
+ target_spans = sorted(spans, key=lambda span: (span[0] + span[1]) / 2)
442
+
443
+
444
+
445
+ # 統合され尽くした領域リストを初期化(このリストを完成させて返す。)
446
+
447
+ merged_spans = []
448
+
449
+
450
+
451
+ # 統合され尽くした領域リストを作るループ(外側のループ)
452
+
453
+ while True:
454
+
455
+
456
+
457
+ # 先頭の領域と、2番目以降の領域リストに分割
458
+
459
+ head, *rest = target_spans
460
+
461
+
462
+
463
+ # 2番目以降の領域リストが空である場合
464
+
465
+ if len(rest) == 0:
466
+
467
+ # 先頭の領域を、統合され尽くした領域リストに追加して、外側のループを抜ける
468
+
469
+ merged_spans.append(head)
470
+
471
+ break
472
+
473
+
474
+
475
+ # 先頭の領域と、2番目以降の各領域を統合するループ(内側のループ)
476
+
477
+ for i, span in enumerate(rest):
478
+
479
+ # 先頭の領域 head と、2番目以降の領域 span を統合を試みる。
480
+
481
+ merged_span = merge(head, span)
482
+
483
+
484
+
485
+ # 統合される場合、統合された領域で置き換えて、内側のループをbreakで抜ける。
486
+
487
+ if merged_span:
488
+
489
+ rest[i] = merged_span
490
+
491
+ break
492
+
493
+ # 先頭の領域が、2番目以降の領域のどれとも統合できなかった場合(内側のループをbreakで抜けなかった場合)
494
+
495
+ else:
496
+
497
+ # 先頭の領域を、統合され尽くした領域リストに追加
498
+
499
+ merged_spans.append(head)
500
+
501
+
502
+
503
+ # 走査対象のリストを2番目以降のリストに置き換えて、外側のループのはじめに戻る
504
+
505
+ target_spans = rest
506
+
507
+
508
+
509
+ # 統合され尽くした領域リストを返す。
510
+
511
+ return merged_spans
512
+
513
+
514
+
515
+
516
+
517
+
518
+
519
+ if __name__ == '__main__':
520
+
521
+
522
+
523
+ from random import random, shuffle
524
+
525
+
526
+
527
+ # 統合すると [0, 20] になる 7 つの領域を含むリスト(端点はともに整数)
528
+
529
+ base_spans = [[0, 3], [2, 4], [1, 6], [5, 11], [7, 10], [8, 17], [16, 20],]
530
+
531
+
532
+
533
+ # テスト1: base_spansをシャッフルしたリスト
534
+
535
+ test1_spans = base_spans
536
+
537
+ shuffle(test1_spans)
538
+
539
+ result = get_merged_spans(test1_spans)
540
+
541
+ print(result)
542
+
543
+
544
+
545
+ # テスト2: base_spansの各領域の端点に0以上1未満の小数を加えてシャッフルしたリスト
546
+
547
+ test2_spans = [[a[0] + random(), a[1] + random()] for a in base_spans]
548
+
549
+ shuffle(test2_spans)
550
+
551
+ result = get_merged_spans(test2_spans)
552
+
553
+ print(result)
554
+
555
+
556
+
557
+ # テスト3: base_spansを +1000 および +2000 移動した領域リストをbase_spansに追加したリスト
558
+
559
+ test3_spans = [
560
+
561
+ *base_spans,
562
+
563
+ *[[a[0] + 1000, a[1] + 1000] for a in base_spans],
564
+
565
+ *[[a[0] + 2000, a[1] + 2000] for a in base_spans]
566
+
567
+ ]
568
+
569
+ shuffle(test3_spans)
570
+
571
+ result = get_merged_spans(test3_spans)
572
+
573
+ print(result)
574
+
575
+
576
+
577
+ # テスト4: テスト3の対象リストの各領域の端点に0以上1未満の小数を加えてシャッフルしたリスト
578
+
579
+ test4_spans = [[a[0] + random(), a[1] + random()] for a in test3_spans]
580
+
581
+ shuffle(test4_spans)
582
+
583
+ result = get_merged_spans(test4_spans)
584
+
585
+ print(result)
586
+
587
+
588
+
589
+ ```

3

ソース修正

2021/09/04 03:23

投稿

退会済みユーザー
test CHANGED
@@ -329,3 +329,9 @@
329
329
 
330
330
 
331
331
  ➡ [サンプル](https://replit.com/@suwmn50799/Si-357204-Shi-Shu-Dui-Ying#main.py)
332
+
333
+
334
+
335
+
336
+
337
+ (※上記のコード、だいぶ無駄なことをやってそうな気がするので、いつか直せるといいかなと思うてます。)

2

ソース修正

2021/09/01 14:11

投稿

退会済みユーザー
test CHANGED
@@ -159,3 +159,173 @@
159
159
 
160
160
 
161
161
  ➡ 一応 上に書いた確認のコードは [replitゆうWEBに挙げときました。](https://replit.com/@suwmn50799/Si-357204-1) (Run 押してもろたらすぐ実行できるで。)
162
+
163
+
164
+
165
+ ---
166
+
167
+
168
+
169
+ **補足:**
170
+
171
+
172
+
173
+ 領域の始点、終点が整数とは限らない場合、どうなるんやろ?とモヤモヤしたもんで、作ってみました。参考になれば幸いですー
174
+
175
+ ```python
176
+
177
+ #
178
+
179
+ # merge(a, b): 2つの領域(閉区間) a と b を受け取り、以下のような2つの要素をもつタプルを返す関数
180
+
181
+ #   第1要素: a と b に共通部分があった場合、True 。共通部分がなかった場合は False
182
+
183
+ #   第2要素: a と b に共通部分があった場合は統合された領域。共通部分がなかった場合は b
184
+
185
+ #
186
+
187
+ def merge(a, b):
188
+
189
+ if a[0] <= b[1] and b[0] <= a[1]:
190
+
191
+ c0 = min([a[0], b[0]])
192
+
193
+ c1 = max([a[1], b[1]])
194
+
195
+ return True, [c0, c1]
196
+
197
+ elif a[0] <= b[0] and b[1] <= a[1]:
198
+
199
+ return True, a
200
+
201
+ elif b[0] <= a[0] and a[1] <= b[1]:
202
+
203
+ return True, b
204
+
205
+
206
+
207
+ return False, b
208
+
209
+
210
+
211
+
212
+
213
+ #
214
+
215
+ # get_merged_spans(spans):領域のリストを受け取り、それに含まれる領域を統合し尽くした領域のリストを返す
216
+
217
+ #
218
+
219
+ def get_merged_spans(spans):
220
+
221
+ merged_spans = []
222
+
223
+
224
+
225
+ while True:
226
+
227
+ head, *rest = spans
228
+
229
+ if len(rest) == 0:
230
+
231
+ merged_spans.append(head)
232
+
233
+ break
234
+
235
+
236
+
237
+ next_spans = [*map(lambda span: merge(head, span), rest)]
238
+
239
+ if any(flag for flag, _ in next_spans):
240
+
241
+ spans = [span for _, span in next_spans]
242
+
243
+ else:
244
+
245
+ merged_spans.append(head)
246
+
247
+ spans = rest
248
+
249
+
250
+
251
+ return sorted(merged_spans)
252
+
253
+
254
+
255
+
256
+
257
+
258
+
259
+ # 以下テスト実行
260
+
261
+ if __name__ == '__main__':
262
+
263
+ import random
264
+
265
+
266
+
267
+ # マージすると、[0, 20] になる 7個の(整数)領域のリスト
268
+
269
+ spans_1 = [
270
+
271
+ [0, 3], [1, 4], [2, 6], [5, 9], [7, 10], [8, 17], [16, 20],
272
+
273
+ ]
274
+
275
+ # spans_1 の各区間を、正方向に1000移動した区間のリスト
276
+
277
+ spans_2 = [[start + 1000, end + 1000] for start, end in spans_1]
278
+
279
+ # spans_1 の各区間を、正方向に200移動した区間のリスト
280
+
281
+ spans_3 = [[start + 2000, end + 2000] for start, end in spans_1]
282
+
283
+
284
+
285
+ # spans_1, spans_2, spans_3 を結合したリストを作る。(3×7=21個の閉区間を含む)
286
+
287
+ target_spans = [*spans_1, *spans_2, *spans_3]
288
+
289
+
290
+
291
+ # target_spansに含まれる全領域の始点と終点に、1未満で小数点以下最大3桁までのランダムな小数を加える。
292
+
293
+ target_spans = list(map(lambda span: [
294
+
295
+ span[0] + (int(random.random() * 1000) / 1000),
296
+
297
+ span[1] + (int(random.random() * 1000) / 1000),
298
+
299
+ ], target_spans))
300
+
301
+
302
+
303
+ # シャッフルする
304
+
305
+ random.shuffle(target_spans)
306
+
307
+
308
+
309
+ # マージされた領域のリストを取得する。
310
+
311
+ merged_spans = get_merged_spans(target_spans)
312
+
313
+
314
+
315
+ # マージされた領域の開始と終了のリストを作る
316
+
317
+ update_start_lis = [start for start, _ in merged_spans]
318
+
319
+ update_end_lis = [end for _, end in merged_spans]
320
+
321
+
322
+
323
+ print(update_start_lis, update_end_lis)
324
+
325
+
326
+
327
+ ```
328
+
329
+
330
+
331
+ ➡ [サンプル](https://replit.com/@suwmn50799/Si-357204-Shi-Shu-Dui-Ying#main.py)

1

ソース修正

2021/09/01 13:50

投稿

退会済みユーザー
test CHANGED
@@ -142,7 +142,11 @@
142
142
 
143
143
 
144
144
 
145
+ ```
146
+
145
- **[0, 2000] [20, 1020]**
147
+ [0, 2000] [20, 1020]
148
+
149
+ ```
146
150
 
147
151
 
148
152
 
@@ -154,4 +158,4 @@
154
158
 
155
159
 
156
160
 
157
- ➡ 一応 上に書いた確認のコードは [replitゆうWEBに挙げときました。](https://replit.com/@suwmn50799/Si-357204-1)
161
+ ➡ 一応 上に書いた確認のコードは [replitゆうWEBに挙げときました。](https://replit.com/@suwmn50799/Si-357204-1) (Run 押してもろたらすぐ実行できるで。)