質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

1回答

712閲覧

Python3 複数の時間データのうち、重複しない部分のみ抽出したい

person

総合スコア223

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2022/11/10 05:56

編集2022/11/10 09:21

前提

可能ならpandas使わない。
pandas使わないと実現できないのであれば使用可。

ここに質問の内容を詳しく書いてください。
(例)
TypeScriptで●●なシステムを作っています。
■■な機能を実装中に以下のエラーメッセージが発生しました。

実現したいこと

作業時間、作業停止時間、休憩時間の3種類のデータがあります。

作業時間は開始日時と終了日時がセットで1回。
作業停止時間は開始日時と終了日時がセットで複数回。
休憩時間は開始時刻と終了時刻がセットで複数回。ただし、時分がint型で区別されている。時は23時を超えても0に戻さない(24, 25, ・・・)。

これらのデータから実作業時間を求めたいです。
実作業時間は作業時間から作業停止時間と休憩時間を除いた時間です。

一旦、データを3種類用意まではできていますが、計算まで至っていません。
とりあえず何か試したいとは思っているのですが、
3つとも重なる時間があるときにどのように計算すればいいか分かりません。

ご教授のほど、宜しくお願いします。

ここに実現したいことを箇条書きで書いてください。

  • ▲▲機能を動作するようにする

発生している問題・エラーメッセージ

エラーメッセージ

該当のソースコード

3種類のデータを用意するところまではできているが、時間の計算は未実装。

Python

1from datetime import datetime, timedelta 2 3# hourに24以上を渡したら調整する関数。マスタデータをdatetimeに変換する用 4def org_datetime(year, month, day, hour, minute): 5 if hour > 23: 6 fix_hour = hour % 24 7 add_day = int(hour / 24) 8 return datetime(year, month, day, fix_hour, minute) + timedelta(days=add_day) 9 10 return datetime(year, month, day, hour, minute) 11 12 13# 作業開始終了日時 14workstart = datetime(2022, 11, 10, 10, 0) 15workend= datetime(2022, 11, 10, 16, 0) 16 17# 作業停止日時 18stop = [ 19 {"start": datetime(2022, 11, 10, 8, 0), "end": datetime(2022, 11, 10, 10, 30)}, 20 {"start": datetime(2022, 11, 10, 14, 30), "end": datetime(2022, 11, 10, 15,30)} 21] 22 23# 休憩時刻 24rest = [ 25 {"starthour": 10, "startmin": 0, "endhour": 10, "endmin": 10}, 26 {"starthour": 12, "startmin": 0, "endhour": 12, "endmin": 45}, 27 {"starthour": 15, "startmin": 0, "endhour": 15, "endmin": 10}, 28] 29 30# 休憩時間 31rest_datetime = [] 32for i in rest: 33 rest_datetime.append( 34 {"start": org_datetime(2022, 11, 10, i["starthour"], i["startmin"]), "end": org_datetime(2022, 11, 10, i["endhour"], i["endmin"])}, 35 )

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

作業時間が異常に長くて分単位で列挙が現実的できない場合、開始・終了のイベントを管理するアプローチの方が良いと思います。
現在の状態(status)を0で初期化して、開始・終了のイベントごとに以下のように処理します。

  • 作業開始で+1, 作業終了で-1
  • 休憩開始で-1, 休憩終了で+1
  • 停止時間開始で-1, 停止時間終了で+1

「時刻で昇順、開始と終了が同時刻の場合は右端を含めないように終了を先に」なるようにソートして順番に処理します。
欲しいのはstatusが1→0の区間だと思うので、以下のログだと以下の部分を抜き出せばOKです。

  • 2022-11-10 10:30:00 → 2022-11-10 12:00:00
  • 2022-11-10 12:45:00 → 2022-11-10 14:30:00
  • 2022-11-10 15:30:00 → 2022-11-10 16:00:00

python

1events = [] 2events.append((workstart, 'start', 1)) 3events.append((workend, 'end', -1)) 4for s in stop: 5 events.append((s['start'], 'start', -1)) 6 events.append((s['end'], 'end', 1)) 7for r in rest_datetime: 8 events.append((r['start'], 'start', -1)) 9 events.append((r['end'], 'end', 1)) 10 11status = 0 12for dt, _, v in sorted(events): 13 status += v 14 print(dt, status) 15 16# 2022-11-10 08:00:00 -1 17# 2022-11-10 10:00:00 -2 18# 2022-11-10 10:00:00 -1 19# 2022-11-10 10:10:00 0 20# 2022-11-10 10:30:00 1 21# 2022-11-10 12:00:00 0 22# 2022-11-10 12:45:00 1 23# 2022-11-10 14:30:00 0 24# 2022-11-10 15:00:00 -1 25# 2022-11-10 15:10:00 0 26# 2022-11-10 15:30:00 1 27# 2022-11-10 16:00:00 0

以下は以前の回答

「作業時間」とか「休憩時間」と書いてあるので、人間が作業しているデータを扱っているのだと思います。だとすれば作業時間はせいぜい丸1日くらいで、分単位で列挙しても24x60=1,440通りにしかなりません。これくらいであれば、作業時間・停止時間・休憩時間 全てを分単位で列挙して計算するのが簡単だと思います。

set型のデータに 作業時間・停止時間・休憩時間 をそれぞれ記録して引き算すれば、作業時間に含まれる要素のうち、停止時間・休憩時間に含まれない要素を計算できます。

※注意※
以下のコードでは開始時刻 datetime(2022, 11, 10, 10, 0)、終了時刻 datetime(2022, 11, 10, 11, 0) の場合、10:00〜10:59 までの60分と11:00 の合計61分として計算するようになっています。範囲の右端を含めたくない場合は適当に修正してください。

python

1# 質問者さんのコード(省略) 2 3def enumerate_time(lst): 4 res = set() 5 for l in lst: 6 start = l['start'] 7 end = l['end'] 8 while start <= end: 9 res.add(start) 10 start += timedelta(minutes=1) 11 return res 12 13work_time = enumerate_time([{'start': workstart, 'end': workend}]) 14print(f'働いた分数={len(work_time)}') 15 16stop_time = enumerate_time(stop) 17rest_time = enumerate_time(rest_datetime) 18work_time = work_time - stop_time - rest_time 19print(*sorted(work_time), sep='\n') 20print(f'停止・休憩を引いた残り={len(work_time)}')

投稿2022/11/10 11:41

編集2022/11/14 03:51
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

person

2022/11/11 02:35

ありがとうございます。期待通りの結果になりました。 各時刻データで重複する要素を取り除くんですね。 > 範囲の右端を含めたくない場合は適当に修正してください。 含めたくないので、enumerate_time()の while start <= end を while start < end とします。
person

2022/11/14 01:47 編集

質問をクローズした後で申し訳ないのですが、 > 人間が作業しているデータを扱っているのだと思います。だとすれば作業時間はせいぜい丸1日くらいで、分単位で列挙しても24x60=1,440通りにしかなりません。 この記載に該当しない、つまり扱う時間データの数が多い場合はMemoryErrorを起こすということでしょうか。 その場合の計算方法をご存じでしたら教えて下さい。 (もし別の質問としてteratailに投げる方がよければご指摘ください。)
退会済みユーザー

退会済みユーザー

2022/11/14 03:40

常識的な作業時間(たとえばロボットに作業させて最長で1年間とか)であれば、いまどきのPCであればメモリも処理時間も問題にならないと思います。 ただ、競技プログラミングのようなケースで「作業時間は最長で1,000,000,000年とする」のような場合、分単位で列挙できないので別アプローチが必要です。参考プログラムを回答に追記しておきます。
person

2022/11/14 05:08

すみません。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問