読者です 読者をやめる 読者になる 読者になる

studies

いろいろ勉強用備忘録的な感じ

pythonとhaskell, perl6で加重平均

python
最後はnumpyの内積(ドット積)を使っている(ずるい?)

>>> weight = [ 0.22, 0.14, 0.04, 0.12, 0.22, 0.26 ]
>>> record = [ 64, 51, 132, 24, 19, 11 ]
>>> sum( a * x for (a, x) in zip(weight, record) )
36.42
>>> sum(map(lambda a, x: a * x, weight, record))
36.42
>>> import operator as op
>>> sum(map(op.mul, weight, record))
36.42
>>> import numpy as np
>>> np.dot(weight, record)
36.420000000000002

haskell

Prelude> let weight = [ 0.22, 0.14, 0.04, 0.12, 0.22, 0.26 ]
Prelude> let record = [ 64, 51, 132, 24, 19, 11 ]
Prelude> sum $ zipWith (*) weight record
36.42


perl6も面白い。

> my @weight = 0.22, 0.14, 0.04, 0.12, 0.22, 0.26
[0.22 0.14 0.04 0.12 0.22 0.26]
> my @record = 64, 51, 132, 24, 19, 11
[64 51 132 24 19 11]
> [+] @weight >>*<< @record
36.42

void *p = &p : 自分自身を参照する初期化の問題

久しぶりの投稿はちょっと変な話。

次のように、自分自身を参照するポインタを作ってみる。

#include <stdio.h>

int main(void)
{
    void *p = &p;
    printf("%p\n", p);
    return 0;
}

自分の環境では、普通にコンパイルが通る。
そして、実行するたびに結果が変わる(実行のたびに別のアドレスにpが配置される)。

こんなプログラムは何の意味もないと思うかもしれないが、次のような場合はどうだろうか?

typedef struct List {
    char value;
    struct List *next;
} List;

static List terminator = {0, &terminator};
List * const tail = &terminator;

terminatorは自分自身を参照するセルである。
つまり、tailは要素数1の環状リストである。

ふつう、線形リストの最後はNULLを指すことで終端を示すが、このような要素数1の環状リストで最後を「結んで」やると、簡潔にコードをかける場合がある(と思う)。
というのは、こうすることで、NULLのnextを参照してしまう恐れがなくなるからだ。

しかし、この書き方には疑問が残る。
つまり、宣言と同時に初期化する場合、実行順序として、「左辺のメモリの確保」→「右辺の評価」となっていることは保証されているのか、という問題である。

    void *p = &p;

が許されるということは、私のコンパイル環境ではその実行順序で正しいようだが、これには一般性があるだろうか?
つまり、規格的にはどうだろうか?
少しネットを探してみたが、それらしい記事を見つけることはできなかった。


もし、この実行順序が保証されないとすると、少し厄介なことも起こる。

static List terminator;
List * const tail = &terminator;

void initialize_tail(void)
{
    tail->value = 0;
    tail->next = tail;
}

terminatorとtailを宣言した後で、ユーザにtailの初期化関数initialize_tail()を呼び出してもらわなければならなくなる。
(関数の「外」に代入文はかけないため)。
C#とかなら、terminatorやtailをListクラスの静的メンバとして、静的コンストラクタを用いて初期化すればよさそうだが、Cではそうもいかない。
これはけっこう厄介だと思う。

無名共用体を見かけた

無名共用体(anonymous union)を久しぶりに見た。

無名共用体というのは、こんなやつ。

#include <iostream>
using namespace std;

static struct {
    char dummy;
    union {
    // これが無名共用体よ
        long data_double_word;
        struct {
        // これは無名構造体だ
        // リトルエンディアンを想定
            short data_word_low;
            short data_word_high;
        };
    };
} foo;

int main() {
    foo.data_word_low = 8;
    foo.data_word_high = 1;
    cout << foo.data_double_word << endl; // 65544 = 1 << 16 + 8
}

しかし、気になったことには、今回見かけたソースは上のようなC++のソースではなく、Cで書かれていた。
Cでanonymous unionとか、anonymous structなんて使えるのか??

Cで書くならこうだろうと思っていた(というか、こういうコードしか見たことがなかった)ので、少し意外な感じ。

#include <stdio.h>

static struct {
    char dummy;
    union {
    /* dataという名前のタグのない共用体 */
        long double_word;
        struct {
        /* wordという名前のタグのない構造体 */
        /* リトルエンディアンを想定 */
            short low;
            short high;
        } word;
    } data;
} foo;

int main(void)
{
    foo.data.word.low = 8;
    foo.data.word.high = 1;

    /* 65544 = 1 << 16 + 8 */
    printf("%ld\n", foo.data.double_word);
}

調べてみると、どうやら、無名共用体も、無名構造体も、C11から使えるようになったらしい。
しかし、C99標準では使えないらしく、可搬性を意識しているソースでは導入が進んでいないようだ。
(もちろん、C11リリース以前に書かれたソースでは、皆無。もしかしたらコンパイラの独自拡張機能としてあったかもしれないけど)。
やはり、今回見かけたCの無名共用体は今のところはまだレアキャラのようです。

www.buildinsider.net
www.kijineko.co.jp

pythonで順序を保つグルーピング

恒例の?問題をpythonでやってみる。
使うアルゴリズムは、perlで最後にやったのと同じ(いちおうバケットソートといえるだろうか?)
studies.hatenablog.com

pythonのcollectionモジュールにあるOrderedDict(順序付き辞書)を使うと楽に書ける。
OrderedDictについての説明は以下にある。
8.3. collections — コンテナデータ型 — Python 3.6.1 ドキュメント

通常の dict メソッドをサポートする、辞書のサブクラスのインスタンスを返します。 OrderedDict は、キーが最初に追加された順序を記憶します。新しい項目が既存の項目を上書きしても、元の挿入位置は変わらないままです。項目を削除して再挿入するとそれが最後に移動します。

さて、書いたコードは以下の通り。

#! /usr/bin/python3
import fileinput
from collections import OrderedDict

pref_to_cities = OrderedDict()

for city in fileinput.input():
    pref = city.split()[1]
    if pref not in pref_to_cities.keys():
        pref_to_cities[pref] = list()
    pref_to_cities[pref].append(city)

for cities in pref_to_cities.values():
    print(*cities, sep="", end="")


以前perlで書いたコードと比べると、ダイヤモンド演算子や組み込みの変数が少ないのと、インデント強制のため、少し長いコードになっている。
しかし、以前は順序を覚えておくためだけに無駄な配列を作っていたのに比べれば、問題に最適なデータ構造を選択できていて、よい。

pythonとhaskellで素数のリストを作ってみる

今回はメモ程度。
リスト内包表記と、filter, mapを使った書き方。

python

>>> [n for n in range(2,100) if 0 not in [n%d for d in range(2,n)]]
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
>>> list(filter(lambda n: 0 not in map(lambda d: n%d, range(2,n)), range(2,100)))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

haskell

Prelude> [n | n <- [2..100], not $ 0 `elem` [n `mod` d | d <- [2..n-1]]]
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]
Prelude> filter (\n -> not $ 0 `elem` map (n `mod`) [2..n-1]) [2..100]
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]

pythonはリスト内包表記のほうがすっきり。
というか、lambdaキーワードが長すぎて…。
haskellはどっちも似たようなものかな~。

pythonにはこんなことできないかな?

Prelude> take 30 $ filter (\n -> not $ 0 `elem` map (n `mod`) [2..n-1]) [2..]
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113]

フィボナッチ数をたくさん出力すると放物線が現れる件

前回、pythonを使ってフィボナッチ数の第n項を返す関数をいくつかのバリエーションで実装した。
ところで、pythonではメモリが許す限りいくらでも大きな整数を扱うことができる。
調子に乗って、前回作った関数でフィボナッチ数を(各項の間にスペースを一個入れながら)、大量に出力してみていると、面白いことに気づいた。
放物線が現れるのである。

f:id:developingskills1110:20170126194748p:plain

おお。
なんだこれは。

若干の感動を覚えつつ、少し考えてみると理由が分かった。

おおざっぱに説明してみよう。

フィボナッチ数列 \{F_n\} とかく。
フィボナッチ数列の漸化式を解くと、一般項は、

    F_n = \frac{\phi^{n} - (1 - \phi)^{n}}{\sqrt{5}}

である。ここで \phi黄金比である。
 1 < \phi < 2であり、したがって -1 < 1 - \phi < 0であるから、

    \begin{eqnarray}\lim_{n\to\infty}(1-\phi)^{n} = 0\end{eqnarray}

ゆえに、nが十分大きいとき、

    F_n \sim O\left(\frac{\phi^{n}}{\sqrt{5}}\right) \sim O(\exp(n))

と評価される。
つまりnが十分大きくなると、その挙動は「指数関数的」になる。


いま、各項は10進数でコマンドラインに表示しているので、各項の桁数は 1 + \lfloor\log_{10} F_n\rfloorである。
先の評価も考慮すれば、第n項の桁数は

    O(\log_{10} \exp(n)) \sim O(n)

で評価されることが分かる。
つまり、フィボナッチ数の各項の桁数はおおよそ「線形」に増えていく。

ところで、上のコマンドラインに放物線が現れたのは、スペースがある適当な間隔で表示されたことが直接の原因である。
ここで、「適当な間隔」というのは、ここまでで議論してきたフィボナッチ数の各項の桁数であり、それはおおむね線形に増加しているのだった。
そこで、長さがO(n)の文字列を繰り返し表示すれば(そして、各文字列のあいだに適当な文字を挟めば)、別にフィボナッチ数列でなくても、同じように放物線が現れそうだと予想がつくだろう。
以下のコードで試してみると、確かにそうなっている。

for i in range(200):
    print(" "*i, end="*")

f:id:developingskills1110:20170126202727p:plain

では、なぜこのような状況で放物線が現れるのか。

次の図で説明する。

f:id:developingskills1110:20170126202835p:plain

コマンドライン下に向かってx軸を、右に向かってy軸をとる。
コマンドラインの左端がy=0で、右端がy=Cである。

各*はxが整数である点に現れていると考え、その座標を、(n, y_n)とする。
上で定義した状況は、図に示した赤色の線の長さの合計が線形に増加する、という状況だから、次のように言い換えられる。

   数列 \{L_n = (C-y_n) + y_{n+1}\}は、等差数列である

これは、Cという定数を抜きにして考えることで、すぐに次のように言い換えられる。

   数列 \{y_n\}の階差数列は、等差数列である

したがって、

    y_n \sim O(n^{2})

が分かり、これにより、放物線が現れることが説明される。
(ほんとうは、離散的な点集合のみで、線は一つも現れてないですけどね)。

あまり、正確には議論していないですが、今日のところはTeXを初めて書いた面倒くささなどもあり、これで

Q.E.D.

ということで許してください。

python3はじめました:とりあえずフィボナッチ数列を書いてみる

ふと読み返してみると、いつからこのブログは旅行記になったのだろう、という感じである。
最近マンネリ気味だったので仕方がない。

さて、なんとなく仕事も閑散期を抜けそうな気がするので、私も心新たに新しい言語を試してみている。
python3である。

とりあえず、python tutorialというサイトが充実しているので、これを見ながら勉強している。

4.6関数定義で出てくるフィボナッチ数列の定義が、ちょっとテクニックを使っていて「おっ」と思った。
そこで、自分でも複数パターン、フィボナッチ数列を返す関数を書いてみたので晒してみることにする。

本家のfib関数は、

# write Fibonacci series up to n

ということで、標準出力含めた形のプロシージャとなっているが、ここでは、
引数nに対して、フィボナッチ数列の第n項を返す関数として定義する。
つまり、副作用のない関数にしておく。
こうしたほうが違いがはっきりするだろう。

この形で本家のfib関数と同じロジックで書くと、次のようになる。

def fib_ver1(n):
    a, b = 0, 1
    for i in range(n):
        a, b = b, a + b
    return a

いっぽう、ふつう教科書とかにありそうなのは、次のような再帰的な記述だ。

def fib_ver0(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib_ver0(n-1) + fib_ver0(n-2)

これは数学的にも、すっきりしていて美しい。
のだが、何回も同じ関数を呼び出すことになり、スタックが有限であるコンピュータの処理的には良くない。

メモ化を使うと次のようになるだろう。

memo = [0, 1]
def fib_ver2(n):
    global memo
    if len(memo) > n:
        return memo[n]
    else:
        ans = fib_ver2(n-1) + fib_ver2(n-2)
        memo.append(ans)
        return ans

グローバルで定義されたリストに、計算結果をメモしておくわけである。
メモリを効率的に使い、ver0で何回も同じことをしていたのを回避している。

for文が死んでもいやであるという純粋主義者がver1を再帰的に書くには、
第n項を返すというインタフェースは諦めて、第n項と第n+1項からなるリストを返すということになるだろう。

def fib_ver1_rec(n):
    if n == 0 :
        return [0, 1]
    else:
        a, b = fib_ver1_rec(n-1)
        return [b, a+b]

これを見ると、ver0では再帰呼び出しを2つ行っているのに対して、ver1では再帰呼び出しを1つに済ませているのがよくわかる。
python tutorialの4.6ではさらっと書かれているが、そういう工夫がされていることには、注意を払うべきポイントだと思う。