We suppose a set of symbols with their frequencies, given as a list of (S, F)
Tuples. E.g. (("a", 45), ("b", 13), ("c", 12), ("d", 16), ("e", 9), ("f", 5))
. Our objective is to construct a list of (S, C)
Tuples, where C is the Huffman code word for the symbol S.
scala> huffman(List(("a", 45), ("b", 13), ("c", 12), ("d", 16), ("e", 9), ("f", 5))) res0: List[String, String] = List((a,0), (b,101), (c,100), (d,111), (e,1101), (f,1100))
허프만 코드는 엘리먼트들의 비중치를 기준으로 오름차순 정렬한 후
0번째와 1번째를 묶는 과정을 더 묶을 수 없을 때까지 반복하고,
만들어진 트리 왼쪽에 0, 오른쪽에 1을 할당해 읽어 내려가면 된다.
예를 들어, 문제처럼
(A, 45), (B, 13), (C, 12), (D, 16), (E, 9), (F, 5) 가 주어졌을 때
1)
(F, 5) |
(E, 9) |
(C, 12) |
(B, 13) |
(D, 16) |
(A, 45) |
↓
((F, 5), (E, 9), 14) |
(C, 12) |
(B, 13) |
(D, 16) |
(A, 45) |
2)
(C, 12) | (B, 13) | ((F, 5), (E, 9), 14) | (D, 16) | (A, 45) |
↓
((C, 12), (B, 13), 25) |
((F, 5), (E, 9), 14) |
(D, 16) |
(A, 45) |
3)
((F, 5), (E, 9), 14) | (D, 16) | ((C, 12), (B, 13), 25) | (A, 45) |
↓
(((F, 5), (E, 9), 14), (D, 16), 30) |
((C, 12), (B, 13), 25) |
(A, 45) |
4)
((C, 12), (B, 13), 25) | (((F, 5), (E, 9), 14), (D, 16), 30) | (A, 45) |
↓
(((C, 12), (B, 13), 25), (((F, 5), (E, 9), 14), (D, 16), 30), 55) |
(A, 45) |
5)
(A, 45) | (((C, 12), (B, 13), 25), (((F, 5), (E, 9), 14), (D, 16), 30), 55) |
↓
((A, 45), (((C, 12), (B, 13), 25), (((F, 5), (E, 9), 14), (D, 16), 30), 55), 100) |
트리로 그려진 결과. 루트부터 빨간 숫자를 따라 읽으면 그 요소의 허프만 코드다.
위 알고리즘 그대로 스칼라 코드로 구현해 보았다.
노드는 왼쪽에 값 또는 다른 노드들을 가질 수 있기 때문에 Either를 사용했고
양쪽에 노드를 가지는 노드는 HuffmanNode라는 case class로 정의했다.
/**
*
* P50.
* 허프만 코드.
*
* @since 2017-01-06
* @author Park Hyo Jun
*
*/
class HuffmanCode[A] {
type Node = (Either[A, HuffmanNode], Int) // (값 혹은 다른 노드, 비중치)
case class HuffmanNode(left: Node, right: Node)
/**
* (값, 비중치) 리스트를 입력받아 (값, 허프만코드) 리스트를 만들어내는 메소드
*
* @param lst (값, 비중치) 리스트
* @return List[A, HuffmanCode)]
*/
def huffman(lst: List[(A, Int)]): List[(A, String)] = {
/**
* 허프만 코드에서 사용되는 트리를 만드는 메소드
*
* @param lst 노드 리스트
* @return 허프만 트리의 최상위 노드
*/
def huffmanTree(lst: List[Node]): Node = {
/**
* 노드 둘의 비중치를 더하는 메소드
*
* @param s1 허프만노드
* @param s2 허프만노드2
* @return 비중치
*/
def getWeight(s1: Node, s2: Node): Int = s1._2 + s2._2
/**
* 리스트에서 왼쪽 두개를 묶어 노드로 만드는 메소드
*
* @param lst 노드 리스트
* @return 왼쪽 두개가 노드로 묶인 노드 리스트
*/
@tailrec
def huffmanRecursive(lst: List[Node]): List[Node] = lst.sortWith(_._2 < _._2) match {
case Nil | _ :: Nil => lst
case h :: m :: t =>
val node = (Right(HuffmanNode(h, m)), getWeight(h, m))
huffmanRecursive(node :: t)
}
huffmanRecursive(lst).head // 루트노드
}
/**
* 트리를 이용해 허프만 코드를 만들어내는 메소드
*
* @param root 루트 노드
* @param fix 이전 할당된 허프만 코드
* @return (A, HuffmanCode) 리스트
*/
def huffmanCodes(root: Node, fix: String = ""): List[(A, String)] = root match {
case (Left(x), _) =>
List((x, fix)) // 값 노드인 경우 (값, 허프만 코드) 리턴
case (Right(x), _) => // 노드인 경우 좌우 재귀호출
val l = huffmanCodes(x.left, fix + "0")
val r = huffmanCodes(x.right, fix + "1")
l ::: r
}
huffmanCodes(huffmanTree(lst.map(x => (Left(x._1), x._2))))
}
}
자바에선 이런 경우 항상 자바 빈 클래스를 만든다.
자바와는 달리 스칼라는 튜플이 지원되니 튜플로 처리할 수 있었다.
하지만 튜플은 오히려 코드의 가독성이 떨어질 수 있다고 생각한다.
_1, 혹은 _2의 값이 무엇인지 바로 알 수 없으니까.
'Scala > Scala Ninety-Nine Problems' 카테고리의 다른 글
P92. Scala로 풀어본 Von Koch's conjection (0) | 2017.03.06 |
---|---|
P91. Scala로 풀어본 Knight's Tour(기사의 여행) (0) | 2017.03.01 |
P90. Scala로 풀어본 N-Queens Problem (0) | 2017.02.27 |
P27 Scala로 만드는 그룹(subsets) (0) | 2017.02.22 |
P26 Scala로 만드는 조합(Combination) (0) | 2017.02.08 |