DNAの塩基を表すクラスを作成したい。
// companion object
object DNA {
// objectで定義するとsingletonになるのでメモリの節約に
object A extends DNA(0)
object C extends DNA(1)
object G extends DNA(2)
object T extends DNA(3)
object N extends DNA(4) // NはA, C, G, Tのどれかを表す
// DNAの文字列をすべて並べる。
val values = Array(A, C, G, T, N)
// 用途によって別の集合を定義することもできる。N以外の塩基
val exceptN = Array(A, C, G, T)
private val codeTable = Array(A, C, G, T, N, N, N, N)
def complement(code:Int) : DNA = codeTable((~code & 0x03) | (code & 0x04))
}
// DNAクラス
sealed abstrat class DNA(val code:Int) {
// object名(最後に$マークが付くので除く)をenum名として使う
val name = this.getClass.getSimpleName.replaceAll("""\$""", "")
override def toString = name
// DNAクラスには自由にメソッドを定義できる
def complement = DNA.complement(code)
}
このように定義すると、パターンマッチが問題なく使えるし、complementなど機能を充実させることもできる。
クラス定義にsealed
を付けると、DNAを拡張したクラスは同一ファイル内でしか定義できなくなる。さらにabstract
クラスにすると、DNAを拡張したクラスはA, C, G, T, N以外にないことも保証できるので、match文をexhaustive(すべての場合を網羅する状態)にできる。
import DNA._
val l = G
l match {
case A => ...
case C => ...
case G => ...
case T => ...
case N => ...
}
DNA配列を表すDNASeq
などを定義する際に上記のDNA
クラスが役立つ。実際に以下のようなDNASeq
traitの機能をもつDNAの配列のクラスを作成してみよう。
trait DNASeq {
def length : Int
def apply(index:Int) : DNA
def reverseComplement : DNASeq
}
// DNA (A, C, G, T)を2bitで表現したDNASeqの実装
class DNASeq2bit(seq:Array[Long], val length:Int) extends DNASeq {
....
}
より詳細な実装は以下のコードを参考にしてください。
塩基配列をビット列で表現すると、配列中にAが何個含まれるかなどの計算が高速に行なえるようになり(popCount)、FM-Indexによるアラインメントの計算等が高速化できます。
IUPACコードなども同様に実装できます。IUPAC.scala