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クラスが役立つ。実際に以下のようなDNASeqtraitの機能をもつ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

comments powered by Disqus