Problem 165
コンテンツ
http://projecteuler.net/index.php?section=problems&id=165
この問題は結構苦労した。
線分交差列挙問題
といったら平面走査法とおもい、実装しようと試みた(前から実装してみたいとは思っていた)が…
無理
例外処理が予想以上に大変だ。
処理しなくてはいけないと思われる例外
- 走査線と平行な線分
- 端点での交差
- 3線分以上の交差
線分を蓄えるデータ構造(平衡木など)が必要になるが、
この要素(線分)の順序関係が動的に変化していくことと例外の存在が非常に厄介。
どこからか「そんな時は記号摂動だよ」という声が聞こえてくるがそんな気力はない。
というかそもそも平面走査法の計算量は交差数をkとすると O ((n+k)log n) (実は O(nlog n + k)があるらしいが複雑らしい)。
つまり、線分が密なとき(k が n^2 程度 )は大して効率よくないよ。
この問題は [0,499]x[0,499]のなかに5000本の線分があるんだから密に決まってるよ。
というわけで O (n^2) のナイーブな解法を採用。
- 計算終わらない -> 有理数方から実数型
- 答えあわない -> 共有点の個数ではなく 異なる 共有点の個数だった。
- なんか遅い -> 重複要素を取り除くのに quick sort みたいな nub を使った (できたリストの順序は非保存、quick sort は もらってきた)
import Data.Maybe (mapMaybe) import Data.Function (on) import Control.Arrow ((***)) type Point = (Integer,Integer) type Segment = (Point,Point,Integer,Integer,Integer) type Intersection = (Double,Double) blumBlumShub :: [Integer] blumBlumShub = map (flip mod 500).tail.iterate sqMod $ 290797 where sqMod s = mod (s*s) 50515093 takeSegs :: Int -> [Integer] -> [Segment] takeSegs _ = [] takeSegs (n+1) (a:b:c:d:es) = segment (a,b) (c,d) : takeSegs n es segment :: Point -> Point -> Segment segment p@(x1,y1) q@(x2,y2) = (p,q,a,b,a*x1+b*y1) where a = y1-y2 b = x2-x1 innerSeg (p1,q1,_,_,_) (x,y) = closure (fst p1,fst q1) x && closure (snd p1,snd q1) y && not endpoint where closure (a,b) c = fromIntegral (min a b) <= c && c <= fromIntegral (max a b) endpoint = same p1 (x,y) || same q1 (x,y) same (a,b) (c,d) = fromIntegral a == c && fromIntegral b == d overlapp (a,b) (c,d) = min a b < max c d || max a b > min b c intersection :: Segment -> Segment -> Maybe Intersection intersection l1@(p1,q1,a1,b1,d1) l2@(p2,q2,a2,b2,d2) | overlappX && overlappY && det /= && innerSeg l1 (x,y) && innerSeg l2 (x,y) = Just (x,y) | otherwise = Nothing where overlappX = on overlapp (fst***fst) (p1,q1) (p2,q2) overlappY = on overlapp (snd***snd) (p1,q1) (p2,q2) det = fromIntegral $ a1*b2-a2*b1 x = fromIntegral (b2*d1-b1*d2) / det y = fromIntegral (a1*d2-a2*d1) / det choose2 :: [a] -> [(a, a)] choose2 [] = [] choose2 (x:xs) = map ((,) x) xs ++ choose2 xs main = print.length.quicknub.intersections $ 5000 intersections n = mapMaybe (uncurry intersection).choose2 $ takeSegs n blumBlumShub -- quick sort like nub quicknub :: Ord a => [a] -> [a] quicknub xs = qnub compare xs [] qnub :: (a -> a -> Ordering) -> [a] -> [a] -> [a] qnub _ [] r = r qnub _ [x] r = x:r qnub cmp (x:xs) r = qpart cmp x xs [] [] r qpart :: (a -> a -> Ordering) -> a -> [a] -> [a] -> [a] -> [a] -> [a] qpart cmp x [] rlt rge r = qnub cmp rlt (x:qnub cmp rge r) qpart cmp x (y:ys) rlt rge r = case cmp x y of LT -> qpart cmp x ys rlt (y:rge) r EQ -> qpart cmp x ys rlt rge r GT -> qpart cmp x ys (y:rlt) rge r
メモリ足りなくなったり、苦労したんだけど、遅いし、きれいなコードじゃない。
作成者 Toru Mano
最終更新時刻 2023-01-01 (c70d5a1)