【问题标题】:Fast huffman decode快速霍夫曼解码
【发布时间】:2016-04-18 19:18:50
【问题描述】:

我在 Scala 中编写了一个霍夫曼压缩/解压缩。解码小文件时需要几秒钟,但解码大文件时需要很长时间。关于如何加快此过程的任何建议?

def lookup(list:List[Int],list2:List[(Char,List[Int])]): Option[String]={
  val mutableListBuffer = scala.collection.mutable.ArrayBuffer(list2: _*)
  var option:Option[String]= None
  for(i <- 0 until mutableListBuffer.length){  
    if(mutableListBuffer(i)._2 == list){
      option =  Some(mutableListBuffer(i)._1.toString)         
    }
  }
  option
}

/*
*Decode function for matching groups of bits in a list to characters    in     the HCodeMap
*@returns String
*/
def decode(acc:Int,s:String,list:(List[Int],List[(Char,List[Int])])):String ={
  var s = ""
  var accum = 1
  val listp1 = scala.collection.mutable.ArrayBuffer(list._1: _*)
  val listp2 = scala.collection.mutable.ArrayBuffer(list._2: _*)
  var tupList = (listp1,listp2)

  while(!tupList._1.isEmpty){
    if(lookup(tupList._1.take(accum).toList,tupList._2.toList).isDefined){
      println(accum)
      s = s ++ lookup(tupList._1.take(accum).toList,tupList._2.toList).getOrElse("a")
      Log.d("MyTAG", "de" + s)  
      tupList._1.remove(0,accum)
      accum = accum - accum + 1
    }
    else{
      accum =  accum + 1
    }        
  }
  s
}

代码在 Android 设备上执行,因此使用递归不是一个有效的选项,使用不可变列表也不是。任何建议将不胜感激。再次感谢。

【问题讨论】:

  • 如果您能解释一下参数是什么,并更详细地命名它们,那将会很有帮助。此外,还有一些奇怪之处 - acc 是第一个解码的 arg,但未使用。真的很难看出代码在做什么。
  • 你说得对, acc 是多余的。第二个是要解码的字符串。第三个是一对包含要解码的位列表以及包含字符及其位表示的代码映射。我以更递归的方式拥有它,但不得不重写它,因为由于递归,android 正在报告堆栈溢出错误。
  • 你为什么使用tupList?直接使用底层的listp1listp2 不会做任何事情,只会更清楚。我认为您应该先清理这段代码,然后再担心优化它,...这样可以更清楚地看到可以改进的地方。
  • 除非我自己明确创建 tupList,否则我会收到“递归值列表需要类型”错误。
  • 递归值列表需要类型 val list = scala.collection.mutable.ArrayBuffer(list: _*)。这就是创建 tupList 变量的原因。我最初使用递归,但在转移到 Android 时出现堆栈溢出错误。

标签: android scala huffman-code


【解决方案1】:

如果你想让它快一点,那你的出发点就错了。您应该直接使用位流,而不是每个整数包含一位的整数列表。您在某处浪费时间将传入的位转换为整数列表,然后浪费大量时间解码该列表。

制作快速霍夫曼解码器的一种方法是构建允许直接索引查找的解码表。选择一些比特,n,这将是一个统一代码的长度。例如。如果你有 256 个符号,让我们从 n 开始作为 8 个。 (您可以稍后再弄乱 n 以优化速度。)

现在构建一个包含 256 个条目的表,该表由接下来的 8 位输入索引。每个条目表示a) 下一个代码为8 位或更少,或b) 下一个代码的长度为9 位或更多。对于 a),该表告诉您代码中的位数及其解码为的符号。在这种情况下,您从流中删除那么多位并发出符号。

对于 b) 表指向下一个表以用后续位和多少位进行索引。然后,您从流中丢弃八位并索引子表,该子表也将指示 a) 或 b)。虽然可能两级表是最佳的,但子表将始终指示 a),完成解码并发出符号。在这种情况下,子表的大小(即索引中的位数)是最长代码的长度减去八,它具有索引主表的八位前缀。

这些表构建起来很简单,构建一次就可以多次使用,因此构建时间非常值得。表中的条目经常重复多次。例如。一个四位代码将在基本八位表中重复 16 次。

您将在位缓冲区(一个整数)上使用位操作,根据需要从流中提取字节,以保持其加载足够的位以形成下一个索引。简单的位移会在使用时将位向下移动,而输入字节的位移会在或将它们放入位缓冲区之前完成。

一旦一切正常,您可以改变 n 并使用代表性输入为解码器计时,以找到 n 的最佳值。

【讨论】:

    【解决方案2】:

    这可能属于 codereview,但我认为这里的代码与您的代码直接等效,无需任何转换为​​ ArrayBuffers(这不是必需的)。列表操作是相当有效的,只是采取和删除,以及列表遍历,所以我认为你在错误的方向上指责不可变列表。

    def lookup(list: List[Int], list2: List[(Char, List[Int])]): Option[String] = 
     list2.find(_._2 == list).map(_._1.toString)
    
    /*
    *Decode function for matching groups of bits in a list to characters    in     the HCodeMap
    *@returns String
    */
    
     def decode(s: String, list: (List[Int], List[(Char, List[Int])])): String = {
    
      var s = ""
      var accum = 1
      var listp1 = list._1
      val listp2 = list._2
    
      while (!listp1.isEmpty) {
    
        lookup(listp1.take(accum), listp2) match {
          case Some(m) =>
            println(accum)
            s = s ++ m
            Log.d("MyTAG", "de" + s)
            listp1 = listp1.drop(accum)
            accum = 1
          case None =>
            accum = accum + 1
        }
      }
      s
    }
    

    【讨论】:

    • 感谢您的帮助,但正如您所说,代码审查是有序的,因为这可能比我的代码快 30 秒。它的质量比我的要高得多,所以我会给你投票作为正确答案,感谢你告诉我不可变列表不是问题,只是我草率的代码:D
    • 使s 成为 StringBuilder 可能会有很大帮助
    • 我为@TheArchetypalPaul 的努力鼓掌。做得好。翻看decode的签名和基本算法,不禁想通过foldLeft做不到。一个相当复杂的foldLeft 是肯定的,但并非无法管理。
    • 我考虑过。它似乎比它的价值更复杂。如果我觉得无聊,我可能会再去一次:)
    猜你喜欢
    • 1970-01-01
    • 2011-01-15
    • 1970-01-01
    • 2014-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多