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

Data.Listの関数まとめ

いやと言う程、関数が紹介されてるのに、いわゆる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と言ってるだけ。

少し骨やすめ

ChatGPTのコア技術「GPT」をざっくり理解する

ChatGPT API を基礎から理解する

ChatGPT の仕組みを理解する(前編)

ChatGPT使い方総まとめ

プロンプトの賢い指示方法が説明されてたので、オイラーも実践。世の中、平 均ばかりが、まかり通っているけど、標準偏差とセットにして欲しいなあ。と 言う事で、オプションです。

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

etc