Skip to content

Commit

Permalink
diamond: Add diamond exercise
Browse files Browse the repository at this point in the history
 * property-based tests
  • Loading branch information
ferhatelmas committed Oct 23, 2016
1 parent 8321391 commit 8319b38
Show file tree
Hide file tree
Showing 3 changed files with 279 additions and 0 deletions.
5 changes: 5 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@
"slug": "robot-name",
"topics": []
},
{
"difficulty": 1,
"slug": "diamond",
"topics": []
},
{
"difficulty": 1,
"slug": "react",
Expand Down
227 changes: 227 additions & 0 deletions exercises/diamond/diamond_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package diamond

import (
"math/rand"
"reflect"
"strings"
"testing"
"testing/quick"
"time"
)

var config = &quick.Config{Rand: rand.New(rand.NewSource(time.Now().UnixNano()))}

type correctChar byte

func (c correctChar) Generate(rand *rand.Rand, _ int) reflect.Value {
return reflect.ValueOf(correctChar('A' + rand.Intn('Z'-'A'+1)))
}

func checkCorrect(requirement func(byte, []string) bool, keepSeparator bool, t *testing.T) {
assertion := func(char correctChar) bool {
d, err := Gen(byte(char))
if err != nil {
return false
}
separator := strings.Split
if keepSeparator {
separator = strings.SplitAfter
}
rows := separator(d, "\n")
if len(rows) < 2 {
return false
}
return requirement(byte(char), rows[:len(rows)-1])
}
if err := quick.Check(assertion, config); err != nil {
t.Error(err)
}
}

func TestFirstRowContainsOneA(t *testing.T) {
requirement := func(char byte, rows []string) bool {
return len(rows) > 0 && strings.Count(rows[0], "A") == 1
}
checkCorrect(requirement, false, t)
}

func TestLastRowContainsOneA(t *testing.T) {
requirement := func(char byte, rows []string) bool {
return len(rows) > 0 && strings.Count(rows[len(rows)-1], "A") == 1
}
checkCorrect(requirement, false, t)
}

func TestAllRowsIdenticalLettersExceptFirstAndLast(t *testing.T) {
requirement := func(char byte, rows []string) bool {
for i, row := range rows {
r := strings.TrimSpace(row)
if r[0] != r[len(r)-1] {
return false
}
if len(r) < 2 && i != 0 && i != len(rows)-1 {
return false
}
}
return true
}
checkCorrect(requirement, false, t)
}

func TestAllRowsHaveSameTrailingSpaces(t *testing.T) {
requirement := func(char byte, rows []string) bool {
for _, row := range rows {
if len(row) == 0 {
return false
}
for i, j := 0, len(row)-1; i < j && row[i] == ' '; i, j = i+1, j-1 {
if row[j] != ' ' {
return false
}
}
}
return true
}
checkCorrect(requirement, false, t)
}

func TestDiamondIsHorizontallySymmetric(t *testing.T) {
requirement := func(char byte, rows []string) bool {
for _, row := range rows {
l := len(row)
for i := l/2 - 1; i >= 0; i-- {
if row[i] != row[l-1-i] {
return false
}
}
}
return true
}
checkCorrect(requirement, false, t)
}

func TestDiamondIsVerticallySymmetric(t *testing.T) {
requirement := func(char byte, rows []string) bool {
for i, j := 0, len(rows)-1; i < j; i, j = i+1, j-1 {
if rows[i] != rows[j] {
return false
}
}
return true
}
checkCorrect(requirement, true, t)
}

func TestDiamondIsSquare(t *testing.T) {
requirement := func(char byte, rows []string) bool {
if int(char-'A')*2+1 != len(rows) {
return false
}
for _, row := range rows {
if len(row) != len(rows) {
return false
}
}
return true
}
checkCorrect(requirement, false, t)
}

func TestDiamondHasItsShape(t *testing.T) {
requirement := func(char byte, rows []string) bool {
var n int
for i, row := range rows {
s := len(strings.TrimSpace(row))
if i > len(rows)/2 && n <= s {
return false
} else if i <= len(rows)/2 && n >= s {
return false
}
n = s
}
return true
}
checkCorrect(requirement, false, t)
}

func TestTopHalfHasAscendingLetters(t *testing.T) {
requirement := func(char byte, rows []string) bool {
var start byte = 'A' - 1
for i := 0; i <= len(rows)/2; i++ {
s := strings.TrimLeft(rows[i], " ")
if s == "" || s[0] <= start {
return false
}
start = s[0]
}
return true
}
checkCorrect(requirement, false, t)
}

func TestBottomHalfHasDescendingLetters(t *testing.T) {
requirement := func(char byte, rows []string) bool {
var start byte = 'A' - 1
for i := len(rows) - 1; i > len(rows)/2; i-- {
s := strings.TrimLeft(rows[i], " ")
if s == "" || s[0] <= start {
return false
}
start = s[0]
}
return true
}
checkCorrect(requirement, false, t)
}

func TestDiamondFourCornersAreTriangle(t *testing.T) {
requirement := func(char byte, rows []string) bool {
notSpace := func(r rune) bool { return r <= 'Z' && r >= 'A' }
var n int
for i, row := range rows {
s := strings.IndexFunc(row, notSpace)
e := len(row) - strings.LastIndexFunc(row, notSpace) - 1
if s != e {
return false
} else if i == 0 {
n = s
} else {
if i > len(rows)/2 && n >= s {
return false
} else if i <= len(rows)/2 && n <= s {
return false
}
n = s
}
}
return true
}
checkCorrect(requirement, false, t)
}

type wrongChar byte

func (c wrongChar) Generate(rand *rand.Rand, _ int) reflect.Value {
b := rand.Intn(256)
for ; b >= 'A' && b <= 'Z'; b = rand.Intn(256) {
}
return reflect.ValueOf(wrongChar(b))
}

func TestCharOutOfRangeShouldGiveError(t *testing.T) {
assertion := func(char wrongChar) bool {
_, err := Gen(byte(char))
return err != nil
}
if err := quick.Check(assertion, config); err != nil {
t.Error(err)
}
}

const targetTestVersion = 1

func TestTestVersion(t *testing.T) {
if testVersion != targetTestVersion {
t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
}
}
47 changes: 47 additions & 0 deletions exercises/diamond/example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package diamond

import (
"errors"
"strings"
)

const testVersion = 1

const startIndex = 'A'

// Gen builds a diamond
func Gen(char byte) (string, error) {
if char > 'Z' || char < 'A' {
return "", errors.New(string(char) + " isn't supported, only between (A, Z)")
}
return gen(char), nil
}

func gen(char byte) string {
var output []string
currentIndex := int(char - startIndex)
for i := 0; i <= currentIndex; i++ {
output = append(output, getLine(currentIndex, i))
}
for i := currentIndex - 1; i > -1; i-- {
output = append(output, getLine(currentIndex, i))
}
return strings.Join(output, "\n") + "\n"
}

func getLine(currentStart, current int) string {
diff := currentStart - current
return spaces(diff) + alphabets(current) + spaces(diff)
}

func alphabets(current int) string {
if current == 0 {
return "A"
}
c := current + startIndex
return string(c) + spaces(current*2-1) + string(c)
}

func spaces(n int) string {
return strings.Repeat(" ", n)
}

0 comments on commit 8319b38

Please sign in to comment.