Three Points Circle (Electronic Station) - 3点を通る円の方程式を求める
どんな問題?
Three Points Circle
http://www.checkio.org/mission/three-points-circle/
3点を通る円の方程式を求めよ。
ただし、中心が(a,b)、半径rの円の方程式は以下の通り。
(x-a)^2+(y-b)^2=r^2
その他の条件
- 3点は一直線上に無いものとする。
- x,y,r < 10 とする。(※)
- 引数の3点の座標は "(2,2),(4,2),(2,4)" のような文字列で与えられる。
- 戻り値の方程式は "(x-4)^2+(y-4)^2=2.83^2" のような文字列で返す。
- 数字の余分なゼロや小数点は除去せよ。
問題文には書かれていないが、例を見る限り、数字は小数点2桁に丸めるようだ。余分なゼロや小数点は除去、というのは、3.0 や 3.00 は 3 に直せ、ということだろう。
(※ 今のところは x,y,r < 10 の場合だけらしいが、いずれテスト項目をもっと増やすらしい。)
例:
checkio("(2,2),(4,2),(2,4)") == "(x-4)^2+(y-4)^2=2.83^2" checkio("(3,7),(6,9),(9,7)") == "(x-6)^2+(y-5.75)^2=3.25^2"
ところで、問題文に出てくる Cartesianって何だろうって思って調べたら、デカルトのことらしい。
(Cartesian coordinate system でデカルト座標系)
デカルト座標系って何だっけと思って調べたら、単なる直交座標系だった。(よく見るX軸とY軸の座標)
どうやって解く?
いや、これPythonというより数学の問題やないか?
流れとしては、
- 文字列から3点の座標を得る。'(2,2),(6,2),(2,6)' → (x1,y1),(x2,y2),(x3,y3)
- 3点から円の中心と半径を求める。
- 方程式(文字列)を作成して返す。
という3ステップになるだろう。2は数学の問題だから、あとでググろう。自分で解く気なし(笑)
3はformatで数字を埋め込めばいいとして、1が一番面倒そうだな。
文字列から3点の座標を得る
普通に考えれば、カンマでsplitしてから'('と')'を除去して、って感じかな。
そういや、先日の問題の答えで eval() というのがあったな。ちょっとテスト。
>>> print(eval("(2,2),(6,2),(2,6)")) ((2, 2), (6, 2), (2, 6))
あれま。evalすげー。
(x1, y1), (x2, y2), (x3, y3) = eval(data)
じゃあこれで。Pythonすごいな。
3点から円の中心と半径を求める
http://okwave.jp/qa/q6389934.html
http://okwave.jp/qa/q7112206.html
http://wilhelmtell.blog46.fc2.com/blog-entry-550.html
http://nobutina.blog86.fc2.com/blog-entry-674.html
これらのリンクを参考にして、Pythonで書いてみた。
import math def get_circle_center_and_radius(x1, y1, x2, y2, x3, y3): """ 3点を通る円の中心と半径を取得 """ d = 2 * ((y1 - y3) * (x1 - x2) - (y1 - y2) * (x1 - x3)) x = ((y1 - y3) * (y1 ** 2 - y2 ** 2 + x1 ** 2 - x2 ** 2) - (y1 - y2) * (y1 ** 2 - y3 ** 2 + x1 ** 2 - x3 ** 2)) / d y = ((x1 - x3) * (x1 ** 2 - x2 ** 2 + y1 ** 2 - y2 ** 2) - (x1 - x2) * (x1 ** 2 - x3 ** 2 + y1 ** 2 - y3 ** 2)) / -d r = math.sqrt((x - x1) ** 2 + (y - y1) ** 2) return (x, y), r
方程式(文字列)を作成して返す
ここが意外と手間取った。まず、浮動小数点を小数点2桁に丸めるには、round()を使ったり、format()を使えばいい。
>>> str(round(3.1415, 2)) '3.14' >>> format(3.1415, '.2f') '3.14'
末尾の「0」と「.」を消す方法だが、小数点2桁なんだから、末尾に'.0'と'.00'があれば削除すればいいか。(←注:後で気づくが、ここが間違っていた。)
文字列の末尾が○○なら削除する、という関数を作っておく。
def remove_suffix(s, suffix): # 文字列の末尾がsuffixならば削除する return s[:-len(suffix)] if s.endswith(suffix) else s
これを strのメソッドとして登録して、s.remove_suffix("abc") とかできればいいのに。しかし、残念なことにPythonでは組み込み型は拡張できない。(C#なら拡張メソッドでstringを拡張できるのになー。)
さて、あとは方程式を作成する。
問題には "(x-a)^2+(y-b)^2=r^2" と書いてあるが、単純に
return "(x-{})^2+(y-{})^2={}^2".format(a, b, r)
というわけにはいかない。
aが-1のときは (x--1)^2 ではなく (x+1)^2 だし、aが0のときは (x-0)^2 ではなく x^2 となる。
def make_equation(x, y, r): """ 円の方程式を作成 """ def format_float(f): # 末尾の0と.を削除 result = str(round(f, 2)) result = remove_suffix(result, '.00') result = remove_suffix(result, '.0') return result def make_part(name, value): # '(x-5)'や'(y+2)'の部分を作成 num = format_float(abs(value)) sign = '-' if value > 0 else '+' return name if num == '0' else '({0}{1}{2})'.format(name, sign, num) return "{}^2+{}^2={}^2".format(make_part('x', x), make_part('y', y), format_float(r))
まとめ
全部まとめたったら、こうなった。
import math def get_circle_center_and_radius(x1, y1, x2, y2, x3, y3): d = 2 * ((y1 - y3) * (x1 - x2) - (y1 - y2) * (x1 - x3)) x = ((y1 - y3) * (y1 ** 2 - y2 ** 2 + x1 ** 2 - x2 ** 2) - (y1 - y2) * (y1 ** 2 - y3 ** 2 + x1 ** 2 - x3 ** 2)) / d y = ((x1 - x3) * (x1 ** 2 - x2 ** 2 + y1 ** 2 - y2 ** 2) - (x1 - x2) * (x1 ** 2 - x3 ** 2 + y1 ** 2 - y3 ** 2)) / -d r = math.sqrt((x - x1) ** 2 + (y - y1) ** 2) return (x, y), r def remove_suffix(s, suffix): # remove suffix if exists return s[:-len(suffix)] if s.endswith(suffix) else s def make_equation(x, y, r): def format_float(f): # remove extraneous zeros result = str(round(f, 2)) result = remove_suffix(result, '.00') result = remove_suffix(result, '.0') return result def make_part(name, value): # value=5 -> '(x-5)', value=-2 -> '(x+2)', value=0 -> 'x' num = format_float(abs(value)) sign = '-' if value > 0 else '+' return name if num == '0' else '({0}{1}{2})'.format(name, sign, num) return "{}^2+{}^2={}^2".format(make_part('x', x), make_part('y', y), format_float(r)) def checkio(data): (x1, y1), (x2, y2), (x3, y3) = eval(data) (x0, y0), r = get_circle_center_and_radius(x1, y1, x2, y2, x3, y3) return make_equation(x0, y0, r)
これでテストクリア&公開!
他の人の答え
文字列から3点の座標を得る
正規表現を使う人、evalを使う人、普通にsplit(',')する人、とまちまち。evalを使うのが一番簡単だろう。
3点から円の中心と半径を求める
複素数を使っている。私にはさっぱりわからない。
http://www.checkio.org/mission/three-points-circle/publications/veky/python-27/cplx-strings/
方程式(文字列)を作成して返す
やはり、数字の末尾の「0」と「.」をどう削除するかというところで、みんな工夫していた。どうも自分の答えに自信がなくなってきて、あれこれ試してみた。
>>> str(round(3.14, 2)) '3.14' >>> str(round(3.10, 2)) '3.1' >>> str(round(3.00, 2)) '3.0' >>> str(round(3, 2)) '3' >>> format(3.14, '.2f') '3.14' >>> format(3.10, '.2f') '3.10' >>> format(3.00, '.2f') '3.00' >>> format(3, '.2f') '3.00'
round(f,2)とformat(f,'.2f')って微妙に違うんだな。round(f,2)では末尾に'.00'がくることはないのか。
私のコードの
result = remove_suffix(result, '.00')
は必要なかったようだ。今回はround()を使っていたので良かったが、format()の場合なら '3.10'を'3.1'とする処理も必要になる。小数点2桁だから'.00'と'.0'を消せばいい、というわけではなかった。
他に気づいた点は、format()で+の符号を追加できるらしい。
>>> format(3.1415, '+.2f') '+3.14' >>> format(-3.1415, '+.2f') '-3.14'
また、.rstrip('0').rstrip('.') とすれば、末尾の「0」と「.」を消すことができる。これなら '3.00'でも'3.0'でも'3.10'でも対応できる。