2.1461806日は何日何時間何分何秒なのか、34.590707は何度何分何秒なのか、の計算

「中途半端に簡単」な計算というヤツは…

随分前からこの計算、「あー、また必要になりやがった」のたんびに、頭を掻きむしってなんなら30分くらいで「簡単に」書いてしまう。つまり「ほどよく毎度疲労する」。簡単だけれども、ちょっと調子が悪いだけで「あ、間違えた、どうだっけ?」とやや試行錯誤ちっくになりながら、MAX で30分程度は迷走するわけだ。(MIN で数分なのが、また憎たらしい。)

何ゆえにこうなるのかといえば、この問題の一般化の名前を知らないから、に尽きる。なんて呼べばいいの、この問題? だから結果として毎度「良い方法を調べる」ことに思いが至らずに、結局毎度書いてしまう。大抵はこうだと思う:

1 # -*- coding: utf-8 -*-
2 def deg2dms(src):
3     deg = int(src)
4     min = int((src - deg) * 60)
5     sec = ((src - deg - min / 60.)) * 3600
6     return (deg, min, sec)

見出しからわかると思うけれど、何度必要になってそのたびに書こうとも、「いまは緯度経度の度分秒換算だけれど、これって時間の計算でも同じはずだから一般化できるはずなんだよなぁ」と、毎度気持ちが悪い。

なのでいい加減、一般化しときたい、と思った。

これ:

\(
\displaystyle c = \frac{a_1\prod _{ k=1 }^{ n-1 }{ b_k } + a_2\prod _{ k=2 }^{ n-1 }{ b_k }+ …+a_{n-1}\prod _{ k=n-1 }^{ n-1 }{ b_k }+a_n }{\prod _{ k=1 }^{ n-1 }{ b_k }}
\)

の \(a_1, …, a_n\)を決める問題、で、合ってる? \(b_1, …, b_{n-1}\) は、(24[時間], 60[分], 60[秒]) あるいは (60[分], 60[秒]) のようなもの、として。

ちょいちょい苦労しつつ、こんな:

 1 # -*- coding: utf-8 -*-
 2 def deg2dms(src, factors):
 3     _mul = lambda x, y: x * y
 4     result = []
 5     d = src * reduce(_mul, factors)
 6     for i in range(len(factors)):
 7         d, m = divmod(d, reduce(_mul, factors[i:]))
 8         result.append(d)
 9         d = m
10     result.append(m)
11     return result
12 
13 print(deg2dms(2.1461806, (24, 60, 60)))
14 # => [2.0, 3.0, 30.0, 30.00384000001941]
15 
16 deg, min, sec = deg2dms(34.590707, (60, 60))
17 print(deg, min, sec)
18 # => (34.0, 35.0, 26.545200000007753)
19 print(deg + min / 60. + sec / 3600.)
20 # => 34.590707

期待通りの結果にはなってる、かな?

さて、「この問題の一般化の名前を知らない」ということは…? なんて名前を付けりゃいいんでしょうか、これ? そして問題は、Python 標準添付ライブラリにもしこれがあっても、名前が想像付かないので、わからない、ってことだ :-) 相当に一般的なタスクと思うので、あっても良さそうなんだけどなぁ…。(というか NumPy, SciPy あたりにはありそうな気もする。)

20分後の追記:
もうちょい合理的に書けるね:

 1 def deg2dms(src, factors):
 2     result = []
 3     f = reduce(lambda x, y: x * y, factors)
 4     d = src * f
 5     for fi in factors:
 6         d, m = divmod(d, f)
 7         result.append(d)
 8         d = m
 9         f /= fi
10     result.append(m)
11     return result

何度も総乗計算しなくてもいい。

1時間20分後の追記:
operator モジュール使えば少しスッキリする:

 1 import operator
 2 
 3 
 4 def deg2dms(src, factors):
 5     result = []
 6     f = reduce(operator.mul, factors)
 7     d = src * f
 8     for fi in factors:
 9         d, m = divmod(d, f)
10         result.append(d)
11         d = m
12         f /= fi
13     result.append(m)
14     return result

operator モジュール使っとけば、逆変換でも使えるのでありがたい。

 1 # -*- coding: utf-8 -*-
 2 import numpy as np
 3 import operator
 4 # ...
 5 fac = (24, 60, 60)
 6 print(deg2dms(2.1461806, fac))
 7 
 8 print(
 9     np.dot(
10         deg2dms(2.1461806, fac),
11         [1] + [1./reduce(operator.mul, fac[:i + 1]) for i in range(len(fac))])
12     )

(なお、dot積使うと一行で書けるからそうしてるけど、別にそうしなくてもいい。)

08:00追記:
最初から気付いてたことだけどあえて触れてなかったのは、これは「3221225472Bytesは何G何M何kか?」という問題にも、ほんの少し問題を変更するだけで適用出来る。数式ではこういうこと:

\(
\displaystyle c = a_1\prod _{ k=1 }^{ n-1 }{ b_k } + a_2\prod _{ k=2 }^{ n-1 }{ b_k }+ …+a_{n-1}\prod _{ k=n-1 }^{ n-1 }{ b_k }+a_n
\)

つまり、プログラムでは最初に総乗を掛けるか掛けないかだけが違う問題。

で、「名前」は…、split_by_units とかかなぁ…?

 1 # -*- coding: utf-8 -*-
 2 import numpy as np
 3 import operator
 4 
 5 def split_by_units(src, factors, first_prod=True):
 6     result = []
 7     f = reduce(operator.mul, factors)
 8     if first_prod:
 9         d = src * f
10     else:
11         d = src
12     for fi in factors:
13         d, m = divmod(d, f)
14         result.append(d)
15         d = m
16         f /= fi
17     result.append(m)
18     return result
19 
20 
21 fac = (24, 60, 60)
22 print(split_by_units(2.1461806, fac))
23 # => [2.0, 3.0, 30.0, 30.00384000001941]
24 
25 print(
26     np.dot(
27         split_by_units(2.1461806, fac),
28         [1] + [1./reduce(operator.mul, fac[:i + 1]) for i in range(len(fac))])
29     )
30 # => 2.1461806
31 
32 lat = 34.590707  #
33 deg, min, sec = split_by_units(lat, (60, 60))
34 print(deg, min, sec)
35 # => (34.0, 35.0, 26.545200000007753)
36 print(deg + min / 60. + sec / 3600.)
37 # => 34.590707
38 
39 print(split_by_units(
40         3221225472,
41         [1024, 1024, 1024], first_prod=False))
42 # => [3L, 0L, 0L, 0L]
43 print(split_by_units(
44         3221225472 + 3 * 1024*1024 + 4 * 1024 + 5,
45         [1024, 1024, 1024], first_prod=False))
46 # => [3L, 3L, 4L, 5L]







2016-02-11 追記:
「この演算の名前がわからない」だけれども、ふと別件での調べ物で関係しそうなものをみつけた。整数の合同。これが答えかどうかまでは理解しきれてないけど、関係はしてそうだ。なのでこれが答えでなくてもこの系統のトピックだろう、きっと。(別件、というのは「ハッシュ関数」の説明で出てきた「prime modulus」。)


2021-04-18 追記:
これを書いた頃にも python 3.x を使ってたので、なんでそこに神経を注がなかったのかちょっと謎。一箇所だけ python 3.x で NG な部分がある。

わかる人はすぐにわかる。「reduce」ね。これ、2.x までは builtins の一味だったのだけれど、3.x からはこれが破棄されて、functools モジュールにあるものだけになった。python 2.7 はこの移行のために必要な「functools.reduce」も提供されてたので、最初から「python 2.7 と 3.x 両方で動く」ものは書けた。なんかすまん、結構意識的にやってたつもりなんだけどね、抜けることもある。