Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Go] Challenge 12 (Unreviewed) #437

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions challenge_12/go/erocs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Challenge 12: Compression and Decompression

## 1. Solution Description

This challenge is all about run length encoding (RLE).

Encode() counts consecutive runs of a single character. If multiple are encountered it adds the character, a pound sign, and the number of occurrences to the output buffer. Single characters are directly transfered to the output buffer.

Decode() has to reverse this but in doing so has to handle more error conditions due to malformed input. Otherwise it detects if a pound sign immediately follows the current character and if so expands that character out the appropriate count into the output buffer.

## 2. Running Tests

In bash in this directory:

export GOPATH=`pwd`
go test c12
129 changes: 129 additions & 0 deletions challenge_12/go/erocs/src/c12/c12.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package c12

import (
"fmt"
"strconv"
"strings"
)

const MaxRunLength = 0xFFFF

func isAToZ(ch rune) bool {
return ('a' <= ch && 'z' >= ch) || ('A' <= ch && 'Z' >= ch)
}

// Run Length Encodes the given string, compressing runs of single characters
// into the form c#1, where c is the character and 1 is the appropriate count.
// This only supports encoding strings which contain the character set
// [a-zA-Z]. No other characters are allowed. Character runs exceeding a 65535
// length will be split into 65535 length encodings.
func Encode(s string) (string, error) {
ss := []string{}
c := 0
cch := '\x00'
for _, ch := range s {
if !isAToZ(ch) {
return "", fmt.Errorf("Non-alphabetic character encountered: %c", ch)
}
large_count := false
if cch == ch {
c++
if c < MaxRunLength {
continue
}
large_count = true
}
if c > 1 {
ss = append(ss, fmt.Sprintf("%c#%d", cch, c))
} else if c == 1 {
ss = append(ss, string(cch))
}
cch = ch
c = 1
if large_count {
c = 0
}
}
if c > 1 {
ss = append(ss, fmt.Sprintf("%c#%d", cch, c))
} else if c == 1 {
ss = append(ss, string(cch))
}
return strings.Join(ss, ""), nil
}

func is0To9(ch rune) bool {
return '0' <= ch && '9' >= ch
}

// Returns parsed count, new index, error value
func parseRepeatCount(rs []rune, i int) (idx, count int, err error) {
idx = i
mark := idx
for idx < len(rs) && is0To9(rs[idx]) {
idx++
}
if mark == idx {
if idx >= len(rs) {
err = fmt.Errorf("No repeat count specified")
} else {
err = fmt.Errorf("Non-digit character encountered for repeat count: %c", rs[idx])
}
return
}
c64, err := strconv.ParseInt(string(rs[mark:idx]), 10, 64)
if err != nil {
return
}
if c64 > MaxRunLength {
err = fmt.Errorf("Excessive repeat count: %v", c64)
return
}
count = int(c64)
return
}

// rs is in/out
func appendRuneNTimes(rs *[]rune, ch rune, n int) {
rout := *rs
if cap(rout) < len(rout)+n {
// Make the length identical to the current length to leverage copy().
tmp := make([]rune, len(rout), cap(rout)*2+n)
copy(tmp, rout)
rout = tmp
}
for n > 0 {
rout = append(rout, ch)
n--
}
*rs = rout
}

// Expands the given Run Length Encoded string to its original value.
// Compressed runs of a single character are represented in the form c#1, where
// c is the character and 1 is the appropriate count. This only supports
// decoding strings which contain the character set [a-zA-Z]. No other
// characters are allowed. A maximum count of 65535 for a given character is
// allowed.
func Decode(s string) (string, error) {
rs := []rune(s)
rout := make([]rune, 0, len(rs)*2)
for i := 0; i < len(rs); i++ {
ch := rs[i]
if !isAToZ(ch) {
return "", fmt.Errorf("Non-alphabetic character encountered: %c", ch)
}
if i+1 >= len(rs) || rs[i+1] != '#' {
rout = append(rout, ch)
continue
}
iTmp, c, err := parseRepeatCount(rs, i+2)
if err != nil {
return "", err
}
// Rewind one character to counter the numeric character search.
i = iTmp - 1
appendRuneNTimes(&rout, ch, c)
}
return string(rout), nil
}
117 changes: 117 additions & 0 deletions challenge_12/go/erocs/src/c12/c12_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package c12

import "testing"

func CheckGood(t *testing.T, raw, expect string) {
s, err := Encode(raw)
if err != nil {
t.Error("Error encoding:", err)
} else if s != expect {
t.Error("Bad encoding:", s, "Expected:", expect)
}
s, err = Decode(s)
if err != nil {
t.Error("Error decoding:", err)
} else if s != raw {
t.Error("Bad decoding:", s, "Expected:", raw)
}
}

func TestEmpty(t *testing.T) {
CheckGood(t, "", "")
}

func TestSingleChar(t *testing.T) {
CheckGood(t, "a", "a")
}

func TestMultiSameChar(t *testing.T) {
CheckGood(t, "aaaaaaaaaaaa", "a#12")
}

func TestMultipleSingles(t *testing.T) {
CheckGood(t, "abcdefgh", "abcdefgh")
}

func TestMultipleMultiples(t *testing.T) {
CheckGood(t, "aaabbbaaacccaaaaaaaaaaaadddddddddddddddddd", "a#3b#3a#3c#3a#12d#18")
}

func TestMixed1(t *testing.T) {
CheckGood(t, "aaabaaacaaadaaaeaaa", "a#3ba#3ca#3da#3ea#3")
}

func TestMixed2(t *testing.T) {
CheckGood(t, "abbbacccaddda", "ab#3ac#3ad#3a")
}

func TestMixed3(t *testing.T) {
CheckGood(t, "aaab", "a#3b")
}

func TestMixed4(t *testing.T) {
CheckGood(t, "abbb", "ab#3")
}

func TestUppercase(t *testing.T) {
CheckGood(t, "AaBBbbcCddDD", "AaB#2b#2cCd#2D#2")
}

func TestReallyLongRune(t *testing.T) {
ll := 0x10011
rs := make([]rune, 0, ll)
for i := 0; i < ll; i++ {
rs = append(rs, 'a')
}
s := string(rs)
CheckGood(t, s, "a#65535a#18")
}

func TestBadChar1(t *testing.T) {
_, err := Encode("@")
if err == nil {
t.Fail()
}
}

func TestBadChar2(t *testing.T) {
_, err := Encode("[")
if err == nil {
t.Fail()
}
}

func TestBadChar3(t *testing.T) {
_, err := Encode("{")
if err == nil {
t.Fail()
}
}

func TestBadDecoding1(t *testing.T) {
_, err := Decode("a#")
if err == nil {
t.Fail()
}
}

func TestBadDecoding2(t *testing.T) {
_, err := Decode("a#a")
if err == nil {
t.Fail()
}
}

func TestBadDecoding3(t *testing.T) {
_, err := Decode("a#99999")
if err == nil {
t.Fail()
}
}

func TestBadDecoding4(t *testing.T) {
_, err := Decode("a#9999999999999999999999999999999999999999999")
if err == nil {
t.Fail()
}
}