目次

スポーク管理システム



サイクルショップで厄介なのがスポークの在庫管理です。
自転車のスポークはリム、ハブ、組み方により長さがことなり、非常に多くのサイズを用意しておかな ければなりません。32本あるいは36本を1セットとしていて、1本でも足りなければホイールを組むことができません。

そこでHaskellで在庫の管理をすることを考えます。
まずスポークの属性をどのように表現するかですが、当然、Haskellのデータ型を使うこ とに なります。このときスポークというものを電算上で過不足なくデータ化できるかで、あとのプログラムが大きく異なりますので、ここは慎重に設計する必要があ ります。この設計に成功すれば、プログラムは半分できたようなものです。

スポークの属性として大事なのはまずなんといっても長さです。
スポークの長さはミリ単位です。
つぎに太さ。これは直径をミリであらわす方法と、番手と呼ば れる鋼線などの太さの単位を使う場合とがあります。14番が2.0mm、15番が1.8mmとなっています。一般には番手がよく使われるようです。その他 にバテッドスポークといって中央部を細くしたスポークがあります。
あと材質やメーカーなどの若干の補足情報が必要です。

長さはIntで決まりですが、太さはどう表現するのが一番いいでしょうか。バテッドスポークを考慮すると単純なIntやFloatではうまくいきそうにあ りません。結局、Stringにしました。バテッドスポークは、たとえば”14-15”とします。補足は当然Stringです。
最後にIntで在庫量を表します。
data Spoke = Sp{leng::Int,size::Int,typ::String,stock::Int} deriving Show
さてこれをどうファイル化するかですが、UNIX流にテキストファイルで保存することにします。




293 15 tb 24
294 15 d 35
294 15 s 1
295 15 d 17




長さ、太さ、メーカー名、在庫量の順です。 このファイル、"stock"を読み込んでデータ型に変換する関数などをまとめて、モジュールにしておきます。

module Spoke where

data Spoke =
Sp{leng::Int,size::Int,typ::String,stock::Int}
deriving Show
instance Eq Spoke where
(Sp l _ _ _) == (Sp l1 _ _ _) = l == l1

toS :: Spoke -> String
toS (Sp l s t st) = show l ++ " #" ++ show s
++ " " ++ ty t ++ " " ++ show st
where
ty "s" = "stainless"
ty "d" = "DT"
ty "b" = "butted"
ty "db" = "DT butted"
ty "tb" = "DT triple butted"
ty "f" = "ferrum"
ty _ = "unknown"

toSpoke :: String -> Spoke
toSpoke s = Sp (read l) (read sz) t (read st)
where [l,sz,t,st] = words s

disp :: [Spoke] -> IO ()
disp spokes = do
mapM_ (\x -> print (toS x)) spokes

find :: Int -> [Spoke] -> [Spoke]
find len spokes =
[x | x <- spokes, x == toSpoke ((show len) ++
" a b c")]

getStock :: IO [Spoke]
getStock = do
t <- readFile "stock"
return $ [toSpoke x | x <- lines t]


Eqを長さだけで比較したのは、とりあえずおなじ長さのスポークを検出し、あとはユーザーが判断したいからです。
words関数はスペースで区切られた文字列をリストにして返す関数で「stock」のようなファイルを読み込むのに最適です。

このモジュールを利用して実際に検索を行なうMainプログラムを書いてみましょう。
-- t.hs
import Spoke

main = do
st <- getStock
print $ find 294
-- end of t.hs
%  runhugs t.hs
294 #15 DT 35
294 #15 stainless 1

さらにリムとハブのデータもファイルから読み込み、選択したリム、ハブに適合したスポークを在庫中に検索し、表示するようにします。

Spokeモジュールにスポークの適合長を計算する関数も追加しておきます。

calLen :: Float -> Float -> Float -> Float -> Float -> Float
calLen a b d h k = sqrt ((a^2+b^2+d^2)
            -
            (2*a*b*(cos $ alfa h k)))
            - 1.2
    where
    alfa h k = rad (360 / h * k)
    rad d = ((2*pi)/360)*d

a はリムの半径、b はハブの PCD の半径、d はフランジとフレームセンターとの距離、h はリムの穴数、k は組み数です。

全体としてつぎのようになりました。


module Main where

import MyUtil
import Hub
import Rim
import Spoke
import Monad

showResult :: [[Float]] -> [Int] -> [Spoke] -> IO ()
showResult fll@(fl:_) kl st = do
let fl' = foldl (\x y -> x ++ [fl !! y])
[(fl !! head kl)] (tail kl)
case fll of
[f] -> show' fl' kl
[f,f1] -> do
putStrLn "Right"
showResult [f] [3,4] st
putStrLn "Left"
showResult [f1] [0,2,3,4] st

[f,f1,f2] -> do
putStrLn "Front"
showResult [f] [0,2,3,4] st
putStrLn "Rear Right"
showResult [f1] [3,4] st
putStrLn "Rear Left"
showResult [f2] [0,2,3,4] st
[f,f1,f2,f3] -> do
putStrLn "Front disk side"
showResult [f] [3,4] st
putStrLn "Front no disk side"
showResult [f1] [0,2,3,4] st
putStrLn "Rear Right"
showResult [f2] [3,4] st
putStrLn "Rear Left"
showResult [f3] [0,2,3,4] st
where
show' fl kl =
zipWithM_ (\x y -> do
putStr (show y)
putStr "X: "
putStr (round10 x)
putStr "mm\n"
showMatched (round x - 1) st
showMatched (round x) st)
fl kl

showMatched :: Int -> [Spoke] -> IO ()
showMatched len stock = disp $ find len stock

main = do
print $ calLen 303 22 35 32 3
rim <- selectRim
print "************"
hub <- selectHub
print "************"
print rim
putStrLn $ hubs2S hub
let ll = [len rim h | h <- hub]
print "************"
st <- getStock
showResult ll [0,2,3,4] st

mainのあるファイルはHaskellによってMainモジュールと認識されます。今回はそれを明示しました。

MonadモジュールのzipWithM_関数はzipWith内でprintなどのアクションを実行するときの関数です。mapとmapM_の関係とお なじです。

さて在庫管理という以上、データの更新作業も行なわないといけません。以前Javaでそのようなプログラムも書いたことがありますが、実際にはテキスト ファイルをエディターで書き換えることで事足りるのです。なんでもプログラムで行なうというのは賢いやりかたではありません。ときには手抜きも必要です。 というわけでこのへんで打ちどめとしておきます。

 MyUtil.hs  Rim.hs    Hub.hs   Spoke.hs   Main.hs     hubs  rims  stock