4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Haskellの配列周辺についてのメモ (続)

Last updated at Posted at 2014-01-15

IArrayのつかいかたメモ

自分のためにとりあえず変更不能配列の方だけ簡単に見ておく(変更可能配列も似たようなものではあるけれど)。

現在普通に使われる変更不能配列はIArray型クラスに属するデータ型で、特にArrayが基本的なもの。で、Arrayはインデックスと要素の値とで2つの型を取る(Array i e)。Array Int Stringみたいな感じ。

インデックスに使える型

インデックスに使える型は、他の言語のいわゆる「配列」と少し違っていて、Ix型クラスに属しているものであればなんでもいい。Intはもちろん、BoolやCharやWordなんかも使える。もちろん、Integerも使える。変わったところではOrdering(LT,EQ,GT)も使えるし、なんとIOMode(ReadMode,WriteMode,AppendMode,ReadWriteMode)なんかも使える。これはまあある意味では当たり前で、自分で定義した列挙型はderiving Ixすれば使える。タプルはクイントゥプルまでは既にData.IxでIxインスタンスになっているが、それ以上の項数のタプルはderivingで(タプルの要素はもちろんIxクラスのインスタンスでなければいけない)。キーにOrd型クラスのインスタンスを取れるMapとは違って、ArrayではDouble型の値をインデクッスにしたりはできない点は注意が必要かもしれない。

データ構築函数

データ構築用には幾つかの函数が用意されている。array函数の使い方はこう:

array (インデックスの最小値, インデックスの最大値) [(インデックス, 要素値)]
array ((0,0),(9,9)) [((x,y), 10*x+y)|x<-[0..9],y<-[0..9]]

要は構築したい配列のインデックスの範囲と、インデックスをキーとした連想リストを渡す、ということね。インデックス範囲内で要素値を渡していないものについてはundefinedになる(当然だが勝手に初期化したりはしてくれない)。注意しなければいけないのは、渡した連想リストの中に、範囲外のインデックスを含むものがあると、配列全体がundefinedになること。あと、連想リストの中でインデックスが重複していると、動作は処理系依存(GHCは後に来たもので上書きするがHaskell 98的にはそのインデックスに対応する要素値はらしい)。で、この段階でundefinedになるかどうかがチェックされる関係上、arrayで構築する(というか構築の際に内部でこのチェックを行っているsafeIndex函数を使っている関係で他の構築函数でも即値配列型UArrayでもなのだが) 配列のインデックスは正格評価になる (もちろんボックス値配列なら要素値は遅延評価)。

もうひとつ、注意しておくべき仕様:インデックスに関して、最小境界が最大境界を上回っている次元がひとつでもあると、そういう配列はそれ自体としては合法だが空になる(連想リストの中身は無視される)。空な配列に(!)演算子とかでインデックスを介してアクセスすると境界外エラーになるが、bounds函数は当の逆転した境界の対を普通にそのまま返してくる。

listArrayarrayと似たようなものだが、連想リストではなく普通の要素値リストを受け取って、最小インデックスから昇順に要素値を配列に突っ込んでいく。なので:

listArray (インデックスの最小値, インデックスの最大値) [昇順で投入されていく要素値]
listArray ((0,0),(9,9)) [0..99]

みたいな感じ。要素値リストが余れば残りは無視される。足りないと、足りなかった部分の要素値はundefinedになる。

場合によっては便利なのかもしれないが、なんでまたこんなものをわざわざライブラリに入れるのか、という感じもするのがaccumArrayで、これはarrayと似ているものの、連想リストのキーが重複してても差し支えない:

accumArray 畳み込み函数 デフォルト値 (最小インデックス,最大インデックス) [(インデックス,要素値)]
accumArray (+) 3 (0,3) [(0,0),(0,2),(1,1)]
=> array (0,3) [(0,3+0+2),(1,3+1),(2,3),(3,3)]
== array (0,3) [(0,5),(1,4),(2,3),(3,3)]

となる。基本的な動作はデフォルト値に対して畳み込み用の函数で連想リスト中の要素値を畳み込んだ値を各インデックスに対して突っ込んでいくというもの。連想リストにないインデックスについては、undefinedにせず、デフォルト値を突っ込む。

アクセス函数

配列なので要素にはインデックスを介してアクセスすることになる。いまさらだが、基本的には(!)演算子を使って:

配列 ! インデックス
arr = listArray (0,9) [9,8..0]
arr ! 3
=> 6

という感じ。indecesは単純に引数にとった配列の最小境界から最大境界までのインデックスのリストを返してくる。もちろん要素値は触らないので要素がundefinedでもインデックスは有効なものとして返ってくる。空の配列なら空リストを返す。elemsは同様にしてインデックス順に要素値を返してくる。assocsは両者を組み合わせたようなもので、連想リストを返してくる。

配列の更新

変更不能配列なので要素の更新は基本的には配列全体を新たに構築して返してくる(IArrayクラスでもDiffArrayは差分を内部で管理して巧くやってくれていることになっているけど)。使うのは配列更新演算子(//)で:

配列 // 連想リスト
listArray (0,2) [100,200,300] // [(1,150),(2,450)]
=> array (0,2) [(0,100), (1,150), (2,450)]

という感じ。ま、レコード型の値をフィールドラベルで更新してるのと同じだよな(配列全体はレコード型でインデックスがラベルと思えば)。

(contravariant)mapみたいなもの

後は要素値にだけ働きかけるmapみたいなamap函数と、インデックスに働きかけるixmap函数がある。amapは直観的に理解できる。ixmapは定義を見るのが早い:

ixmap (新たな最小インデックス,新たな最大インデックス) インデックス間の写像 元の配列
ixmap (l,u) f arr =
    array (l,u) [(i, arr ! f i) | i <- range (l,u)]

インデックス値iを函数fで写した結果jを使い元の配列でインデックスjに対応する要素を新たな配列のインデックスiに対応する要素として突っ込む。うーむ。これは確かに使うときには使いそうな気がする。インデックスに作用するものとしては確かに自然で納得のいく定義なんだが、普通のmapとかとは逆方向というかcontravariantなんだよな:

出発元のインデックスの集合          出発元の配列
          f     >-- ixmap -->      (ixmap f)
到着先のインデックスの集合          到着先の配列

という仕組みになってる。ixmap fの矢印方向がfと逆で、つまりcontravariant。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?