[英]How can I speed up my Aho-Corasick Algorithm?
我正在嘗試解決HackerRank上的問題; “確定DNA健康”。 在討論了一些討論后,我決定將Aho-Corasick算法作為最佳選擇。 問題涉及在字符串中搜索具有相關值的各種序列。 任務是從給定列表中獲取這些序列值對的一個子部分,並找到與輸入字符串關聯的值。 這意味着要完成48850次,並列出100000個序列值對。 我已經實現了該算法,盡管它比我第一次嘗試快很多,但仍然不夠快才能通過此測試用例。 這是我的實現:
構建特里:
def createValueTrie(gs: Array[(String, Int)]): TrieNodeWithVal = {
def recurse(genes: Array[(String, Int)]): Map[Char, TrieNodeWithVal] = {
genes
.groupBy(_._1.head)
.map(x => (x._1, x._2.map(y => (y._1.tail, y._2))))
.map{
case (c, arr: Array[(String, Int)]) => {
val value = arr.filter(_._1.length == 0).foldLeft(0)(_ + _._2)
val filtered = arr.filter(_._1.length > 0)
val recursed = recurse(filtered)
(c, new TrieNodeWithVal(arr.exists(_._1.length == 0), recursed, value))
}
}
}
new TrieNodeWithVal(false, recurse(gs), 0)
}
通過特里搜索:
def findValueMatches(trie: TrieNodeWithVal, sequence: String): Iterator[(String, Long)] = {
sequence.scanRight("")(_ + _).dropRight(1).iterator.flatMap(s => {
Iterator.iterate[(Iterator[Char], Option[TrieNodeWithVal])]((s.iterator, Some(trie))) {
case (it: Iterator[Char], Some(node)) => if (it.hasNext) (it, node(it.next())) else (it, None)
case (it: Iterator[Char], None) => (it, None)
}.takeWhile {
case (_, Some(_)) => true
case _ => false
}.map {
case (_, Some(node)) => node
}.zipWithIndex.withFilter {
case (node, _) => node isWord
}.map {
case (node, i) => (s.slice(0, i), node.value)
}
})
}
特里節點類:
class TrieNode(isAWord: Boolean, childs: Map[Char, TrieNode]) {
val isWord = isAWord
val children: Map[Char, TrieNode] = childs
def apply(c: Char): Option[TrieNode] = children.get(c)
override def toString(): String = "(" + children.map(x => (if (x._2.isWord) x._1.toUpper else x._1) + ": " + x._2.toString()).mkString(", ") + ")"
}
class TrieNodeWithVal(isAWord: Boolean, childs: Map[Char, TrieNodeWithVal], valu: Long) extends TrieNode(isAWord, childs) {
val value = valu
override val children: Map[Char, TrieNodeWithVal] = childs
override def toString(): String = "(" + children.map(x => (if (x._2.isWord) x._1.toUpper + "[" + x._2.value + "]" else x._1) + ": " + x._2.toString()).mkString(", ") + ")"
override def apply(c: Char): Option[TrieNodeWithVal] = children.get(c)
}
我知道對於失敗案例,這里可以做更多的事情,但是討論中的幾個人說,這樣做會比較慢,因為需要為每個查詢重建特里樹。 我應該使用一些更有效的集合來解決此類問題嗎? 如何在保持純功能性樣式的同時加快速度?
有各種各樣的變化,有些可能會影響性能,而另一些僅僅是表面上的。
在recurse
您可以結合使用兩個map
調用,並使用partition
來減少測試數組的次數:
def recurse(genes: Array[(String, Int)]): Map[Char, TrieNodeWithVal] = {
genes
.groupBy(_._1.head)
.map { x =>
val c = x._1
val arr = x._2.map(y => (y._1.tail, y._2))
val (filtered, nonFiltered) = arr.partition(_._1.nonEmpty)
val value = nonFiltered.foldLeft(0)(_ + _._2)
val recursed = recurse(filtered)
(c, new TrieNodeWithVal(nonFiltered.nonEmpty, recursed, value))
}
}
您可以通過在case
語句上使用條件並組合一些操作來簡化findValueMatches
:
def findValueMatches(trie: TrieNodeWithVal, sequence: String): Iterator[(String, Long)] = {
sequence.scanRight("")(_ + _).dropRight(1).iterator.flatMap(s => {
Iterator.iterate[(Iterator[Char], Option[TrieNodeWithVal])]((s.iterator, Some(trie))) {
case (it: Iterator[Char], Some(node)) if it.hasNext => (it, node(it.next()))
case (it: Iterator[Char], _) => (it, None)
}.takeWhile {
_._2.nonEmpty
}.zipWithIndex.collect {
case ((_, Some(node)), i) if node.isWord =>
(s.slice(0, i), node.value)
}
})
}
最后,您可以使用val
參數簡化類
class TrieNode(val isWord: Boolean, val children: Map[Char, TrieNode]) {
def apply(c: Char): Option[TrieNode] = children.get(c)
override def toString(): String = "(" + children.map(x => (if (x._2.isWord) x._1.toUpper else x._1) + ": " + x._2.toString()).mkString(", ") + ")"
}
class TrieNodeWithVal(isAWord: Boolean, childs: Map[Char, TrieNodeWithVal], val value: Long) extends TrieNode(isAWord, childs) {
override val children: Map[Char, TrieNodeWithVal] = childs
override def toString(): String = "(" + children.map(x => (if (x._2.isWord) x._1.toUpper + "[" + x._2.value + "]" else x._1) + ": " + x._2.toString()).mkString(", ") + ")"
override def apply(c: Char): Option[TrieNodeWithVal] = children.get(c)
}
如果我無意中更改了算法,則全部已編譯但未經測試,因此深表歉意。
我沒有加快算法的速度,但是我發現,如果我給每個節點一個從原始序列和值列表中得到的索引,那么不必每次都重新構建嘗試,我只能使用一個,並且僅計算具有范圍內的索引。 這將時間從8分鍾縮短到11秒!
您可以嘗試使用三進制嘗試該算法。 我的php實現: https : //github.com/Tetramatrix/phpahocorasick 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.