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

Line 1: Error: Invalid Blog('by Esehara' )

または私は如何にして心配するのを止めてバグを愛するようになったか

>> Zanmemo

あと何かあれは 「esehara あっと じーめーる」 か @esehara まで

ジャンケンみたいに三すくみになるサイコロについて

今日の風景

f:id:nisemono_san:20160810152435j:plain

mograg garage【ART SPACE /GALLERY】の展示で買った郡司侑祐という方の作品です。

はじめに

普通、サイコロと言うと、1から6までの数字が書いてあるものだ。イカサマのサイコロでは無い限り、お互いの出た目を比較すると、だいたい半々の確率になる筈だろう。しかし、これではゲーム性があまりにもない。そこで、サイコロの目を改造し、あるサイコロAと、あるサイコロB、あるサイコロCの出目がそれぞれジャンケンのように、三すくみになるようなサイコロが作れるという話が実はある。

オンライン上では、英語になるけれども、この記事で概要を知ることができる。また、実物は海外だとグランド・イリュージョン社が販売している。

f:id:nisemono_san:20160810225508j:plain

側面が二つ2のサイコロと、見えている側面全部が4であるサイコロが見える。

さて、このサイコロを実際に自分達の手で作ってみることは非常に楽しいことであるのは間違いないけれども、プログラマーなんだから、簡単にこの手のダイズについてシミュレーションしてみるのが楽しいだろう。

ジャンケンみたいなサイコロの組みあわせ

まず、最初にこの「ジャンケンみたいなサイコロ」の定義から始める。まず、任意の目を持つサイコロが三種類あり、それぞれのサイコロに対して、A、B、Cと名づける。このとき、AはBより勝率が高く、AはCより勝率が低い。同様に、BはCより勝率が高い。当然、CはAより勝率が高い……というようにくりかえされる。

さて、まず気になる出目に関してだが、3つ見つかっている。

  • {3, 3, 5, 5, 7, 7}, {2, 2, 4, 4, 9, 9}, {1, 1, 6, 6, 8, 8}
  • {1, 1, 1, 13, 13, 13}, {0, 3, 3, 12, 12, 12}, {2, 2, 2, 11, 11, 14}
  • {1, 4, 4, 4, 4, 4}, {3, 3, 3, 3, 3, 6}, {2, 2, 2, 5, 5, 5}

元記事によれは、最初の3組は、下の魔方陣から取られている。

|8|1|6|
|3|5|7|
|4|9|2|

それぞれの横の列が、そのままこのジャンケンみたいなサイコロの出目となっている。

実際に勝負させてみる

というわけで、実際に勝負させてみよう。これは簡単なプログラムなので、Lisp系であるところのRacketを使って書き下してみよう:

(define dice-a '(3 3 7 7 5 5))
(define dice-b '(2 2 9 9 4 4))
(define dice-c '(1 1 8 8 6 6))

(define (winner? a b)
  (> (car (shuffle a)) (car (shuffle b))))

(define (win a b)
  (printf "勝利数: ~a\n"
   (count (lambda(x) x)
          (build-list 100000 (lambda (x) (winner? a b))))))

特徴としては、build-listが、第2引数の回数だけ、第三引数の関数の結果をリスト化している。さて、まずdice-adice-bを5回セットさせてみると、以下のようになる。

> (for ([i 5]) (win dice-a dice-b))
勝利数: 55763
勝利数: 55565
勝利数: 55306
勝利数: 55609
勝利数: 55500

だいたい、5.5割の確率で勝利する。同様にAとCの場合:

> (for ([i 5]) (win dice-a dice-c))
勝利数: 44678
勝利数: 44407
勝利数: 44390
勝利数: 44805
勝利数: 44593

となる。つまり、0.5割ほど有利だということになる。次に、dice-bdice-cを組みあわせた場合

> (for ([i 5]) (win dice-b dice-c))
勝利数: 55475
勝利数: 55514
勝利数: 55418
勝利数: 55576
勝利数: 55436

となり、若干有利である。

2回振ると逆転するようなサイコロ

さて、次は{1, 4, 4, 4, 4, 4}, {6, 3, 3, 3, 3, 3}, {2, 2, 2, 5, 5, 5}なのだが、これには不思議な特徴があるらしい。その特徴というのが、なんでも二回振った合計の数だと、勝率が逆転してしまうというものらしい。なので、下のようにリファクタする

#lang racket
(define dice-a '(1 4 4 4 4 4))
(define dice-b '(6 3 3 3 3 3))
(define dice-c '(2 2 2 5 5 5))

(define (throw a) (car (shuffle a)))

(define (winner? a b)
  (> (throw a) (throw b)))

(define (winner-two? a b)
  (> (+ (throw a) (throw a))
     (+ (throw b) (throw b)))) 

(define (win a b)
  (printf "1回だけの勝利数: ~a\n"
   (count (lambda(x) x)
          (build-list 100000 (lambda (x) (winner? a b))))))

(define (win-two a b)
  (printf "2回の勝利数: ~a\n"
   (count (lambda(x) x)
          (build-list 100000 (lambda (x) (winner-two? a b))))))```

一度だけ振ってみる

まず、dice-adice-bの勝率から:

> (for ([i 5]) (win dice-a dice-b))
1回だけの勝利数: 69428
1回だけの勝利数: 69579
1回だけの勝利数: 69485
1回だけの勝利数: 69529
1回だけの勝利数: 69625

次に、dice-adice-cの勝率:

1回だけの勝利数: 41912
1回だけの勝利数: 41687
1回だけの勝利数: 41898
1回だけの勝利数: 41521
1回だけの勝利数: 41748

最後に、dice-bdice-cの勝率:

1回だけの勝利数: 58290
1回だけの勝利数: 58308
1回だけの勝利数: 58267
1回だけの勝利数: 58166
1回だけの勝利数: 58587

若干ばらつきはあるものの、AとBが対決した場合は、Aは7割勝利し、AとCが対決した場合はAは4割勝利し、BとCの場合は5.8割となる。ゲームとしてはともかく、定義的にはあっている。

次に二回振ってみる

そこで、次に二回振ってみることにする。dice-aおよびdice-bの場合だとすると:

> (for ([i 5]) (win-two dice-a dice-b))
2回の勝利数: 48276
2回の勝利数: 47996
2回の勝利数: 48410
2回の勝利数: 48008
2回の勝利数: 48136

ということになり、dice-adice-cだと

> (for ([i 5]) (win-two dice-a dice-c))
2回の勝利数: 59055
2回の勝利数: 59072
2回の勝利数: 59139
2回の勝利数: 59035
2回の勝利数: 59024

ということになる。そこで、さらにdice-bdice-cを比較すると:

(for ([i 5]) (win-two dice-b dice-c))
2回の勝利数: 40914
2回の勝利数: 40884
2回の勝利数: 40908
2回の勝利数: 41044
2回の勝利数: 41082

となるので、見事有利なサイコロが逆転した

まとめ

これが数学に属するのかどうかはよくわからないけれども、このように三すくみのサイコロができるという観点は面白かった。このサイコロはちょっと欲しい気もするけれども、作るとなると、オーダーメイドになるよなーという感じではある。それはともかくとして、「サイコロにはこういう観点から見ることもできるのだな」と思ったので、とてもわかりがあってとても面白かった。

反直観の数学パズル―あなたの数学的思考力を試す14の難問

反直観の数学パズル―あなたの数学的思考力を試す14の難問

数学のおもちゃ箱 下

数学のおもちゃ箱 下

シャッフルしたカードを順番にならべていったら、その並び順と同じ数が出てこない確率はどれくらいだろうか

今日の料理

f:id:nisemono_san:20160808113612j:plain

メンチカツサンドを汚なく作る方法。

トレーズというゲーム、そしてその問題

トレーズ(treize)というゲームがある。このゲームの名前はフランス語で13を表している。このゲームは、ジョーカーを抜いた52枚のトランプを使って遊ぶ。

まず、トランプを良くシャッフルする。そのトランプのカードに1から順番を付けながらめくっていく。このとき、順番と同じ番号が出た場合は勝利し、次のプレイヤーに残りのトランプを渡す。当然ながら、順番とカードの数が一致しないときもある。このとき、プレイヤーは負けということになる。

そこで、このカードゲームにならって、我々は13枚のカードを用意することにしよう。そして、このカードを順番を付けながらめくっていった場合、果して順番と一致しない数しか出ない確率はどれほどになるだろうか。さらに、もしこのカードの枚数を2倍に増やした場合、勝つ確率は上がるだろうか。それとも下るだろうか。

相対確率を調べる

上記の問題は、『反直感の数学パズル』から引用したものである。ちなみに相対確率(試行から当てはまったものを使う方法)では、次のようにして調べることができる。

def treize n
  card = [*0..(n - 1)].shuffle
  card.each_with_index do |k, i|
    return true if k == i
  end
  return false
end

TIMES = 100000

2.upto(52) do |n|
  win = 0
  1.upto(TIMES) do
    win += 1 if treize n
  end
  puts "#{n}枚の当たった回数は#{win}, 確率は#{win.to_f / TIMES}"
end

このようにして出力した結果は下のようになる。

13枚の当たった回数は63272, 確率は0.63272
26枚の当たった回数は63277, 確率は0.63277
39枚の当たった回数は63165, 確率は0.63165
52枚の当たった回数は63187, 確率は0.63187

本書には、13枚で1枚以上当たる確率と、52枚一枚以上当たる確率は、理論上「0.632121」まで一致すると伸べている。

完全順列

先ほどの、任意のカードをシャッフルして、順番と一致しないということを言いかえると、{1, 2, 3, ... , n}の順列とそれがシャッフルしてできた順列({a1, a2, a3, .. , an}と表記する)を照らしあわせると、それぞれの位置において同じ要素が現れない、という風に説明できる。これを完全順列と言うらしい。

さて、もしこのゲームにおいて外れる確率を計算したいとするならば、完全順列の総数を、順列の総数で割ればいいということになる。そこで、本書で紹介されているベルヌーイの方法を利用することにする。

包除原理を使う

包除原理とは、例えばAとBという集合があるとし、nをそこに属する要素を求める関数であると定義した場合、n(A \cup B)に属する要素を求めるにはどうすればいいか、ということである。

もちろん、AとBが独立している場合(つまり、要素の重複が存在しない場合)、n(A) + n(B)とすることで計算ができる。とはいえ、あらかじめ、AとBが独立しているかどうかがわからない場合、n(A) + n(B) - n(A \cap B)とする。理由は、Aを数え、かつBを数えた場合、AとBに属している数は2度数えられているという計算になるからだ。

さて、集合が2つの場合はわかった。今度は3つの集合であるA, B, Cの場合を考えてみよう。最初同様に、n(A) + n(B) + n(C)を計算する。当然ながら、n(A \cap B)n(A \cap C)n(B \cap C)の場合は前出同様、2重に数えられているわけだから、これを消す。

しかし、ちょっと考えてみよう。n(A \cap B \cap C)は、この段階でどれだけ含まれているか。まず、n(A) + n(B) + n(C)の段階で3回数えられている。そのあとn(A \cap B)n(A \cap C)n(B \cap C)で3回除去されているので、実質計算されていないことになる。これではまずいので、n(A \cap B \cap C)を足しあわせることにする。

ここで、ある法則性が理解できる。先ほどの集合の名前A, B, CをA1, A2, A3と表現した場合、まず最初にそれぞれの総数をとりあえず求め、そのあとに、2組の重複、つまりは偶数組は引き算をおこない、そして、3組の重複 、つまりは奇数組は足すとよい。これは集合が4つある場合もそうであるので、もし手元に手軽なノートがあるならば、各自で確認して欲しい。

完全順序と包除原理

さて、そこで完全順序の求めかたを考えてみよう。そこで、わかりやすいように、順列が{1, 2, 3}の場合を考えてみよう。

まず最初にこの順列をシャッフルして取りうる全ての数を計算する。そのリストを上げると下のようになる。

{1, 2, 3}
{1, 3, 2}
{2, 1, 3}
{2, 3, 1}
{3, 1, 2}
{3, 2, 1}

この程度なら個数は数えあげられるのだけれど、今はロジックを確認するためのものだから勘弁して欲しい。

まずそれぞれの順列に対して{a1, a2, a3}という名前を付ける。まず最初のケースとして{a1 = 1}, {a2 = 2}, {a3 = 3} という場合を取りあげる(今後、このように完全順序にならないものを「固定した」と表現する)。このとき、元の順列と、上記の事例は完全順序ではないので排除される。

{2, 3, 1}
{3, 1, 2}

実際、ここで既に要素は出ているのだが、あくまで包除原理にもとづいて計算を進める。先に言うと、奇数回で消さなければならない要素は{1, 2, 3}である。そこで、偶数回である今回は、{a1 = 1, a2 = 2}, {a2 = 2, a3 = 3} といった配列を足すことにする。とはいえ、これに当てはまるのは {1, 2, 3}だけなので、{1, 2, 3}を足すことになる。

{1, 2, 3}
{2, 3, 1}
{3, 1, 2}

で、これを改めて消すと、元の組みあわせが出てくる。

この説明を元に公式を作ると、次のようになる。まずa_nはn個が固定された(つまり、完全順序数とならない)組みあわせ数だと考えて欲しい。

 n! - a_1(n - 1)! + a_2(n - 2)! - a_3(n - 3)! + ... 1

そこで、{a1}や{a1, a2}の組み合わせの総数が必要となる。これは箱の中にボールが入っていて、そこから取りだすときの組みあわせと考えるとわかりやすい。

先の例であるならば、固定された要素数は1個である。その1個をボールから取り出す可能性は3つある。なので、3になり、そして残りの順序を計算する。これは_nC_r = \frac{n!}{r!(n - r)!}という公式があるので、これを利用し、変形すると、次のようになる。

 n! - \frac{n!}{1!(n - 1)!}(n! - 1)! + \frac{n!}{2!(n - 2)!}(n - 2)! - \frac{n!}{3!(n - 3)(n - 3)!} + ... 1

ということになる。良く見れば、分母は削ることができる。なので、さらに簡略化して

 n! - \frac{n!}{1!} + \frac{n!}{2!} - \frac{n!}{3!} + ... 1

とでき、さらに共通のn!をまとめれば、次の公式が出てくる。

 n!(1 - \frac{1}{1!} + \frac{1}{2!} - \frac{1}{3!} ... + (-1)^{n}\frac{1}{n!}

実際にこれを利用して3を計算すると、次のようになる。

 6(\frac{2}{6})

また、5を計算すると、44となり、本書と一致する。そして、確率を計算する場合、完全順序の総数を順序の総数で割れば良い。なので、実質:

 1 - \frac{1}{1!} + \frac{1}{2!} - \frac{1}{3!} ... + (-1)^{n}\frac{1}{n!}

ということになる。そして、この式からわかるように影響する分子はどんどん大きくなっていくため、枚数をいくら増やしても、その確率にほぼ影響は無い(興味がある人は実際にコードを書いて検証してみるといいかもしれない)。

まとめ

だいたい、エントリを書く前にノートに草稿を書くという律儀なことをしているのだけれど、このエントリを書くためにノートを4ページくらい使った。とはいえ、それを使うだけの納得感は味わえたので、非常によかった。数学の良さがわかるように、一歩くらいは前進したのかもしれない。

反直観の数学パズル―あなたの数学的思考力を試す14の難問

反直観の数学パズル―あなたの数学的思考力を試す14の難問

運気を上げるために魔方陣を作る(初級編)

今日の寿司

f:id:nisemono_san:20160808145908j:plain

中身が気になる御歳頃。

はじめに

最近運気が悪く、悪夢も良くみるようになったので、これは邪気を払う必要がある。なので、そういうのに使えそうなものと言えば何か考えたら、魔方陣ではないか、ということを考えていた。

昔の夢は何だったか思い出すと、科学者か魔法使いになりたかった思い出があり、極まったプログラマというのはWizardと呼ばれる。さすがに、自分がそのレベルに達しているとは思えないが、どうせだし、魔法陣くらいは作れるようになりたいと思ったので、今回のエントリを書く。以下、『魔方陣の世界』という参考書(エントリの最後に記述する)を元に書く。

魔方陣とはなにか

とはいえ、魔方陣とは何かについて、最初に書いておかないといけない。魔方陣とは、1から始まる連続した異なる自然数を碁盤の目状に並べて、それぞれの列、それぞれの行、そして対角線の数の和が一緒であるものを指す。具体的には、下のような形となる。

----------------
|14|25|08|16|02|
----------------
|03|11|22|09|20|
----------------
|17|04|15|23|06|
----------------
|10|18|01|12|24|
----------------
|21|07|19|05|13|
----------------

このような、5x5(以下「5次の魔方陣」と呼ぶ)は、その特性上、各和が65となる。試しに適当に抜きだしてみると、8 + 22 + 15 + 1 + 1930 + 15 + 20とでき、合計が65になることが確認できる。

一般的に、n次の魔方陣の定和(つまり、それぞれの行や列の総和)は\frac{n(n^{2} + 1)}{2}という公式で導き出すことができる。5次の場合、5 * (25 + 1) / 2 = 130 / 2 = 65となる。これは1 + 2 + 3 .. nの総和を出す公式が\frac{n(n+1)}{2}であることから由来している。これがもし「7次の魔方陣」であるならば、総和は175ということになる。7桁の魔方陣であるならば、次のようになる。

----------------------
|27|33|10|49|18|36|02|
----------------------
|07|25|29|09|48|19|38|
----------------------
|37|06|26|31|14|46|15|
----------------------
|17|42|04|22|30|13|47|
----------------------
|43|16|41|05|24|35|11|
----------------------
|12|45|21|39|01|23|34|
----------------------
|32|08|44|20|40|03|28|
----------------------

6桁の魔方陣が1600京以上(!!)あると言われていることから、当然のことながら、7桁の魔方陣も、それに匹敵する数存在していることは間違いない。しかし、その作りかたは、考え方さえわかってしまえば、至って簡単である。

二つの補助魔方陣を作る

まず、7次の魔方陣を作るさいに、7進法の考え方をする。例えば、8であるならば10、9であるならば11、といったように、7で位が上がる数のことを指す。

この時に、2の桁と1の桁を分離する。そして、1から7までの数を利用して、列同士では数は被っても良いので、定和性を満す魔方陣を作る(この場合ならば、1から7なので、28となる)。具体的にプログラミングで出力すると、次のような補助魔方陣である。

[4, 7, 6, 1, 5, 3, 2]
[2, 4, 7, 6, 1, 5, 3]
[3, 2, 4, 7, 6, 1, 5]
[5, 3, 2, 4, 7, 6, 1]
[1, 5, 3, 2, 4, 7, 6]
[6, 1, 5, 3, 2, 4, 7]
[7, 6, 1, 5, 3, 2, 4]

このとき、生成を簡単にするために、あるトリックを使っているのだが、それはとりあえずあとまわしにしよう。そして、もう一つの補助魔方陣を用意する。

[4, 6, 2, 1, 7, 3, 5]
[1, 7, 3, 5, 4, 6, 2]
[5, 4, 6, 2, 1, 7, 3]
[2, 1, 7, 3, 5, 4, 6]
[3, 5, 4, 6, 2, 1, 7]
[6, 2, 1, 7, 3, 5, 4]
[7, 3, 5, 4, 6, 2, 1]

まず魔方陣の最初の特性として、「同じ数を二度使わないことである」が挙げられる。とすると、この時、この二つの補助魔方陣を組みあわせた場合、同じ組みあわせである場合、それは同じ数であるわけだから、魔方陣としては成立していない。なので、補助魔方陣のお互いの要件の第一条件として、組みあわせたときに、「同じ数の組みあわせは二度あらわれない」となる。

利用する補助魔方陣の性質

ここからは、ちょっとややこしい話も含まれるので、コードを見せながら解説する。

まず補助魔方陣の一つ目の特徴だが、まず左上からスタートする対角線上に(n + 1) / 2(この場合のnは「n次の魔方陣」のn)を引いたあと、それ以外の数はシャッフルしても良い。7の場合は4なので、4が対角線上に配置されていれば良い。

class Mahou

  DIMENSION = 7

  def self.simple_a
    seed = [*1..DIMENSION]
    delete_elem = (DIMENSION + 1) / 2
    seed.delete(delete_elem)
    seed.shuffle!
    seed.insert(delete_elem - 1, delete_elem)
    a = Array.new(DIMENSION) { seed.clone }
    r = delete_elem
    0.upto(DIMENSION - 1) do |x|
      r -= 1
      a[x].rotate!(r)
      r = DIMENSION - 1 if r < 0
    end
    return a
  end
end

さて、もう一つの補助魔方陣を作る必要があるのだが、ちょっと困ったことがある。それは法則上、nが3の倍数の時は違う法則性を持つ。従って、今回はnが3以外の倍数であるときのことを考える。

考え方としては簡単で、1からnまでの数字をまず適当に並べる。そのあと、対角線と並べた数字とは逆に、縦に並べていく。解りにくいだろうので、1から5の間の数字で説明すると、次のようになる。

[1, 3, 5, 2, 4]
[5, 2, 4, 1, 3]
[4, 1, 3, 5, 2]
[3, 5, 2, 4, 1]
[2, 4, 1, 3, 5]

縦方向に着目してもらえればわかるように、1の次は5,5の次は4というように、対角線に並べられた数字とは逆の数字が配置されている。同様に、2列目も、2の次は1, 1の次は循環して5……というように並べられている。これをコードにすると、やや泥臭いが次のようになる。

  def self.simple_b
    b = Array.new(DIMENSION) { Array.new(DIMENSION) }
    raw_seed = [*1..DIMENSION].shuffle
    0.upto(DIMENSION - 1) { |i| b[i][i] = raw_seed[i] }
    seed = raw_seed.reverse * DIMENSION

    0.upto(DIMENSION - 1) do |x|
      point = seed.index(raw_seed[x])
      start = x
      1.upto(DIMENSION) do |y|
        b[start][x] = seed[point]
        point += 1
        start += 1
        start = 0 if start >= b.size
      end
    end
    return b
  end

組みあわせる

組みあわせるのは非常に簡単だ。2次元配列なので、入れ子状にしてあげればよい

  def self.format_print m
    l = "---" * DIMENSION + "-"
    puts l
    m.each do |n|
      n.each do |e|
        print "|"
        printf "%02d", e
      end
      puts "|"
      puts l
    end
  end

  def self.simple
    a = simple_a
    b = simple_b
    m = Array.new(DIMENSION) { Array.new (DIMENSION) }
    a.each_with_index do |i, j|
      i.each_with_index do |k, n|
        m[j][n] = (k - 1) * DIMENSION + b[j][n]
      end
    end
    format_print m
  end

aは7進法の二桁目、 bは一桁目をさしている。ただし、二桁目に関しては、0である場合もあることを考慮しなければならない。なので、実際の計算には一度1を引いてやる必要がある。また、上記では考慮していないが、魔方陣に必要なのは、「それぞれの列、行、対角線の総和が一緒であること」なので、それをチェックするメソッドもあったほうがいいだろう。

  def self.column_check a, b
    0.upto(DIMENSION - 1) do |i|
      sum = 0
      0.upto(DIMENSION - 1) do |j|
        sum += a[j][i]
      end
      raise "縦の行: #{j + 1}#{sum} なので定和性を満しません" if sum != b
    end

    0.upto(DIMENSION - 1) do |i|
      sum = a[i].inject(:+)
      raise "横の行: #{i + 1}#{sum} なので定和性を満しません" if sum != b
    end

    sum = 0
    0.upto(DIMENSION - 1) do |i|
      sum += a[i][i]
    end
    raise "左上からの対角線が #{sum} なので定和性を満しません" if sum != b

    sum = 0
    0.upto(DIMENSION - 1) do |i|
      sum += a[i][DIMENSION - 1 - i]
    end
    raise "右上からの対角線が #{sum} なので定和性を満しません" if sum != b
  end

まとめ

というわけで、古典的な「魔方陣を解く」というコードを書いてみたけれども、意外と難儀した。多分、手元でやれば一瞬で終わることがコードにうまく落としこめないと、自分が終わっているような気がして気が沈むので良くないと思う。やはりコードを書いていくことは大切だということがわかったりした。

また、魔方陣が気軽にできるようになったので、悪いことが起きたら、気軽に実行して魔除けしたいと思った。これで運気が上がればなーと思っている。

ちなみに実行可能なコードはこちらです

参考文献

魔方陣の世界

魔方陣の世界

『オープン・デザイン』は、むしろプログラマが読むべき本だった

今日の十六茶

f:id:nisemono_san:20160807001117j:plain

試してガッテン方式で入れている。

はじめに

オライリー社から2013年に発売された『オープン・デザイン』という本は、率直に言ってしまえば、如何にもデザイナー向けの思弁的な議論のアンソロジーとなっている。それらは、直接的には技術的な洞察を与えるものではないだろうし、また同様に、それが直接的に業務に使えるものかといったらそうでもない。

そうではないのにも関わらず、この本は、プログラマにとって重要な本であることは間違いないと、僕は確信している。逆説的なことではあるが、この本が技術書でないからこそ、あまりにも無視され続けた本であると思うのだが、だからこそ、今読むべき本であると思う。

プログラマはデザインが下手であるという現実を直視する

もちろん、デザインという言葉は多義的な言葉であることは間違いない。まず指摘できることは、日本語の場合、デザインという言葉は「設計」という言葉ではなく、どちらかというと「グラフィック」に偏りがちである。そのような瑣末はどうでもいい。

よく言われる揶揄の一つとして、プログラマはデザインが下手であるということが良く言われる。お世辞にも、プログラマが作るようなアプリケーションには、UIが極めて粗雑なプロダクトは多くはない。それは、おそらく当人であるプログラマがそう認めるだろう。このブログも、お世辞なりとも、デザイン的に優れているとは言いがたい。

それがどうしてなのか、については、ここでは觝れないようにしよう。問題は、例えば「Twitter Bootstrap」のように、デザインがオープンにされたことにより、幾分かマシなUIというのが、プロトタイピングの段階で手にすることができるようになった。この本の中に収録されている一文について、書きうつしてみよう:

未来のデザイナーは、スキルのないユーザーでもものをデザインできるような環境を形づくる、メタデザイナーにならなければならない。ーー『デザインのリデザイン』,p041より

自分が理解する限り、ここには一つの洞察がこめられれている。未来予測というのは、いつでも扇動的でおおげさなものであるけれども、この文句は魅力的だ。少くとも、この論文が示している通り、「人間は自身の世界を自分でデザインせねばならない(p.039)」わけだけれど、しかし全ての人間が環境をデザインできるわけではない

序論では、次のような言葉が掲げられている:

現代のパイオニアはたいてい、ハッカーやアーティスト、アクティビストのような気質を持っている人々だ。彼らは、「世界」を外部から与えられたものとして額面通りに受け入れることはしない。むしろ、「世界」を、こじ開けることが可能な、いじくり回せる対象として捉えている『序論』,(p.021) より

しかし、このように「こじ開けることが可能な、いじくり回せる対象」として捉えたとしても、デザインとして、それをどのように「いじくり回すのか」ということには、技量が必要になる。それは、デザイナが如何にしてプログラマがコードをハックするのかについて、未知であるように、デザイナがプログラマにとってどのようにデザインをハックしているのかについて未知であるのと似ているのである。

ユーザーにとってのオープンデザイン

あとの言及になるのだが、この本には「はじめに」という序文が付いている。そこで、「クリエイティヴ・イノベーション」に言及しながら、それは「参加型イノベーション」ではないか、と伸べている。この言葉は多義的であるが、一つの示唆がある。

例えば、最近であるならば、「リーンUX」といったような、ユーザーの声を聞きながら、よりよいユーザー体験を提供できるように、デザインを改良していく方法が盛んとなっている。本書におさめられている『オーケストラ演奏のようなデザイン』においては、簡潔にデザイン(特にプロダクトにおいて)について語られている。それをざっくりと要約すれば、次のようになる。

まず、デザインというのは、「デザインに関する議論の最前線に「センスのよさ」というエリート主義的な見方が注がれていたわけだけれども、「特定なデザインの妥当性は、クリエイターによってではなく、ユーザーによって決定されるという事実に気付かされた」。その後、「ユーザーの意見が重要になっていき、より賢明なデザイナーはユーザー中心のデザインプロセスを広め始めた」とまとめている(p.28)。

この議論はいささか乱暴にも聞こえるが、しかし、デザインの中に、「ユーザー」という登場人物が出てきたことは、考慮すべき問題なのだと思われる。だから、本書において中心になっているのは、「ユーザー」という言葉である(例えば、『オープンデザインを生成する基盤』では、「ユーザー主導」という言葉が使われている)。そして、恐らくは、そのユーザーの中にわれわれがいるのだ。

その他のトピックス

この本では、その話題は多岐に渡っている。最近でいうならば、やはり「3Dプリンタ」の話題が多く出ている。これは、モデルとその素材を手に入れることができるならば、すぐにそのパーツは手に入れられるということに、そのオープンな可能性を示唆しているものもあるし、あるいは、やはり「オープンである」からには「ライセンス」の問題は避けられないだろう。

また、「オープンデザイン」、もしかしたら「オープンソース」にとって、いや、これから来たる「オープンX」にとっての関心事は、本書の言葉を借りるならば「知識と生産手段の再配分」『JORIS LAARMANによるオープンソースデザインの実験』(p.130)であると言えるかもしれない。だからこそ、「もし著作権を放棄したらどうなるだろう?」『ベストセラーの消失』(p.104)問いが出てくる。あるいは、知識の問題だからこそ、教育の問題も同様に出てくる。冒頭には、このような刺激的な文章が添えられている。

実際、ものを作ることと、いわゆる「科学」の領域との間には本質的な隔たりがあるという、社会における根本的な分断こそが議論すべきテーマなのかもしれない。ものを作るべきことには科学が少なすぎるし、科学のなかで何かを作ることもほとんどない。この2つはあまりに遠く、接点がないのだ。

まとめ: オープンということが空気になりつつある時代に

この本はあまりにも面白くて、書評は、書こう書こうと思って、つい先伸ばしにしていたものである。で、今回やっと書くことができた。

とはいえ、既にオープンという言葉が「空気」となり、同時に「うさんくさい」時代にとって、オープンデザインという考えかたが埋もれてしまうのはもったいないだろう。たしかに、本書はややイデオロチック、つまり堅苦しくて扇動的、言いかえればユートピア的であることも否めないが、しかし人間が何か進歩していくということは、前向きの想像力でもあるということを実感させてくれる意味でも、とてもいい本だと思う。デザイナーだけではなく、幅広い人に読んで欲しい本だ。

オープンデザイン ―参加と共創から生まれる「つくりかたの未来」 (Make: Japan Books)

オープンデザイン ―参加と共創から生まれる「つくりかたの未来」 (Make: Japan Books)

  • 作者: Bas Van Abel,Lucas Evers,Roel Klaassen,Peter Troxler,田中浩也,川本大功,巾嶋良幸,古賀稔章,水野祐,岩倉悠子,菊地開司
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2013/08/24
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログ (7件) を見る

参考文献

古典的なデザインの本も参考までに

ノンデザイナーズ・デザインブック [第4版]

ノンデザイナーズ・デザインブック [第4版]

あとはリーンの奴も

Lean UX ―リーン思考によるユーザエクスペリエンス・デザイン (THE LEAN SERIES)

Lean UX ―リーン思考によるユーザエクスペリエンス・デザイン (THE LEAN SERIES)

  • 作者: ジェフ・ゴーセルフ,坂田一倫,ジョシュ・セイデン,エリック・リース,児島修
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2014/01/22
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログ (2件) を見る

立方体がいつのまにか正六角形に見える錯視画像をProcessingで作る

今日の風景は休みです

f:id:nisemono_san:20160804085435j:plain

はじめに

元々錯視というか、トリックアートみたいなのが好きだったのだけれども、久しぶりに「なるほど」と思った錯視画像が『哲学101問』の中に紹介されていた。

f:id:nisemono_san:20160806112707p:plain

f:id:nisemono_san:20160806112528p:plain

これが、立方体が正六角形に見えるというものなのだけれど、単純にそれを書いても面白くないだろう、ということで、久しぶりにProcessingを使って、GIFアニメーションにしたら、錯視っぽさが出てくるのではないだろうかと思って作ってみたのが、今回のエントリとなる。

制作物

とはいえ、いきなりProcessingのことを書きはじめても仕方ないので、先に成果物を出す。

このような単純な動画であるならば、Processingで簡単にできる。

UbuntuによるProcessingのインストール

これ自体は簡単で、Linux用の圧縮ファイルの解凍する場所に、processingというパスが通るようにするといい。色々な方法はあるけれど、自分の場合だと、自分のホームディフェクトリに対して$HOME/binというディフェクトリを掘り、そこにパスが通るようにしている。

Processingの利用言語をPythonとして使う

手元にあるProcessingの本によれば、Processingは、メディアアートのための「スケッチブック」という側面を強調 している。つまり、気軽に自分のグラフィック的なアイデアを簡単に実現するためのもの、という側面を見ている。

しかし、デフォルトの言語であるJavaは、静的型付け言語という側面もあり、簡単に書く分には少々面倒臭い。ProcessingにはPythonをサポートしているのもあるので、そちらを使うほうが、自分の性にあっている。なので、Pythonを利用して、自分は書くことを決めた。Pythonの言語を追加する方法は下のような感じ:

右の言語の部分をクリックすると「モードの追加」が出てくるので、それを選択:

f:id:nisemono_san:20160806115636p:plain

そのあとにPythonを選んでインストール完了

f:id:nisemono_san:20160806115834p:plain

Processingの基本

先にコードを見せたほうが早いので、コードを先に掲載しておく:

add_library('GifAnimation')
animation = GifMaker(this, "/home/esehara/Pictures/sample.gif")
frame = 0
rev = False


def setup():
    size(300, 300)
    stroke(0, 0, 0)
    strokeWeight(3)
    animation.setRepeat(0)


def draw():
    background(255)
    stroke(255, 255)

    draw_quad(fillalpha(rev))
    draw_line(fillalpha(rev))

    animation.setDelay(1)
    animation.addFrame()
    update()


def fillalpha(rev):
    global frame
    if rev:
        return (255 * 2) - frame
    else:
        return frame    


def draw_quad(fillalpha):
    stroke(0, 255)
    fill(255, 255, 0, fillalpha)
    quad(150, 35, 50, 94, 50, (94 + 115), 150, 150)
    
    fill(255, 0, 255, fillalpha)
    quad(50, (94 + 115), 150, (300 - 45), 250, (94 + 115), 150, 150)

    fill(0, 255, 255, fillalpha)
    quad(150, 35, 250, 94, 250, (94 + 115), 150, 150)


def draw_line(fillalpha):
    stroke(0, fillalpha)
    line(50, 94, 150, 150)
    line(250, 94, 150, 150)
    line(150, 150, 150, 250)


def update():
    global frame
    global rev
    frame += 5
    if frame == 255:
        rev = True
    if frame > (255 * 2):
        animation.finish()

基本的には、Processingが実行されるときに呼びだされるsetupという関数と、定期的に実行されるdrawという関数を定義するという方針になっている。とりあえず、この二つで、画像を書いていくことになる。

ちなみに、Processingは、キャンパスに対して重ね描きするので、初期化方法として、backgroundを使う。これによって、キャンパスに描かれたものがクリアされる。

考え方

まず最初に、正三角形をぐるりと中心から配置した正六角形を作る。ここから、三つの線を消すことによって、立方体に見せることにする。

三つの線を消すということを考えた場合、愚直に三角形を描写することを考えず、四辺形を描写するquad関数を使い、これを消えるラインで分割することにする。ただ、先に一辺がどれくらいの長さになるかを知らないといけない。

正三角形の高さに対し、辺の長さを求める公式はあるのだが、とりあえずその公式はすっとばして、垂線の高さが100の場合は115になる(このサイトを利用した)。一辺が115になることがわかれば、図のようにAB、CD, DE, EFの座標を計算すれば良い。あとは、このラインにそって引いていけば、正三角形による正六角形ができあがる。図にすれば下のような感じ。

f:id:nisemono_san:20160806121056p:plain

ちなみに、ABの中点を仮にHと置くと、HGは100となる。

GIFの作りかた

Processingには、GifAnimationというプラグインが存在している。Ubuntuの場合、これを、Processing起動時に生成されるホームディレクトリ下のsketchbook/librariesに置くと良い。そのあと、「スケッチ」というメニューから「ライブラリをインポート」で、「GifAnimation」を選択すれば、ライブラリ用のコードが投入される。(参考:Processing 3でGIFアニメを作って保存する方法 - Qiita)

まとめ

個人的な印象だと、やはりプロトタイピングというか、スケッチベースの落書きをコードベースでやるんだったら、動的型付け言語のほうが好きだなあ、というのがある。なんで型を気にしながら、いちいち書いていかなければいけないのか、なんて面倒なことを考えてしまう。

以前にClojureから使えるQuilを使っていても思ったけれども、なにかをインタラクトに書く用途であるならば、自分としては動的型付け言語がいいな、と思ったりしたのであった。

参考文献

哲学101問 (ちくま学芸文庫)

哲学101問 (ちくま学芸文庫)

Processing:ビジュアルデザイナーとアーティストのためのプログラミング入門

Processing:ビジュアルデザイナーとアーティストのためのプログラミング入門

  • 作者: ベン・フライ,ケイシー・リース,中西泰人,安藤幸央,澤村正樹,杉本達應
  • 出版社/メーカー: ビー・エヌ・エヌ新社
  • 発売日: 2015/09/20
  • メディア: 単行本
  • この商品を含むブログを見る

何人集めたら、誕生日が同じペアが生まれる確率が高いのか確かめる

今日の風景

f:id:nisemono_san:20160803153457j:plain

なぜ人は半額のシールを見ると買ってしまうのか。

お話

あるところに、パーティ好きの二人組がいた。この二人のやるパーティは多種多様だったので、同じ客層はほぼ無かった。ところで、この二人組の最近の悩みは、パーティを主催するのにも飽きて来たことである。そこで、次のパーティを開くにあたって、このような提案をした。

「パーティを開いたときに、客が会場に入ってくるだろ? そこで、会場に入った人数で区切りをつけで、その中に同じ誕生日のペアがいるかどうかを賭けてみるというのはどうだろう?」

当然ながら、同じ誕生日のペアがいる確率は365人になればなるほど近づくだろうし、同じ誕生日でなければ、2人ならば、ほぼ同じ誕生日にならないだろう。それだと、この賭けは面白くない。そこで、できるだけお互いの確率を一緒にしたい。さて、この区切りを何人にすれば、お互いに平等であると言えそうだろうか。

議論

有名な話であり、Wikipediaにも「誕生日のパラドックス」として詳細にまとめられているけれども、解説する。

まず単純な話から始めよう。街中で、二人に声をかけ、その二人の誕生日が違う確率について考えてみよう。二人目が違う誕生日であるということは、改めて確認することなく、一人目の誕生日と一緒ではない、ということだ。

誕生日は365通りあり、そのうちの一つが選ばれる。その一つ以外を選べば違うということになるわけだから、残り364通りとなる。もちろん、誕生日自体は365通りあるわけだから、確率的には364/365 = 約99.72%となる。

さて、今度は3人に声をかけて、誕生日が違う確率について考えてみよう。まず2人が違う確率は364/365だった。そして、3人目は、その中の363通りなので、363/365となる。

ここで、単純に363/365と考えてはだめで、364/365 * 363/365と考えなくてはいけない。なぜなら、363/365だと、365通りのうち、2つの誕生日が含まれている確率だからだ。

しかし、考慮しなければならないのは、この場合、お互いの誕生日ということだ。まず、一人目と二人目を照しあわせ、その次に、一人目二人目と、三人目を照らしあわせないといけない。従って、364/365 * 363/365となる。

先のWikipediaの記事によれば、22人がだいたい五割に近いということになっている。計算の仕方は、1/2 * 2/3が2/6 = 1/3になるように、分母を乗算したものに対して、分子を364 * 363 * 362 ...といったようにしていけばよい。Haskellで書くなら次のようになるだろう:

import Text.Printf

birth_probability :: Integer -> IO ()
birth_probability 0 = return ()
birth_probability n = do
  putStrLn $ printf "Member :: %i" $ n + 1
  putStrLn $ show $ fromIntegral (product [(365 - n)..364]) / (365 ^ n)
  birth_probability $ n - 1

main :: IO ()
main = birth_probability 50

ここで顕著な例として、10人、20人、22人、23人、30人、40人の場合を取りだしてみると、次のような確率となる。

Member :: 10
0.8830518222889224
Member :: 20
0.58856161641942
Member :: 22
0.5243046923374499
Member :: 23
0.49270276567601456
Member :: 30
0.2936837572807314
Member :: 40
0.10876819018205101

この計算を信用するならば、10人なら約9割の確率で外れ、20人なら約6割、最も公平なのは23人の場合で5割、30人なら3割で40人なら1割しか外れない。

これが「誕生日のパラドックス」と呼ばれるのは、このように、直感的には少ないと思われる人数であっても、誕生日のペアが成立することにあるのだが、ここはプログラマ、さっそくこれを検証してみることにするわけだけど、コード自体は非常に簡単だ。

[10, 20, 22, 23, 30, 40].each do |n| 
  not_uniq_birthday = 0
  1.upto(1000000) do
    people = Array.new(n) { Random.rand(366)}
    not_uniq_birthday += 1 if people.size == people.uniq.size
  end
  puts "#{n}人の場合 => #{not_uniq_birthday}回外れ"
end

実行すると、以下のようなログとなる。

10人の場合 => 883407回外れ
20人の場合 => 589574回外れ
22人の場合 => 525452回外れ
23人の場合 => 493833回外れ
30人の場合 => 294524回外れ
40人の場合 => 109550回外れ

びっくりするほど、計算通りに外れてくれることがわかる。

結論

誕生日のパラドックス自体は有名な話ではあるけれども、しかし実際にそういう風に計算ができる、というのは知っていたとしても、では試行すると、本当にその計算に近い数字が出るのか、ということをやってみた人は少ない気がする。プログラミングによって、こういう素朴なことを試せるようになったようになったことはいいことだと思う。計算だけでなく、実験もどんどんやっていって、「確かに計算通りになるんだな!」というのを実感していってみたいと思う。

哲学101問 (ちくま学芸文庫)

哲学101問 (ちくま学芸文庫)

ギャンブラーの錯覚は本当に錯覚なのかどうかをRubyで検証する

今日の料理

f:id:nisemono_san:20160802154059j:plain

食べものに困ったときは、近くの肉屋でハムカツを買ってくればいい[要出典]。

お話

あるところに三人のギャンブラーがいた。この三人は仲が良く、今度カジノに繰りだそうということになった。そのカジノには、三つで一組のスロット台が存在していて、その三つのどれかが当たるような仕組みとなっていた。また、このカジノの売りは、それぞれのスロットを完全に操作せず、ランダムに当たりが出るようにしているということである。

ところで、ギャンブルにはオカルトというか、ある種の信念めいたものが、ギャンブラーにつきまとう。このギャンブラー三人は、それぞれ違った信念を持ちあわせていた。

まず一人目のギャンブラーは、「それぞれの台が完全にランダムに当たりが選ばれるとするならば、次にどの台を選択するかということを悩んでも仕方がない。なので、直感を信じて、台に座るのが正しい」

しかし、二人目のギャンブラーはこの意見に反対だった。「スロットが操作されていないとするならば、最終的にはそれぞれの台の当たりは同程度になる筈だ。とすると、例えば一台目が連続して20回当たるようなことがあれば、一台目が当たる確率は少なくなる筈だ。そう考えると、今までの台の中で一番当たってないものを選ぶのが正しい」

三人目はどちらのギャンブラーの意見にも疑問を抱いていた。「そもそも、二人の意見は余りにも頭でっかち過ぎる。それぞれの台が操作されていなかったとしても、ギャンブルには流れというものが存在している。君が出した、一台目が連続20回当たるのは、一台目に流れが来ているからだ。そのことを考慮に入れるならば、もっとも当っている台に賭けるのが正しい」

さて、この中で一番誰が勝利しただろうか。

議論

これはギャンブラーの錯覚という言葉で良く知られる(ギャンブラーの誤謬とも言う。英語だとGambler's fallacy)として有名である。乱暴に要約してしまえば、「なんらかの事象が起きたときに、その次に同じ事象が起きる確率は低くなる」とする信念のことである。

ギャンブラーの話に出てきたように、例えばコイントスで表が10回連続で起きると、「次は裏が出るのではないか」と不安になるだろう。ちなみに、コイントスで表が10回連続で起きる確率は 1/1024となり、11回目で 1/2048 となる。

とはいえ、そのような連続して何回、ということを考えずに素朴に考えるならば、歪なコインでは無い限り、1/2の確率となる筈である。

確率の世界には、「ある事象とある事象は独立である」という言いかたをする。これは、ある事象(仮にこの事象をAとしよう)が起きた上でのある事象の確率(仮にこの事象をBとする)が、B単体で起きる場合と同等であるときを指す。本来ならば、「ある台が当たった」という条件下では「次に他の台が当たる」確率に影響は及ぼさない筈である。今回の場合、これを疑ってみるというわけだ。

コード

コード自体は至って簡単である。まず最初に、クラス定義を行う。この話において必要なのは、スロットマシンとプレイヤーである。プレイヤーの勝利判定などは共通化できるので、クラスとしてまとめておき、推理ロジックの部分だけ継承してダックタイピングができるように整える。

実験方針としては以下の通り。まず、スロットマシンにどの台が当たったかの統計をまとめておく。次に、プレイヤーは、そのスロットの統計を基準に推理する。当然、一人目のプレイヤーはランダムに選ぶというのを決めているので、統計は無視する。そして、セット数を決め、各プレイヤーには、セットごとの順位でポイントが与えられるようにする。セットのはじめには、勝利数はリセットされるようにする。

class SlotMachine
  attr_accessor :win
  attr_accessor :win_static

  def initialize
    @win = {}
    @win_static = {0 => 0, 1 => 0, 2 => 0}
  end

  def set_win!
    @win = Random.rand(3)
    @win_static[@win] += 1
  end

  def sort_win
    win_static.sort {|(a1, a2), (b1, b2)| a2 <=> b2}
  end
end

class Player
  attr_accessor :win
  attr_accessor :point

  def initialize
    @win = 0
    @point = 0
  end

  def win? machine
    @guess == machine.win
  end

  def win_or_lose machine
    @win += 1 if win? machine
  end

  def result
    puts "Player Win: #{win}"
  end
end

class SimplePlayer < Player
  def guess machine
    @guess = Random.rand(3)
  end
end

class LeastGuessPlayer < Player
  def guess machine
    @guess = (machine.sort_win)[0][0]
  end
end

class MostGuessPlayer < Player
  def guess machine
    @guess = (machine.sort_win)[-1][0]
  end
end

で、実際に試行するコードは以下の通り。

simple_player = SimplePlayer.new
least_player = LeastGuessPlayer.new
most_player = MostGuessPlayer.new
players = [simple_player, least_player, most_player]

slot = SlotMachine.new
1.upto(1000) do
  players.each { |player| player.win = 0 }
  1.upto(1000) do
    slot.set_win!
    players.each do |player|
      player.guess slot
      player.win_or_lose slot
    end
  end
  players.sort! { |p1, p2| p1.win <=> p2.win }
  players[0].point += 2
  players[1].point += 1
  players[2].point += 0
end

puts "==========================="
puts "Result:"
players.each { |player| puts "#{player.class}: #{player.point}" }
puts "==========================="

結論

結論から言わせてもらうと、特に明確な誤差は出なかった。時々によって、誰が勝利するかというのはあるけれども、だいたいプラスマイナス50の範囲で収まっているし、試行によって、どのプレイヤーが勝利するかということに関しては、まちまちであったと言える(逆に言うならば、どのプレイヤーも同等に勝利している)。これは多くの読者が想像していた通りの結果だろう。

この実験によって、ギャンブラーの錯覚が錯覚であることがわかったわけだけども、しかし、ギャンブルというのは統計だけではなく、良くわからない「流れ」とか「ツキ」とかを信じるほうが人間味も増すし、楽しみも増すことは、恐らく間違いない。もし、どの信念を採用しようとも、それが勝利とさほど関係ないとするならば、自分の好きな信念を持ってしてギャンブルするというのは、一つのやりかたであるのかもしれない。

また、直感と確率論的に齟齬が生じるモンティ・ホール問題というものもある。過去にそのことについて言及した記事があるので、興味のある方は参考にして欲しい。

参考文献

確率論入門 (ちくま学芸文庫)

確率論入門 (ちくま学芸文庫)

また、実際のゲームで統計的なアプローチを取っているものとして、下のような本があるらしい(自分は未読である)。

科学する麻雀 (講談社現代新書)

科学する麻雀 (講談社現代新書)