エンジニアのソフトウェア的愛情

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

いかにして表計算の列名をつくるか・特別篇

Haskellでたわむれていると。 id:nobsun さんが実に軽快な解法をコメントに残してくださいました。

colns = concat cns where cns'@(_:cns) = [""] : [[c:ns | c <- ['A'..'Z'], ns <- nss] | nss <- cns' ]


Qiita では、さらにエレガントな解を展開されていますので、ぜひそのシンプルなコードを見てみてください。

[Char] と [String] から [String]

A〜Zの並びをかけ合わせるためにわたしが取った方法は、文字列のリストのリストに sequence を適用するというものでした。

Prelude> sequence [ [ "A", "B", "C" ], [ "A", "B", "C" ] ]
[["A","A"],["A","B"],["A","C"],["B","A"],["B","B"],["B","C"],["C","A"],["C","B"],["C","C"]]

(A〜Zの文字を使うと結果が大きくなってしまうため、A〜Cの文字で説明しています。以下同様)


ただし、これだと生成されるのは文字列のリストのリストになってしまいます。
そのためリストの各要素(文字列のリスト)を連結してやる必要がありました。

Prelude> map concat $ sequence [ [ "A", "B", "C" ], [ "A", "B", "C" ] ]
["AA","AB","AC","BA","BB","BC","CA","CB","CC"]


この方法をリスト内包表記で書き換えると、おおよそ次のようになります。

Prelude> [ concat [x, y] | x <- [ "A", "B", "C" ], y <- [ "A", "B", "C" ] ]
["AA","AB","AC","BA","BB","BC","CA","CB","CC"]


ここで String の連結のために concat [x, y] としていますが、xChar であれば x:y と書くことができます。
また x に文字を取り出すのであれば x <- [ "A", "B", "C" ]x <- ['A'..'C'] と書けばよいことになります。

Prelude> [ x:y | x <- ['A'..'C'], y <- [ "A", "B", "C" ] ]
["AA","AB","AC","BA","BB","BC","CA","CB","CC"]

自分の内包表記に自分を使う

y の値を取り出しているリスト [ "A", "B", "C" ] は、上の式の [ "A", "B", "C" ][""] に置き換えることで得ることができます。

 [ x:y | x <- ['A'..'C'], y <- [""] ]
["A","B","C"]


これを cns_0 と置くと、

Prelude> let cns_1 = [ x:y | x <- ['A'..'C'], y <- cns_0 ]
Prelude> cns_1
["A","B","C"]


先ほどの式を cns_2 とすると、

Prelude> let cns_2 = [ x:y | x <- ['A'..'C'], y <- cns_1 ]
Prelude> cns_2
["AA","AB","AC","BA","BB","BC","CA","CB","CC"]


となります。
cns_0 = [""] とし、f s = [ x:y | x <- ['A'..'C'], y <- s ] とすれば、

cns_0 = [""]
cns_1 = f cns_0 = ["A","B","C"]
cns_2 = f cns_1 = ["AA","AB","AC","BA","BB","BC","CA","CB","CC"]
...

となり、[ cns_1, cns_2 ] というリストを得たいばあい、f[ cns_0, cns_1 ] の要素を次々に適用して得られる値をリストにすればよいことになります。

Prelude> [ [ x:ns | x <- ['A'..'C'], ns <- nss] | nss <- [ cns_0, cns_1 ] ]
[["A","B","C"],["AA","AB","AC","BA","BB","BC","CA","CB","CC"]]


つまり [ cns_1, cns_2, ... ] というリストを得たいばあい、f[ cns_0, cns_1, ... ] の要素を次々に適用して得られる値をリストにすればよく、これはつまり得たいリストの先頭に cns_0 を付加したリストになります。

Prelude> let cns' = [""]:[ [ x:ns | x <- ['A'..'C'], ns <- nss] | nss <- cns' ]
Prelude> take 4 cns'
[[""],["A","B","C"],["AA","AB","AC","BA","BB","BC","CA","CB","CC"],["AAA","AAB","AAC","ABA","ABB","ABC","ACA","ACB","ACC","BAA","BAB","BAC","BBA","BBB","BBC","BCA","BCB","BCC","CAA","CAB","CAC","CBA","CBB","CBC","CCA","CCB","CCC"]]


このリストの要素を連結し先頭の空文字列を削除すれば、最終的に欲しかった文字列のリストが得られることになります。

Prelude> let colns = tail $ concat cns' where cns' = [""]:[ [ x:ns | x <- ['A'..'C'], ns <- nss] | nss <- cns' ]
Prelude> take 39 colns
["A","B","C","AA","AB","AC","BA","BB","BC","CA","CB","CC","AAA","AAB","AAC","ABA","ABB","ABC","ACA","ACB","ACC","BAA","BAB","BAC","BBA","BBB","BBC","BCA","BCB","BCC","CAA","CAB","CAC","CBA","CBB","CBC","CCA","CCB","CCC"]

いかにして先頭の要素を落とすか

tail 関数を使って。

Prelude> tail [1,2,3]
[2,3]


パタンマッチングを使って。

Prelude> let h:t = [1,2,3]
Prelude> h
1
Prelude> t
[2,3]


パタンマッチングを使って(元の値も使いたいとき)。

Prelude> let a@(h:t) = [1,2,3]
Prelude> h
1
Prelude> t
[2,3]
Prelude> a
[1,2,3]