commit 8ec883ff0193ed4b708dc4b781aedd7b8c0e3ef5 Author: Jordan Orelli Date: Mon Dec 15 11:30:20 2014 -0500 hi diff --git a/lexnum.go b/lexnum.go new file mode 100644 index 0000000..8df1d0f --- /dev/null +++ b/lexnum.go @@ -0,0 +1,149 @@ +package lexnum + +import ( + "fmt" + "strconv" +) + +type Encoder struct { + pos rune + neg rune +} + +func NewEncoder(pos rune, neg rune) *Encoder { + if pos < neg { + panic("positive lexnum rune must be of higher rank than negative lexnum rune") + } + return &Encoder{pos: pos, neg: neg} +} + +func (l Encoder) EncodeInt(i int) string { + if i == 0 { + return "0" + } + if i > 0 { + return l.encodePos(i) + } + return l.encodeNeg(i) +} + +func (l Encoder) encodePos(i int) string { + s := strconv.Itoa(i) + if len(s) == 1 { + return fmt.Sprintf("%c%s", l.pos, s) + } + return fmt.Sprintf("%c%s%s", l.pos, l.encodePos(len(s)), s) +} + +func (l Encoder) encodeNeg(i int) string { + if i < 0 { + i = -i + } + runes := []rune(strconv.Itoa(i)) + for i := range runes { + runes[i] = l.flip(runes[i]) + } + if len(runes) == 1 { + return fmt.Sprintf("%c%s", l.neg, string(runes)) + } + return fmt.Sprintf("%c%s%s", l.neg, l.encodeNeg(len(runes)), string(runes)) +} + +func (l Encoder) flip(r rune) rune { + switch r { + case '0': + return '9' + case '1': + return '8' + case '2': + return '7' + case '3': + return '6' + case '4': + return '5' + case '5': + return '4' + case '6': + return '3' + case '7': + return '2' + case '8': + return '1' + case '9': + return '0' + default: + panic(fmt.Sprintf("can't flip illegal rune %c", r)) + } +} + +func (l Encoder) flipInPlace(runes []rune) { + for i := range runes { + runes[i] = l.flip(runes[i]) + } +} + +func (l Encoder) isDigit(r rune) bool { + return r >= '0' && r <= '9' +} + +func (l Encoder) prefixCount(runes []rune) int { + i := 0 + for _, r := range runes { + if r == l.neg || r == l.pos { + i++ + } else { + break + } + } + return i +} + +func (l Encoder) DecodeInt(s string) (int, error) { + if s == "" { + return 0, fmt.Errorf("illegal Lexnum decode of empty string") + } + runes := []rune(s) + if len(runes) == 1 { + if runes[0] == '0' { + return 0, nil + } + return 0, fmt.Errorf("illegal Lexnum decode of non-zero unit string: %s", s) + } + switch runes[0] { + case l.neg: + return l.decodeNeg(runes) + case l.pos: + return l.decodePos(runes) + default: + return 0, fmt.Errorf("illegal Lexnum decode of string without %c or %c as initial rune: %s", l.neg, l.pos, s) + } +} + +func (l Encoder) decodePos(runes []rune) (int, error) { + return l._decodePos(runes, 1, l.prefixCount(runes)) +} + +func (l Encoder) _decodePos(runes []rune, size int, index int) (int, error) { + n, err := strconv.ParseInt(string(runes[index:index+size]), 10, 64) + if err != nil { + return 0, err + } + if index+size > len(runes) { + return 0, fmt.Errorf("illegal Lexnum decode of abnormally long string %s", string(runes)) + } + if index+size == len(runes) { + return int(n), nil + } + return l._decodePos(runes, int(n), index+size) + +} + +func (l Encoder) decodeNeg(runes []rune) (int, error) { + p := l.prefixCount(runes) + l.flipInPlace(runes[p:len(runes)]) + n, err := l._decodePos(runes, 1, p) + if err != nil { + return 0, err + } + return -n, nil +} diff --git a/lexnum_test.go b/lexnum_test.go new file mode 100644 index 0000000..1dff5ef --- /dev/null +++ b/lexnum_test.go @@ -0,0 +1,77 @@ +package lexnum + +import ( + "math/rand" + "sort" + "testing" + "time" +) + +var lexNumTests = []struct { + in int + out string +}{ + {0, "0"}, + {1, "x1"}, + {9, "x9"}, + {10, "xx210"}, + {99, "xx299"}, + {100, "xx3100"}, + {12345, "xx512345"}, + {123456789, "xx9123456789"}, + {1234567890, "xxx2101234567890"}, + {-1, "o8"}, + {-2, "o7"}, + {-9, "o0"}, + {-10, "oo789"}, + {-11, "oo788"}, + {-123, "oo6876"}, + {-123456789, "oo0876543210"}, + {-1234567890, "ooo7898765432109"}, +} + +func TestLexnum(t *testing.T) { + rand.Seed(time.Now().UnixNano()) + e := NewEncoder('x', 'o') + for _, test := range lexNumTests { + s := e.EncodeInt(test.in) + t.Logf("%d -> %s", test.in, test.out) + if s != test.out { + t.Errorf("encode input: %v, output: %v, expected output: %v", test.in, s, test.out) + } + n, err := e.DecodeInt(s) + if err != nil { + t.Errorf("decode error: %v", err) + continue + } + if n != test.in { + t.Errorf("decode input: %v, output: %v, expected output: %v", s, n, test.in) + } + } + + runsize := 8 + for runz := 0; runz < 4; runz += 1 { + nums, stringz := make([]int, runsize), make([]string, runsize) + for i := 0; i < runsize; i++ { + nums[i] = rand.Int() + if rand.Int()%2 == 0 { + nums[i] = -nums[i] + } + stringz[i] = e.EncodeInt(nums[i]) + } + sort.Strings(stringz) + sort.Ints(nums) + t.Logf("stringz sorted: %v", stringz) + t.Logf("nums sorted: %v", nums) + for i := 0; i < runsize; i++ { + n, err := e.DecodeInt(stringz[i]) + if err != nil { + t.Errorf("unable to decode our own input: %v", stringz[i]) + continue + } + if n != nums[i] { + t.Errorf("sorting is broken") + } + } + } +}