old haskell code (2)
Table of Contents
Z世代のアメリカ
なんて本を読んだ。 Z世代の次はAA世代か、なんて思うぞ。だって、EXCELの列でZの次はAAでしょ。
一体Z世代って何や? マーケティング用語か? オイラーも引っかかった口。 それを知らないと、あんた、遅れてると馬鹿にされるからね。
色々な解説が有るけど、こんなのどうよ。 Z世代とは?何歳からを指すのか、なぜZなのかを簡単に解説
Zの次はαで、その次はβって具合にするつもりなのか。逆に、Y世代だとかX 世代も有るんだな。誰か、変換式を教えて。オイラー的には、令和世代、平成 世代、昭和世代って具合に、唯一和暦を許容してあげたい。
こういうの、言葉の世界では、記号接地と言うらしい。
「記号接地」という観点から看過できません。人間の学習においては経験とひも付けながら、ことば(記号)を身体感覚に「接地」していくことが非常に重要です。
例えば、100分の99に近い整数は何でしょうか。もちろん「1」なのですが、「99」とか「100」と答える中学生がかなりいる。小学生で分数というものを「記号接地」できないまま、学年が進んでしまったことが原因です。
と、高名な言語学者様は、注意を促しておられます。
update old code
前回の恥かしいコードをアップデートした。元はと言えば、前々回に、 ChatGPTに統計値の中心近くのやつを計算させてって問題に端を発している。 提案内容が怪しげだったので、自前で実装を始めたのさ。
-- new bld stats data Rec = Rec { ymd :: String , hi :: Int , lo :: Int } deriving (Show, Eq, Read, Ord) isCmm :: Char -> Bool isCmm c = c == ',' sepCm :: String -> [String] sepCm s = case dropWhile isCmm s of "" -> [] s' -> w : sepCm s'' where (w,s'') = break isCmm s' csvRead :: IO ([Rec], [Rec]) csvRead = do al <- readFile "test.csv" return $ foldr ampm ([], []) (lines al) where ampm ln ~(am,pm) = case sepCm ln of [ymdh, hi, lo, pl] -> if h <= "12" then (Rec ymd (read hi) (read lo):am, pm) else (am, Rec ymd (read hi) (read lo):pm) where (ymd, h) = splitAt 6 ymdh _ -> error "Invalid CSV line format" main :: IO () main = do (am, pm) <- csvRead print am print pm
ちょっとデータ型から脈拍を削除して、簡略化。
下記の検算データを喰わせてみる。
[sakae@fb /tmp/hs-oldstats]$ cat test.csv 12010121,109,70,71 12010221,105,62,67 12010304,122,81,59 12010405,120,80,60 12010421,132,82,61
実に説明的なデータが表示された。ちゃんと、午前、午後に分離され、昇順と 言うか、データの出現順になってるな。こういうデータが見られるのは、コー ドの建設中の今だけです。
ghci> :main [Rec {ymd = "120103", hi = 122, lo = 81},Rec {ymd = "120104", hi = 120, lo = 80}] [Rec {ymd = "120101", hi = 109, lo = 70},Rec {ymd = "120102", hi = 105, lo = 62},Rec {ymd = "120104", hi = 132, lo = 82}]
list op
いやと言う程、関数が紹介されてるのに、いわゆるforってかforeachが無いぞ。 探したら、こんなのが出てきた。
Haskell > 繰り返し処理(foreach相当)を行う
正統派には、何故無い? それは、命令型言語向けな奴だから。使う場面が限 定的なんよ。forM とかは、ちゃんと有る。副作用を許す場面ね。
上のコードを考えている時、foreachが欲しくなったんだ。だってschemeなん かだと普通に提供してるからね。リストのエレメントのそれぞれに対して何か やる。なんでもλ
これが有れば、事前に定義しておいた([],[])に対して、更新していける、、、 はずなんだけどな。それは、タプルに対しての、副作用そのもの。そんな事は、 (多分)特殊な事をしない限り許される事では、ないだろう。 そういう観点でschemeを眺めると、日和った言語である。(別に、悪口を言っ ている訳ではないので、あしからず)
setup using let
次は、いよいよ統計なんだけど、その前に、面倒臭い準備が必要だ。多数の変 数を用意して、下準備する。こういう場合は、whereを使うより、letを登用し て、schemeの乗りで記述するのが良いだろう。
main :: IO () main = do stats "230501" divideData :: [Rec] -> [Rec] -> ([Int], [Int], [Int], [Int]) divideData am pm = (hiAM, loAM, hiPM, loPM) where hiAM = [hi | (Rec _ hi _) <- am] loAM = [lo | (Rec _ _ lo) <- am] hiPM = [hi | (Rec _ hi _) <- pm] loPM = [lo | (Rec _ _ lo) <- pm] getFstEnd :: [Rec] -> [Rec] -> (String, String, String, String) getFstEnd am pm = (fstAM, endAM, fstPM, endPM) where fstAM = ymd (head am) endAM = ymd (last am) fstPM = ymd (head pm) endPM = ymd (last pm) stats :: String -> IO () stats yymmdd = do (amALL, pmALL) <- csvRead let selAM = take 10 $ dropWhile (\(Rec ymd _ _) -> ymd < yymmdd) amALL selPM = take 10 $ dropWhile (\(Rec ymd _ _) -> ymd < yymmdd) pmALL (hiAM, loAM, hiPM, loPM) = divideData selAM selPM (fstAM, endAM, fstPM, endPM) = getFstEnd selAM selPM putStrLn $ m3 "hi@am" hiAM fstAM endAM putStrLn $ m3 "hi@pm" hiPM fstPM endPM putStrLn $ m3 "lo@am" loAM "" "" putStrLn $ m3 "lo@pm" loPM "" "" m3 :: String -> [Int] -> String -> String -> String m3 ttl dat st sp = ttl ++ " " ++ show (length dat) ++ " " ++ st ++ " " ++ sp
全体の形を確認する。肝心の統計部分は未完。
ghci> :main hi@am 10 230501 230510 hi@pm 10 230501 230510 lo@am 10 lo@pm 10
注意すべきは、headの演算。内容が無いリストに適用すると、ドッカンと爆発 するぞ。個人使用なので、注意して使うって事で。。
それより、当初の目的、開始年月日を与えなかった場合、最新のデータを参照 するってのが、実現出来ていない。改造は簡単で、不要なデータはdropする事 で、必要なデータが得られる。selAM/PMの変更が必要になってくるんだけど、 今回は手を出さないでおこう。
それより、本チャンの統計だな。これは、別ファイルに独立させてモジュールっ て扱かいにしておいた方が良いだろう。
Library and Executable
今迄はずっと app/Main.hsを使用してた。今回はLibも使うんで、それ用に構 成して、実行ルーチンを確認しとく。
[sakae@arch tmp]$ mkdir newstats [sakae@arch tmp]$ cd newstats/ [sakae@arch newstats]$ cabal init -i Should I generate a simple project with sensible defaults? [default: y] n What does the package build: 1) Executable 2) Library 3) Library and Executable Your choice? 3 : [sakae@arch newstats]$ cat app/Main.hs module Main where import qualified MyLib (someFunc) main :: IO () main = do putStrLn "Hello, Haskell!" MyLib.someFunc [sakae@arch newstats]$ cat src/MyLib.hs module MyLib (someFunc) where someFunc :: IO () someFunc = putStrLn "someFunc"
自動作成されたソースも確認。
[sakae@arch newstats]$ cabal repl : [1 of 1] Compiling MyLib ( src/MyLib.hs, interpreted ) Ok, one module loaded. ghci> :main <interactive>:1:53: error: • Variable not in scope: main :: IO a0 • Perhaps you meant ‘min’ (imported from Prelude) ghci> :l app/Main.hs <no location info>: warning: [-Wmissing-home-modules] These modules are needed for compilation but not listed in your .cabal file's other-modules: MyLib [1 of 2] Compiling MyLib ( src/MyLib.hs, interpreted ) [2 of 2] Compiling Main ( app/Main.hs, interpreted ) Ok, two modules loaded. ghci> :main Hello, Haskell! someFunc
Libしかロードされないので、自分でロード。
後は、頑張って統計関数を定義してくだけだな。
add option
で、とりあえず当初の目的の3Mは、完成した。ああ、3Mってのは、統計の基本 ね。データを畳み込んだ時の、中央付近の値を表わしている。名前が皆Mで始 まるんで、勝手に、3Mと言ってるだけ。
少し骨やすめ
プロンプトの賢い指示方法が説明されてたので、オイラーも実践。世の中、平 均ばかりが、まかり通っているけど、標準偏差とセットにして欲しいなあ。と 言う事で、オプションです。
haskellで標準偏差を出力する関数を、作成して。 関数の型は、次の通り std :: [Int] -> Double
run stats
10年前の結果と比べてみる。
λ> :main 130601 mode(#) median mean std N from to hi@am 117(10) 120.0 119.63 6.81 100 130601 130908 hi@pm 103(9) 103.5 105.03 7.48 100 130601 130908 lo@am 71(13) 71.0 70.5 3.77 100 lo@pm 62(14) 60.0 60.09 4.50 100 λ> :main 230601 mode(#) median mean std N from to hi@am 135(8) 138.0 139.08 7.46 81 230601 230821 hi@pm 125(6) 125.0 124.85 9.25 83 230601 230821 lo@am 69(12) 72.0 71.55 4.47 81 lo@pm 68(12) 65.0 64.34 4.14 83
code
app/Main.hs
-- new bld stats module Main(main) where import qualified MyLib (m3) import System.Environment (getArgs) data Rec = Rec { ymd :: String , hi :: Int , lo :: Int } deriving (Show, Eq, Read, Ord) isCmm :: Char -> Bool isCmm c = c == ',' sepCm :: String -> [String] sepCm s = case dropWhile isCmm s of "" -> [] s' -> w : sepCm s'' where (w,s'') = break isCmm s' csvRead :: IO ([Rec], [Rec]) csvRead = do al <- readFile "current.csv" return $ foldr ampm ([], []) (lines al) where ampm ln ~(am,pm) = case sepCm ln of [ymdh, hi, lo, pl] -> if h <= "12" then (Rec ymd (read hi) (read lo):am, pm) else (am, Rec ymd (read hi) (read lo):pm) where (ymd, h) = splitAt 6 ymdh _ -> error "Invalid CSV line format" divideData :: [Rec] -> [Rec] -> ([Int], [Int], [Int], [Int]) divideData am pm = (hiAM, loAM, hiPM, loPM) where hiAM = [hi | (Rec _ hi _) <- am] loAM = [lo | (Rec _ _ lo) <- am] hiPM = [hi | (Rec _ hi _) <- pm] loPM = [lo | (Rec _ _ lo) <- pm] getFstEnd :: [Rec] -> [Rec] -> (String, String, String, String) getFstEnd am pm = (fstAM, endAM, fstPM, endPM) where fstAM = ymd (head am) endAM = ymd (last am) fstPM = ymd (head pm) endPM = ymd (last pm) stats :: String -> IO () stats yymmdd = do (amALL, pmALL) <- csvRead let selAM = take 100 $ dropWhile (\(Rec ymd _ _) -> ymd < yymmdd) amALL selPM = take 100 $ dropWhile (\(Rec ymd _ _) -> ymd < yymmdd) pmALL (hiAM, loAM, hiPM, loPM) = divideData selAM selPM (fstAM, endAM, fstPM, endPM) = getFstEnd selAM selPM putStrLn " mode(#) median mean std N from to" putStrLn $ MyLib.m3 "hi@am" hiAM fstAM endAM putStrLn $ MyLib.m3 "hi@pm" hiPM fstPM endPM putStrLn $ MyLib.m3 "lo@am" loAM "" "" putStrLn $ MyLib.m3 "lo@pm" loPM "" "" main :: IO () main = do args <- getArgs case args of [yymmdd] -> stats yymmdd _ -> putStrLn "Usage: ./programName yymmdd"
src/MyLib.hs
module MyLib (m3) where import Data.List(group,sort,sortOn) fi :: Int -> String fi v = replicate (8 - x) ' ' ++ show v where x = length $ show v ff :: Double -> String ff v = replicate (8 - x) ' ' ++ s where t = span (/= '.') (show v) s = fst t ++ take 3 (snd t) x = length s mean :: [Int] -> Double mean cs = fromIntegral (sum cs) / fromIntegral (length cs) median :: [Int] -> Double median ns = let l = length ns n = l `div` 2 ms = sort ns in if even l then fromIntegral ((ms !! (n - 1)) + (ms !! n)) / 2 else fromIntegral (ms !! n) countOccurrences :: [Int] -> [(Int, Int)] countOccurrences xs = map (\x -> (head x, length x)) (group $ sort xs) mode :: [Int] -> (Int, Int) mode cs = last $ sortOn snd $ countOccurrences cs showmode :: [Int] -> String showmode cs = fi v ++ "(" ++ show cnt ++ ")" ++ pad where (v,cnt) = mode cs pad = if length (show cnt) == 1 then " " else "" std :: [Int] -> Double std xs = sqrt (sum squaredDiffs / fromIntegral n) where n = length xs mean = fromIntegral (sum xs) / fromIntegral n squaredDiffs = [(fromIntegral x - mean) ** 2 | x <- xs] m3 :: String -> [Int] -> String -> String -> String m3 ttl dat st sp = ttl ++ " " ++ showmode dat ++ " " ++ ff (median dat) ++ " " ++ ff (mean dat) ++ ff (std dat) ++ " " ++ show (length dat) ++ " " ++ st ++ " " ++ sp