diff --git a/.gitignore b/.gitignore index 6bb54e93e..d07759563 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out coverage.txt +*.vscode diff --git a/00_hello_world/juliaogris/main.go b/00_hello_world/juliaogris/main.go old mode 100644 new mode 100755 diff --git a/00_hello_world/juliaogris/main_test.go b/00_hello_world/juliaogris/main_test.go old mode 100644 new mode 100755 diff --git a/03_letters/hsy3418/main.go b/03_letters/hsy3418/main.go deleted file mode 100644 index d4629c710..000000000 --- a/03_letters/hsy3418/main.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "fmt" - "io" - "os" - "sort" - "strings" -) - -var out io.Writer = os.Stdout - -//letters is a function returns a mapping of each letter to its frequency. -func letters(s string) map[rune]int { - m := make(map[rune]int) - for _, r := range s { - m[r]++ - } - return m -} - -//sorLetters is a function returns a sorted slice of strings with elements -func sortLetters(m map[rune]int) []string { - sortSlices := make([]string, 0, len(m)) - for key, val := range m { - sortSlices = append(sortSlices, fmt.Sprintf("%c:%d", key, val)) - } - sort.Strings(sortSlices) - return sortSlices -} - -func main() { - fmt.Fprintln(out, strings.Join(sortLetters(letters("aba")), "\n")) -} diff --git a/03_letters/hsy3418/main_test.go b/03_letters/hsy3418/main_test.go deleted file mode 100644 index 0c054af08..000000000 --- a/03_letters/hsy3418/main_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "bytes" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" -) - -var letterTests = []struct { - input string - want map[rune]int -}{ - {input: "abcdefghijk", - want: map[rune]int{'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1, 'f': 1, 'g': 1, 'h': 1, 'i': 1, 'j': 1, 'k': 1}}, - {input: "894431kdk", - want: map[rune]int{'8': 1, '9': 1, '4': 2, '3': 1, '1': 1, 'k': 2, 'd': 1}}, - {input: "好好学习天天向上", - want: map[rune]int{'好': 2, '学': 1, '习': 1, '天': 2, '向': 1, '上': 1}}, - {input: "🏦🔫🗯💰😬🚓🚓🚓🚓", - want: map[rune]int{'🏦': 1, '🔫': 1, '🗯': 1, '💰': 1, '😬': 1, '🚓': 4}}, -} - -var sortletterTests = []struct { - input map[rune]int - want []string -}{ - {input: map[rune]int{'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1, 'f': 1, 'g': 1, 'h': 1, 'i': 1, 'j': 1, 'k': 1}, - want: []string{"a:1", "b:1", "c:1", "d:1", "e:1", "f:1", "g:1", "h:1", "i:1", "j:1", "k:1"}}, - {input: map[rune]int{'8': 1, '9': 1, '4': 2, '3': 1, '1': 1, 'k': 2, 'd': 1}, - want: []string{"1:1", "3:1", "4:2", "8:1", "9:1", "d:1", "k:2"}}, - {input: map[rune]int{'好': 2, '学': 1, '习': 1, '天': 2, '向': 1, '上': 1}, - want: []string{"上:1", "习:1", "向:1", "天:2", "好:2", "学:1"}}, - {input: map[rune]int{'🏦': 1, '🔫': 1, '🗯': 1, '💰': 1, '😬': 1, '🚓': 4}, - want: []string{"🏦:1", "💰:1", "🔫:1", "🗯:1", "😬:1", "🚓:4"}}, -} - -func TestLetters(t *testing.T) { - for _, test := range letterTests { - actual := letters(test.input) - assert.Equal(t, test.want, actual) - } -} - -func TestSortLetters(t *testing.T) { - for _, test := range sortletterTests { - actual := sortLetters(test.input) - assert.Equal(t, test.want, actual) - } -} - -func TestMainOutput(t *testing.T) { - var buf bytes.Buffer - out = &buf - - main() - - expected := strconv.Quote("a:2\nb:1\n") - actual := strconv.Quote(buf.String()) - - if expected != actual { - t.Errorf("Unexpected output in main()\nexpected: %q\nactual: %q", expected, actual) - } -} diff --git a/06_puppy/hsy3418/main.go b/06_puppy/hsy3418/main.go new file mode 100644 index 000000000..dfa6d274e --- /dev/null +++ b/06_puppy/hsy3418/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "io" + "os" +) + +var out io.Writer = os.Stdout + +func main() { + p1 := Puppy{Breed: "Poodle", Colour: "White", Value: 1280.5} + p2 := Puppy{Breed: "Poodle", Colour: "Grey", Value: 1340.5} + mapStore := NewMapStore() + syncStore := NewSyncStore() + _ = mapStore.CreatePuppy(p1) + _ = syncStore.CreatePuppy(p2) + puppy, _ := mapStore.ReadPuppy(0) + puppy2, _ := syncStore.ReadPuppy(0) + fmt.Fprintf(out, "Puppy ID %d is %v", puppy.ID, puppy.Value) + fmt.Fprintf(out, "Puppy ID %d is %v", puppy2.ID, puppy2.Value) +} diff --git a/06_puppy/hsy3418/main_test.go b/06_puppy/hsy3418/main_test.go new file mode 100644 index 000000000..5f7cdd9b1 --- /dev/null +++ b/06_puppy/hsy3418/main_test.go @@ -0,0 +1,18 @@ +package main + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMain(t *testing.T) { + assert := assert.New(t) + var buf bytes.Buffer + out = &buf + main() + expected := "Puppy ID 0 is 1280.5Puppy ID 0 is 1340.5" + actual := buf.String() + assert.Equal(expected, actual) +} diff --git a/06_puppy/hsy3418/mapStore.go b/06_puppy/hsy3418/mapStore.go new file mode 100644 index 000000000..3cac6a70e --- /dev/null +++ b/06_puppy/hsy3418/mapStore.go @@ -0,0 +1,50 @@ +package main + +import "fmt" + +// MapStore is a implementation of storer for the storage of puppies +type MapStore struct { + puppies map[int32]Puppy + nextID int32 +} + +// NewMapStore creates a new mapStore +func NewMapStore() *MapStore { + return &MapStore{ + puppies: map[int32]Puppy{}, + } +} + +// CreatePuppy adds a nuw puppy to the puppy store and returns the id for the puppy +func (m *MapStore) CreatePuppy(puppy Puppy) int32 { + puppy.ID = m.nextID + m.nextID++ + m.puppies[puppy.ID] = puppy + return puppy.ID +} + +// ReadPuppy retrieves the puppy for a given id from puppies store +func (m *MapStore) ReadPuppy(id int32) (Puppy, error) { + if _, ok := m.puppies[id]; !ok { + return Puppy{}, fmt.Errorf("puppy with %d ID does not exist", id) + } + return m.puppies[id], nil +} + +//UpdatePuppy updates the puppy for the given id +func (m *MapStore) UpdatePuppy(puppy Puppy) error { + if _, ok := m.puppies[puppy.ID]; !ok { + return fmt.Errorf("puppy with %d ID does not exist", puppy.ID) + } + m.puppies[puppy.ID] = puppy + return nil +} + +//DeletePuppy delete the puppy for the given id from puppies store +func (m *MapStore) DeletePuppy(id int32) error { + if _, ok := m.puppies[id]; !ok { + return fmt.Errorf("puppy with %d ID does not exist", id) + } + delete(m.puppies, id) + return nil +} diff --git a/06_puppy/hsy3418/store_test.go b/06_puppy/hsy3418/store_test.go new file mode 100644 index 000000000..fcf937ea6 --- /dev/null +++ b/06_puppy/hsy3418/store_test.go @@ -0,0 +1,116 @@ +package main + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +func (suite *storerTestSuite) SetupTest() { + suite.store = suite.makeStore() + suite.nonExistPuppy = Puppy{ID: 123456, Breed: "Poodle", Colour: "White", Value: 1000.5} + suite.toBeCreatedPuppy = Puppy{Breed: "Poodle", Colour: "White", Value: 1000.5} + suite.existsPuppy = Puppy{Breed: "Poodle", Colour: "White", Value: 1280.5} + suite.toBeUpdatedPuppy = Puppy{ID: suite.existsPuppy.ID, Breed: "Poodle", Colour: "White", Value: 2000} + suite.store.CreatePuppy(suite.existsPuppy) +} + +type storerTestSuite struct { + suite.Suite + store Storer + makeStore func() Storer + toBeCreatedPuppy Puppy + existsPuppy Puppy + toBeUpdatedPuppy Puppy + nonExistPuppy Puppy +} + +func (suite *storerTestSuite) TestCreatePuppy() { + assert := assert.New(suite.T()) + testCases := []struct { + title string + input Puppy + }{ + {"Create new puppy", suite.toBeCreatedPuppy}, + } + for _, tc := range testCases { + tc := tc + suite.T().Run(tc.title, func(t *testing.T) { + id := suite.store.CreatePuppy(tc.input) + assert.True(id > 0) + }) + } +} + +func (suite *storerTestSuite) TestUpdatePuppy() { + assert := assert.New(suite.T()) + testCases := []struct { + title string + inputPuppy Puppy + expectedError error + }{ + {"Update puppy successfully", suite.toBeUpdatedPuppy, nil}, + {"Update non-existing puppy", suite.nonExistPuppy, + fmt.Errorf("puppy with %d ID does not exist", suite.nonExistPuppy.ID)}, + } + for _, tc := range testCases { + tc := tc + suite.T().Run(tc.title, func(t *testing.T) { + err := suite.store.UpdatePuppy(tc.inputPuppy) + assert.Equal(tc.expectedError, err) + }) + } +} + +func (suite *storerTestSuite) TestReadPuppy() { + assert := assert.New(suite.T()) + testCases := []struct { + title string + input int32 + expected Puppy + expectedError error + }{ + {"Read puppy successfully", suite.existsPuppy.ID, suite.existsPuppy, nil}, + {"Read non-existing puppy", suite.nonExistPuppy.ID, Puppy{}, + fmt.Errorf("puppy with %d ID does not exist", suite.nonExistPuppy.ID)}, + } + for _, tc := range testCases { + tc := tc + suite.T().Run(tc.title, func(t *testing.T) { + readPuppy, err := suite.store.ReadPuppy(tc.input) + assert.Equal(tc.expected, readPuppy) + assert.Equal(tc.expectedError, err) + }) + } +} + +func (suite *storerTestSuite) TestDeletePuppy() { + assert := assert.New(suite.T()) + testCases := []struct { + title string + input int32 + expectedError error + }{ + {"Delete puppy successfully", suite.existsPuppy.ID, nil}, + {"Delete non-existing puppy", suite.nonExistPuppy.ID, + fmt.Errorf("puppy with %d ID does not exist", suite.nonExistPuppy.ID)}, + } + for _, tc := range testCases { + tc := tc + suite.T().Run(tc.title, func(t *testing.T) { + err := suite.store.DeletePuppy(tc.input) + assert.Equal(tc.expectedError, err) + }) + } +} + +func TestStorers(t *testing.T) { + suite.Run(t, &storerTestSuite{ + makeStore: func() Storer { return NewMapStore() }, + }) + suite.Run(t, &storerTestSuite{ + makeStore: func() Storer { return NewSyncStore() }, + }) +} diff --git a/06_puppy/hsy3418/syncStore.go b/06_puppy/hsy3418/syncStore.go new file mode 100644 index 000000000..60b9a8a6d --- /dev/null +++ b/06_puppy/hsy3418/syncStore.go @@ -0,0 +1,60 @@ +package main + +import ( + "fmt" + "sync" +) + +// SynceStore is a implementation of `Storer` backed by a sync.Map +type SyncStore struct { + sync.Mutex + sync.Map + nextID int32 +} + +// NewSyncStore creates a new SyncStore +func NewSyncStore() *SyncStore { + return &SyncStore{} +} + +// CreatePuppy adds a nuw puppy to the puppy store and returns the id for the puppy +func (m *SyncStore) CreatePuppy(puppy Puppy) int32 { + m.Lock() + defer m.Unlock() + puppy.ID = m.nextID + m.nextID++ + m.Store(puppy.ID, puppy) + return puppy.ID +} + +// ReadPuppy retrieves the puppy for a given id from puppies store +func (m *SyncStore) ReadPuppy(id int32) (Puppy, error) { + p, ok := m.Load(id) + if !ok { + return Puppy{}, fmt.Errorf("puppy with %d ID does not exist", id) + } + puppy, _ := p.(Puppy) + return puppy, nil +} + +//UpdatePuppy updates the puppy for the given id +func (m *SyncStore) UpdatePuppy(puppy Puppy) error { + m.Lock() + defer m.Unlock() + if _, ok := m.Load(puppy.ID); !ok { + return fmt.Errorf("puppy with %d ID does not exist", puppy.ID) + } + m.Store(puppy.ID, puppy) + return nil +} + +//DeletePuppy delete the puppy for the given id from puppies store +func (m *SyncStore) DeletePuppy(id int32) error { + m.Lock() + defer m.Unlock() + if _, ok := m.Load(id); !ok { + return fmt.Errorf("puppy with %d ID does not exist", id) + } + m.Delete(id) + return nil +} diff --git a/06_puppy/hsy3418/types.go b/06_puppy/hsy3418/types.go new file mode 100644 index 000000000..49deba920 --- /dev/null +++ b/06_puppy/hsy3418/types.go @@ -0,0 +1,17 @@ +package main + +// Puppy defines the data structure corresponding to a pet +type Puppy struct { + ID int32 `json:"id"` + Value float32 `json:"value"` + Breed string `json:"breed"` + Colour string `json:"colour"` +} + +//Storer define standard CRUD operations for puppys +type Storer interface { + CreatePuppy(Puppy) int32 + ReadPuppy(ID int32) (Puppy, error) + UpdatePuppy(puppy Puppy) error + DeletePuppy(ID int32) error +}