2018年6月21日木曜日

複素数

Pythonは複素数を直接書くことができます。

Python 2.7.12 (default, Dec  4 2017, 14:50:18)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a=0+1j
>>> a
1j

実数部が0のとき省略できます。

>>> a=1j
>>> a
1j

虚数部が0のときこれも省略できます。

>>> b=1
>>> b
1

これはただの実数ですが複素数と組み合わせるとちゃんと複素数の計算をしてくれます。

>>> a-b
(-1+1j)

複素数は距離の計算をとても楽にしてくれます。

a,b 2点間の距離は差をとって絶対値を計算すればよい。

>>> d=abs(a-b)
>>> d
1.4142135623730951

これは√2です。
これをbに代入します。

>>> b=d
>>> b
1.4142135623730951
>>> d=abs(a-b)
>>> d
1.7320508075688774

これは√3です。
このように計算結果を次々に代入していけば全ての整数の平方根がもとめられます。
(複素数の計算の内部で sqrt を使っているのでは? というツッコミはあると思いますが無視してください)

Haskellではこのような記法はできません。
Data.Complex モジュールの

(:+)

というコンストラクターで複素数のインスタンスをつくります。
Haskellではコンストラクターは関数であり、(:+)は演算子として定義されているので次のようにします。

GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help
Prelude> import Data.Complex
Prelude Data.Complex> let a=0:+1
Prelude Data.Complex> a
0 :+ 1
Prelude Data.Complex> let b=1:+0
Prelude Data.Complex> b
1 :+ 0

ちょっと不格好ですが・・・

Haskell は、複素数の絶対値を複素数で返します。

Prelude Data.Complex> abs(a-b)
1.4142135623730951 :+ 0.0

これは abs が
abs :: Num a => a -> a
と定義されているからです。
かわりにmagnitudeをつかいます。

Prelude Data.Complex> :t magnitude
magnitude :: RealFloat a => Complex a -> a

Prelude Data.Complex> magnitude(a-b)
1.4142135623730951

ちなみに Data.Complex では abs を
abs z               =  magnitude z :+ 0
と再定義しているようです。(^_^;)

N−1 の平方根がわかれば N の平方根が計算できることから再帰的に定義できます。

--r.hs
import Data.Complex
dist a b = magnitude (a-b)
a = 0 :+ 1
r 0 = 0
r n = dist a b
    where b = r (n-1) :+ 0


Prelude> :l r.hs
[1 of 1] Compiling Main             ( r.hs, interpreted )
Ok, modules loaded: Main.
*Main> map r [0..9]
[0.0,1.0,1.4142135623730951,1.7320508075688774,2.0,2.23606797749979,2.4494897427831783,2.6457513110645907,2.8284271247461903,3.0000000000000004]

容易に想像できると思いますがNが大きくなると時間がかかるようになります。

*Main> :set +s
*Main> r 1000000
1000.0000000000299
(7.74 secs, 1,673,363,512 bytes)

このような再帰は fold で置き換えることができます。

*Main> let r' n = foldl (\x _ -> dist a (x :+ 0)) 0 [1..n]
*Main> r' 1000000
1000.0000000000299
(5.28 secs, 1,474,441,216 bytes)

さらに Data.List モジュールの foldl'  を使うと

*Main> import Data.List
*Main Data.List> let r'' n = foldl' (\x _ -> dist a (x :+ 0)) 0 [1..n]
*Main Data.List> r'' 1000000
1000.0000000000299
(2.52 secs, 1,293,766,728 bytes)

magnitude 自体が重い処理なのでそこそこかかります。

fold を使ったコードは最初に説明した手続き的処理をそのまま置き換えている点に注目です。

手続き的なscanlの説明
参照

余談ですが (:+) の型は 

Prelude Data.Complex> :t (:+)
(:+) :: a -> a -> Complex a

です。引数の型は何でもいいようです。ですので

Prelude Data.Complex> "hello" :+ "world"
"hello" :+ "world"
Prelude Data.Complex> :t it
it :: Complex [Char]

もちろん演算とかはできません。

2017年12月9日土曜日

gist.github の罠

上のコードをコピペして実行する。

$ runghc a.hs

a.hs:3:11: error: Variable not in scope: n

たぶんこのようなエラーになるはずです。
最初はなぜエラーになるのか理解できませんでした。


Vim上ではまったく見た目は同じです。

カーソルをのせてみると違いがわかります。






show してみると

$ runghc b.hs
"n"
"\65358"

65358は、16進の"ff4e"
つまりUnicodeの小文字 n です。
対して2行目の n はアスキー。

これはコードを日本語入力がONのまま修正したことでおこりました。
見た目は同じでも異なる文字なのでエラーとなりました。

2017年11月6日月曜日

ギヤ比

ソースコード
https://github.com/index333/gear

ギヤ比は一般には単に前後の比率で表します。
例えばフロント30T x リヤ20Tの場合
30➗20で1.50

欧米では伝統的にダルマ型自転車の前輪径に換算します。
30x20の場合
27x1.5=41インチギヤ
のように表現します。
欧米式の利点は小径車にも統一的に使えることです。
上の場合
20x1.5=30インチギヤとなります。
逆にロードレーサーの41インチギヤと同じ速度を小径車で得るには
30x15あるいは39x20という前後の組み合わせが必要となります。


ケーデンスを固定した時の時速で表したほうが直感的と思います。

まずタイヤ周長を選択します。

データはキャットアイのページからコピーしました。

$ cat tires
18-622 700x18C 2070 207
19-622 700x19C 2080 208
20-622 700x20C 2086 209
23-622 700x23C 2096 210
25-622 700x25C 2105 211
28-622 700x28C 2136 214
30-622 700x30C 2146 215
32-622 700x32C 2155 216
xx-xxx  Tubular 2130 213
35-622 700x35C 2168 217
38-622 700x38C 2180 218
40-622 700x40C 2200 220
42-622 700x42C 2224 222
44-622 700x44C 2235 224
45-622 700x45C 2242 224
47-622 700x47C 2268 227

$ cat tires | runghc selectItem.hs > tmp
$ cat tmp
23-622 700x23C 2096 210


Round モジュール、MySpinBox モジュールが必要です。
$ cat tmp | runghc gear.hs

2017年11月3日金曜日

pcd,歯底距離

チェーンリングのPCD(ピッチ・サークル・ダイアメーター)を計算します。
チェーンリングのPCDとはチェーン用ギヤにチェーンを巻きつけたときチェーンピンが形作る仮想円の直径のことです。
これは1辺の長さが12.7mmの正N角形に外接する円の直径の計算に還元できます。
(
クランクのPCDと区別するため、あちらはBCD(ボルト・サークル・ダイアメーター)と表現します
)
次に歯底距離を計算します。

歯底距離を測ればチェーンリングの歯数がわかります。

$ ghci
Prelude> :l Ch.hs
[1 of 1] Compiling Ch               ( Ch.hs, interpreted )
Ok, modules loaded: Ch.
*Ch> rd0 43
(43,166)
*Ch> rd0 44
(44,170)

歯底距離が166mmであれば歯数は43T
歯底距離が170mmであれば歯数は44T

チェーリングのPCDとリヤスプロケットのPCDがわかれば、シングル・バイクのチェーン長が計算できます。

芯間距離(チェーンステイ長)405mm、前スプロケット歯数48Tそして後スプロケット歯数16Tの場合

*Ch> chlen 48 16 405
1226.491724535387
*Ch> it / p
96.57415153821945

97リンク。(半コマ使用時)
98リンク。(半コマ不使用時)

チェーン長計算は下図を参考にしてください。


SpinBoxで入力を楽に

初期データは「tmp]に

$ cat tmp
[48.0,16.0,405.0]




スプロケを交換したときに
リヤアクスルが何ミリ移動するか計算します。

2017年11月1日水曜日

ランダム法

「Haskell98標準のrandomパッケージは、1.長くない周期、2.Haskell98由来の古いAPIの制約で内部状態を毎回コピーする、3.そのため遅い、4.CPU時間と現在時間で初期化(ポケモンかな???)など、過去に標準だったため多くの環境で使用出来ると考えられる、という点以外に余りお勧めできる所が有りません。」
(Haskellの乱数事情より)
https://qiita.com/philopon/items/8f647fc8dafe66b7381b

だそうですがここでは便利さをとり
 System.Random
モジュールを使います。

 System.Randomモジュールはcabalコマンドでパッケージを導入しなければなりません。
まず

sudo apt install cabal-install

でcabalをインストール。

$ cabal update -v
$ cabal install random -v

でImportできるようになります。
結構時間がかかるので -v オプションをつけて経過を見ながら。

なお表題の「ランダム法」はランダムを使っているという意味であり「モンテカルロ法」とは関係ありません。

基本のコードは



これで、未知数が一つ、解の範囲があらかじめ推測できるという条件の方程式を解くことができます。

上のコードに

root n x = evalStateT (try (pf n x)) (1,x) >>= return
pf n x g = g^n < x
main = root 2 2 >>= print

を付け加えて実行しますと

$ runghc -XFlexibleContexts  randomMethod.hs
(1.4142135623728276,1.4142135623731693)

3角形の3辺の長さがわかっていれば、その形と大きさが決まることから面積を計算できるはずです。
ヘロンの公式です。
ちなみにヘロンの公式はHaskellでは極めて簡潔にこう書けます。
heron a b c = sqrt (s*(s-a)*(s-b)*(s-c)) where s = (a+b+c) / 2

余談ですがどんな複雑な形の土地でも3角形に分割することで巻き尺1本で面積計算ができます。
(ただし平面地)
ヘロンの公式が思い出せなくてもランダム法で直感的に解くことができます。

三角形の3辺の長さが a,b,c で

bを複素平面上の実数軸においたとき、
三角形の頂点は極座標で
(a,θ)
で表せます。
このθをランダム法で探ればよいのです。




$ runghc -XFlexibleContexts  heron.hs
6.0

円に内接する正6角形と、同じ円に外接する正4角形を考えると、
円周率は3と4の間に存在するとわかります。
3と4の間の実数のどれかはπの近似値になるはずです。
では、それをランダム法で求められるでしょうか。
残念ながらできません。円周率を求める方程式が存在しないからです。

複素数を使えば間接的にπを求めることはできます。
複素数 c=-1+0i を極座標で表しますと (1、π)
位相を未知数θとして θを 3と 4 の間で、さまざまに変化させ c と近似する値をみつければよい。

でもこれはチートなのでは?

2017年10月31日火曜日

2017年10月30日月曜日

手続き的なscanlの説明


手続き的なscanlの説明 foldの動作をxへの代入、再代入と捉えると分かりやすくなります。 直接foldの動きを見るのではなく、 まずscanlの動作を見てみます。 scanlの型は Prelude> :t scanl scanl :: (a -> b -> a) -> a -> [b] -> [a] となっています。 scanlは、2引数の関数と初期値と何らかのリストを引数にとり、リストを返す関数です。 実際に動かしてみましょう。 Prelude> scanl(\x y -> x + y) 0 [1..3] [0,1,3,6] scanlが内部で何をやっているのか想像してみます。 まずscanlが呼び出されたとき、変数xに初期値、(この場合は「0」)を代入します。 次に変数yにリストの最初の要素(この場合は「1」)を代入します。 次にx+yを計算して結果の「1」をxに再代入します。 次にyにリストの次の要素「2」を代入し、x+yを計算します。この時xは1になっていますので, 結果は「3」です。これをxに再代入します。 次にyにリストの次の要素「3」を代入し、x+yを計算します。この時xは3になっていますので,結果は「6」です。これをxに再代入します。 リストの終わりに到達したので、リスト[0,1,3,6]を出力して終了します。 scanlはxに値が代入された時はすべてその値をリストに保存するようです。 こう見ますとfoldlはscanlと動作は同じで終了時にxの値のみを出力して終了する、と捉えることができます。 Prelude> :t foldl foldl :: (a -> b -> a) -> a -> [b] -> a foldlの型です。 同様にscanrの動作もみてみます。 scanrはxとyの役割が逆転しており、リストの作り方も異なっています。 Prelude> scanr (\x y -> x / y) 2 [3..5] [1.875,1.6,2.5,2.0] pythonで動きを追ってみると >>> x=5. >>> y=x/y >>> y 2.5 >>> x=4 >>> y=x/y >>> y 1.6 >>> x=3 >>> y=x/y >>> y 1.875 代入、再代入はyに対しておこなわれます。 yに代入があった時、リストの先頭に値を保存します。 Prelude> 1.875:1.6:2.5:2.0:[] [1.875,1.6,2.5,2.0] haskellはソースコード上での、変数への値の再代入を許していません。 そのため繰り返しのための構文が用意されていません。 それが可能な言語、例えばPythonでは次のようなコードが書けます。

x=0
for y in [1,2,3]: 
 x=x+y  print x
これは上記の説明のfoldと全く同じです。 つまりfoldは一般的な言語の繰り返し処理をHaskellで書くための一つの方法になっているわけです。

(PythonのFor文は他の凡百の言語のFor文とはまったく異なります。どちらかというとMapに近い。ただ書き換えのできるグローバル変数も使えるので非常に自由度が高い。)




fold を手続き的にとらえると考えやすくなる処理があります。

Data.List モジュールには insert という関数が用意されています。

Prelude> import Data.List
Prelude Data.List> :t insert

insert :: Ord a => a -> [a] -> [a]

ソート済みのリストに一つ要素を加えてソート順を崩さずに新たなリストを返す関数です。
他の関数が一般的なリスト処理の関数なのに比べてやや特殊な感じがあります。
たぶん「挿入ソートを実装してください」というメッセージだと思います。
挿入ソートは再起でも書けるとは思うのですが、いきなり fold を使ったほうがわかりやすい。

Prelude Data.List> foldl (\x y -> insert y x) [] [2,7,4,0]
[0,2,4,7]

まず初期値は空リストです。
x に 代入されます。
y には リストの第一要素が代入されます。
x y をひっくり返して insert 関数を適用します。
結果 x に [2] が y に 7 が代入されます。
7 を [2]  に insert し [2,7]  ・・・・(略)

この操作をリストの終わりまで繰り返します。 これでソート完了。

flip を使って書き直すと

Prelude Data.List> foldl (flip insert) [] [2,7,4,0]
[0,2,4,7]

さらに

Prelude Data.List> foldr insert [] [2,7,4,0]
[0,2,4,7]

foldl と foldr では動作が異なりますが、この場合は結果は同じです。



も参照ください。

myPlayer

-- pipe.hs import System.Process import System.Environment main :: IO () a:_ IO [FilePath] randomize lst = do let c = length lst ...