The Lempel-Ziv-Welch (LZW) algorithm provides loss-less data compression.

You can read a complete description of it in the   Wikipedia article   on the subject.   It was patented, but it entered the public domain in 2004.




 
def compress(tc:String) = {
    //initial dictionary
    val startDict = (1 to 255).map(a=>(""+a.toChar,a)).toMap
    val (fullDict, result, remain) = tc.foldLeft ((startDict, List[Int](), "")) { 
      case ((dict,res,leftOver),nextChar) =>
        if (dict.contains(leftOver + nextChar)) // current substring already in dict
          (dict, res, leftOver+nextChar) 
        else if (dict.size < 4096) // add to dictionary
          (dict + ((leftOver+nextChar, dict.size+1)), dict(leftOver) :: res, ""+nextChar)
        else // dictionary is full
          (dict, dict(leftOver) :: res, ""+nextChar)
    }
    if (remain.isEmpty) result.reverse else (fullDict(remain) :: result).reverse
}
 
def decompress(ns: List[Int]): String = {
  val startDict = (1 to 255).map(a=>(a,""+a.toChar)).toMap
  val (_, result, _) =
    ns.foldLeft[(Map[Int, String], List[String], Option[(Int, String)])]((startDict, Nil, None)) {
    case ((dict, result, conjecture), n) => {
      dict.get(n) match {
        case Some(output) => {
          val (newDict, newCode) = conjecture match {
            case Some((code, prefix)) => ((dict + (code -> (prefix + output.head))), code + 1)
            case None => (dict, dict.size + 1)
          }
          (newDict, output :: result, Some(newCode -> output))
        }
        case None => {
          // conjecture being None would be an encoding error
          val (code, prefix) = conjecture.get
          val output = prefix + prefix.head
          (dict + (code -> output), output :: result, Some(code + 1 -> output))
        }
      }
    }
  }
  result.reverse.mkString("")
}
// test
val text = "TOBEORNOTTOBEORTOBEORNOT"
val compressed = compress(text)
println(compressed)
val result = decompress(compressed)
println(result)
 