Решение на Game of Life от Ралица Великова

Обратно към всички решения

Към профила на Ралица Великова

Резултати

  • 10 точки от тестове
  • 0 бонус точки
  • 10 точки общо
  • 14 успешни тест(а)
  • 0 неуспешни тест(а)

Код

package main
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"sync"
)
type Cell struct {
X int64 `json:"x"`
Y int64 `json:"y"`
}
func (c *Cell) cellToArray() (cell [2]int64) {
cell[0] = c.X
cell[1] = c.Y
return
}
type JsonCell struct {
X *int64 `json:"x"`
Y *int64 `json:"y"`
}
func (j *JsonCell) isValid() (result bool) {
if j.X != nil && j.Y != nil {
result = true
}
return
}
func (j *JsonCell) toCell() (result Cell, err error) {
if j.isValid() {
result.X = *j.X
result.Y = *j.Y
} else {
err = errors.New("Cannot convert JsonCell to Cell")
}
return
}
type GameOfLifeHandler struct {
Generation int
Board map[Cell]bool
Neighbours map[Cell]int
BoardMutex sync.Mutex
NeighboursMutex sync.Mutex
RequestMutex sync.RWMutex
}
/* util functions */
func sendError(w http.ResponseWriter, err error) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
}
func parseXYQuery(w http.ResponseWriter, r *http.Request) (result Cell, err error) {
values, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
sendError(w, err)
return
}
// validate
if len(values) != 2 {
err = errors.New("Invalid parameters query - invalid number of query parameters")
sendError(w, err)
return
}
xString := r.FormValue("x")
yString := r.FormValue("y")
if xString == "" || yString == "" {
err = errors.New("Invalid parameters query - x or y not set")
sendError(w, err)
return
}
x, err := strconv.ParseInt(xString, 10, 64)
if err != nil {
sendError(w, err)
return
}
y, err := strconv.ParseInt(yString, 10, 64)
if err != nil {
sendError(w, err)
return
}
result.X = x
result.Y = y
return
}
func updateNeighbours(cell Cell, neighbours *map[Cell]int) {
var neighbour Cell
for i := cell.X - 1; i <= cell.X+1; i++ {
for j := cell.Y - 1; j <= cell.Y+1; j++ {
if i == cell.X && j == cell.Y {
continue
}
neighbour.X = i
neighbour.Y = j
(*neighbours)[neighbour]++
}
}
}
/* url handlers */
func (g *GameOfLifeHandler) addNewCells(w http.ResponseWriter, r *http.Request) (err error) {
var cellSlice []JsonCell
cellSlice = make([]JsonCell, 0)
jsonBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
sendError(w, err)
return
}
err = json.Unmarshal(jsonBytes, &cellSlice)
if err != nil {
sendError(w, err)
return
}
var cell Cell
for _, jsonCell := range cellSlice {
cell, err = jsonCell.toCell()
if err != nil {
sendError(w, err)
return
}
g.BoardMutex.Lock()
if g.Board[cell] {
g.BoardMutex.Unlock()
continue
}
g.Board[cell] = true
g.BoardMutex.Unlock()
g.NeighboursMutex.Lock()
updateNeighbours(cell, &g.Neighbours)
g.NeighboursMutex.Unlock()
}
return
}
// writes json as a response
func (g *GameOfLifeHandler) checkCell(w http.ResponseWriter, cell Cell) (err error) {
alive := make(map[string]bool)
alive["alive"] = g.Board[cell]
output, err := json.Marshal(alive)
if err != nil {
sendError(w, err)
return
}
w.WriteHeader(http.StatusOK)
w.Write(output)
return
}
// writes json as a response
func (g *GameOfLifeHandler) generation(w http.ResponseWriter) {
generation := make([][2]int64, 0, len(g.Board))
var arrayCell [2]int64
for cell := range g.Board {
arrayCell = cell.cellToArray()
generation = append(generation, arrayCell)
}
currentGeneration := make(map[string]interface{})
currentGeneration["generation"] = g.Generation
currentGeneration["living"] = generation
output, err := json.Marshal(currentGeneration)
if err != nil {
sendError(w, err)
return
}
w.WriteHeader(http.StatusOK)
w.Write(output)
}
func (g *GameOfLifeHandler) evolve(w http.ResponseWriter) {
newBoard := make(map[Cell]bool)
newNeighbourBoard := make(map[Cell]int)
g.NeighboursMutex.Lock()
for cell, neighbours := range g.Neighbours {
g.BoardMutex.Lock()
if g.Board[cell] {
if neighbours == 2 || neighbours == 3 {
newBoard[cell] = true
updateNeighbours(cell, &newNeighbourBoard)
}
} else {
if neighbours == 3 {
newBoard[cell] = true
updateNeighbours(cell, &newNeighbourBoard)
}
}
g.BoardMutex.Unlock()
}
g.NeighboursMutex.Unlock()
g.BoardMutex.Lock()
g.Board = newBoard
g.BoardMutex.Unlock()
g.NeighboursMutex.Lock()
g.Neighbours = newNeighbourBoard
g.NeighboursMutex.Unlock()
g.Generation++
w.WriteHeader(http.StatusNoContent)
}
func (g *GameOfLifeHandler) reset(w http.ResponseWriter) {
g.Generation = 0
g.BoardMutex.Lock()
g.Board = make(map[Cell]bool)
g.BoardMutex.Unlock()
w.WriteHeader(http.StatusNoContent)
}
func (g *GameOfLifeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
switch {
case regexp.MustCompile(`/generation/evolve/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.Lock()
g.evolve(w)
g.RequestMutex.Unlock()
}
case regexp.MustCompile(`/generation/`).MatchString(path):
// GET
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.RLock()
g.generation(w)
g.RequestMutex.RUnlock()
}
case regexp.MustCompile(`/cell/status/`).MatchString(path):
// GET
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.RLock()
cell, err := parseXYQuery(w, r)
if err == nil {
g.checkCell(w, cell)
}
g.RequestMutex.RUnlock()
}
case regexp.MustCompile(`/cells/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.Lock()
if err := g.addNewCells(w, r); err == nil {
w.WriteHeader(http.StatusCreated)
}
g.RequestMutex.Unlock()
}
case regexp.MustCompile(`/reset/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.Lock()
g.reset(w)
g.RequestMutex.Unlock()
}
default:
w.WriteHeader(http.StatusNotFound)
}
}
func NewGameOfLifeHandler(cells [][2]int64) *GameOfLifeHandler {
board := make(map[Cell]bool)
neighbours := make(map[Cell]int)
var newCell Cell
for _, cell := range cells {
newCell.X = cell[0]
newCell.Y = cell[1]
board[newCell] = true
updateNeighbours(newCell, &neighbours)
}
gameOfLifeHandler := &GameOfLifeHandler{Generation: 0, Board: board, Neighbours: neighbours}
return gameOfLifeHandler
}

Лог от изпълнението

PASS
ok  	_/tmp/d20160126-5892-1qpvw0v	0.004s
PASS
ok  	_/tmp/d20160126-5892-1qpvw0v	0.004s
PASS
ok  	_/tmp/d20160126-5892-1qpvw0v	0.006s
PASS
ok  	_/tmp/d20160126-5892-1qpvw0v	0.008s
PASS
ok  	_/tmp/d20160126-5892-1qpvw0v	0.007s
PASS
ok  	_/tmp/d20160126-5892-1qpvw0v	0.006s
PASS
ok  	_/tmp/d20160126-5892-1qpvw0v	0.006s
PASS
ok  	_/tmp/d20160126-5892-1qpvw0v	0.009s
PASS
ok  	_/tmp/d20160126-5892-1qpvw0v	0.006s
PASS
ok  	_/tmp/d20160126-5892-1qpvw0v	0.009s
PASS
ok  	_/tmp/d20160126-5892-1qpvw0v	0.010s
PASS
ok  	_/tmp/d20160126-5892-1qpvw0v	0.017s
PASS
ok  	_/tmp/d20160126-5892-1qpvw0v	0.015s
PASS
ok  	_/tmp/d20160126-5892-1qpvw0v	0.226s

История (7 версии и 5 коментара)

Ралица обнови решението на 23.01.2016 19:53 (преди над 2 години)

+package main
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+/* util functions */
+func checkError(err error) {
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func parseXYQuery(query string) (result [][2]int64) {
+ ampersandCount := strings.Count(query, "&")
+
+ result = make([][2]int64, ampersandCount+1)
+ queryParams := make([]string, ampersandCount+1)
+
+ queryParams = strings.Split(query, "&")
+
+ var (
+ param []string
+ index int = 0
+ number int64
+ err error
+ )
+ for _, val := range queryParams {
+ param = strings.Split(val, "=")
+
+ number, err = strconv.ParseInt(param[1], 10, 64)
+ checkError(err)
+
+ if param[0] == "x" {
+ result[index][0] = number
+ } else {
+ result[index][1] = number
+ index++
+ }
+ }
+
+ return
+}
+
+/* url handlers */
+type GameOfLifeHandler struct {
+ Generation int
+ Board map[[2]int64]bool
+ Neighbours map[[2]int64]int
+}
+
+func (g *GameOfLifeHandler) addNewCells(r *http.Request) (err error) {
+
+ var cellSlice []map[string]int64
+ cellSlice = make([]map[string]int64, 0, 2)
+
+ jsonBytes, _ := ioutil.ReadAll(r.Body)
+
+ err = json.Unmarshal(jsonBytes, &cellSlice)
+ checkError(err)
+
+ var newCellValue [2]int64
+ var neighbour [2]int64
+
+ for _, cell := range cellSlice {
+ newCellValue[0] = cell["x"]
+ newCellValue[1] = cell["y"]
+
+ g.Board[newCellValue] = true
+
+ for i := cell["x"] - 1; i < cell["x"]+1; i++ {
+ for j := cell["y"] - 1; j < cell["y"]+1; j++ {
+
+ if i == cell["x"] && j == cell["y"] {
+ continue
+ }
+
+ neighbour[0] = i
+ neighbour[1] = j
+
+ g.Neighbours[neighbour]++
+ }
+ }
+ }
+
+ return err
+}
+
+// writes json as a response
+func (g *GameOfLifeHandler) checkCell(w http.ResponseWriter, cell [2]int64) {
+ alive := make(map[string]bool)
+ alive["alive"] = g.Board[cell]
+
+ output, err := json.Marshal(alive)
+ checkError(err)
+
+ w.Write(output)
+}
+
+// writes json as a response
+func (g *GameOfLifeHandler) generation(w http.ResponseWriter) {
+ generation := make([][2]int64, 0, len(g.Board))
+
+ for cell := range g.Board {
+ generation = append(generation, cell)
+ }
+
+ currentGeneration := make(map[string]interface{})
+ currentGeneration["generation"] = strconv.Itoa(g.Generation)
+ currentGeneration["living"] = generation
+
+ output, err := json.Marshal(currentGeneration)
+ checkError(err)
+
+ w.WriteHeader(http.StatusCreated)
+ w.Write(output)
+}
+
+func (g *GameOfLifeHandler) evolve() {
+ newBoard := make(map[[2]int64]bool)
+ newNeighbourBoard := make(map[[2]int64]int)
+
+ var neighbour [2]int64
+
+ for cell, neighbours := range g.Neighbours {
+ if g.Board[cell] {
+ if neighbours == 2 || neighbours == 3 {
+ newBoard[cell] = true
+
+ for i := cell[0] - 1; i < cell[0]+1; i++ {
+ for j := cell[1] - 1; j < cell[1]+1; j++ {
+
+ if i == cell[0] && j == cell[1] {
+ continue
+ }
+
+ neighbour[0] = i
+ neighbour[1] = j
+
+ newNeighbourBoard[neighbour]++
+ }
+ }
+ }
+ }
+ }
+
+ g.Board = newBoard
+ g.Neighbours = newNeighbourBoard
+ g.Generation++
+}
+
+func (g *GameOfLifeHandler) reset() {
+ g.Generation = 0
+ g.Board = make(map[[2]int64]bool)
+}
+
+func (g *GameOfLifeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ path := r.URL.Path
+
+ switch {
+ case regexp.MustCompile(`/generation/evolve/`).MatchString(path):
+ // POST
+ if r.Method != "POST" {
+ w.WriteHeader(http.StatusMethodNotAllowed)
+ } else {
+ g.evolve()
+ w.WriteHeader(http.StatusOK)
+ }
+
+ case regexp.MustCompile(`/generation/`).MatchString(path):
+ // GET
+ if r.Method != "GET" {
+ w.WriteHeader(http.StatusMethodNotAllowed)
+ } else {
+ g.generation(w)
+ }
+
+ case regexp.MustCompile(`/cell/status/.*`).MatchString(path):
+ // GET
+ if r.Method != "GET" {
+ w.WriteHeader(http.StatusMethodNotAllowed)
+ } else {
+ cell := parseXYQuery(r.URL.RawQuery)[0]
+ g.checkCell(w, cell)
+ }
+
+ case regexp.MustCompile(`/cells/`).MatchString(path):
+ // POST
+ if r.Method != "POST" {
+ w.WriteHeader(http.StatusMethodNotAllowed)
+ } else {
+ if err := g.addNewCells(r); err != nil {
+ w.WriteHeader(http.StatusBadRequest)
+ } else {
+ w.WriteHeader(http.StatusCreated)
+ }
+ }
+
+ case regexp.MustCompile(`/reset/`).MatchString(path):
+ // POST
+ if r.Method != "POST" {
+ w.WriteHeader(http.StatusMethodNotAllowed)
+ } else {
+ g.reset()
+ w.WriteHeader(http.StatusOK)
+ }
+
+ default:
+ w.WriteHeader(http.StatusBadRequest)
+ _, err := w.Write(nil)
+ checkError(err)
+ }
+}
+
+func NewGameOfLifeHandler(cells [][2]int64) *GameOfLifeHandler {
+ board := make(map[[2]int64]bool)
+ neighbours := make(map[[2]int64]int)
+
+ var neighbour [2]int64
+
+ for _, cell := range cells {
+ board[cell] = true
+
+ for i := cell[0] - 1; i < cell[0]+1; i++ {
+ for j := cell[1] - 1; j < cell[1]+1; j++ {
+
+ if i == cell[0] && j == cell[1] {
+ continue
+ }
+
+ neighbour[0] = i
+ neighbour[1] = j
+
+ neighbours[neighbour]++
+ }
+ }
+ }
+
+ gameOfLifeHandler := &GameOfLifeHandler{Generation: 0, Board: board, Neighbours: neighbours}
+
+ http.Handle("/", gameOfLifeHandler)
+
+ return gameOfLifeHandler
+}

Ралица обнови решението на 23.01.2016 19:57 (преди над 2 години)

package main
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"regexp"
"strconv"
"strings"
)
/* util functions */
func checkError(err error) {
if err != nil {
log.Fatal(err)
}
}
func parseXYQuery(query string) (result [][2]int64) {
ampersandCount := strings.Count(query, "&")
result = make([][2]int64, ampersandCount+1)
queryParams := make([]string, ampersandCount+1)
queryParams = strings.Split(query, "&")
var (
param []string
index int = 0
number int64
err error
)
for _, val := range queryParams {
param = strings.Split(val, "=")
number, err = strconv.ParseInt(param[1], 10, 64)
checkError(err)
if param[0] == "x" {
result[index][0] = number
} else {
result[index][1] = number
index++
}
}
return
}
/* url handlers */
type GameOfLifeHandler struct {
Generation int
Board map[[2]int64]bool
Neighbours map[[2]int64]int
}
func (g *GameOfLifeHandler) addNewCells(r *http.Request) (err error) {
var cellSlice []map[string]int64
cellSlice = make([]map[string]int64, 0, 2)
jsonBytes, _ := ioutil.ReadAll(r.Body)
err = json.Unmarshal(jsonBytes, &cellSlice)
checkError(err)
var newCellValue [2]int64
var neighbour [2]int64
for _, cell := range cellSlice {
newCellValue[0] = cell["x"]
newCellValue[1] = cell["y"]
g.Board[newCellValue] = true
for i := cell["x"] - 1; i < cell["x"]+1; i++ {
for j := cell["y"] - 1; j < cell["y"]+1; j++ {
if i == cell["x"] && j == cell["y"] {
continue
}
neighbour[0] = i
neighbour[1] = j
g.Neighbours[neighbour]++
}
}
}
return err
}
// writes json as a response
func (g *GameOfLifeHandler) checkCell(w http.ResponseWriter, cell [2]int64) {
alive := make(map[string]bool)
alive["alive"] = g.Board[cell]
output, err := json.Marshal(alive)
checkError(err)
w.Write(output)
}
// writes json as a response
func (g *GameOfLifeHandler) generation(w http.ResponseWriter) {
generation := make([][2]int64, 0, len(g.Board))
for cell := range g.Board {
generation = append(generation, cell)
}
currentGeneration := make(map[string]interface{})
currentGeneration["generation"] = strconv.Itoa(g.Generation)
currentGeneration["living"] = generation
output, err := json.Marshal(currentGeneration)
checkError(err)
w.WriteHeader(http.StatusCreated)
w.Write(output)
}
func (g *GameOfLifeHandler) evolve() {
newBoard := make(map[[2]int64]bool)
newNeighbourBoard := make(map[[2]int64]int)
var neighbour [2]int64
for cell, neighbours := range g.Neighbours {
if g.Board[cell] {
if neighbours == 2 || neighbours == 3 {
newBoard[cell] = true
for i := cell[0] - 1; i < cell[0]+1; i++ {
for j := cell[1] - 1; j < cell[1]+1; j++ {
if i == cell[0] && j == cell[1] {
continue
}
neighbour[0] = i
neighbour[1] = j
newNeighbourBoard[neighbour]++
}
}
}
+ } else {
+ if neighbours == 3 {
+ newBoard[cell] = true
+
+ for i := cell[0] - 1; i < cell[0]+1; i++ {
+ for j := cell[1] - 1; j < cell[1]+1; j++ {
+
+ if i == cell[0] && j == cell[1] {
+ continue
+ }
+
+ neighbour[0] = i
+ neighbour[1] = j
+
+ newNeighbourBoard[neighbour]++
+ }
+ }
+ }
}
}
g.Board = newBoard
g.Neighbours = newNeighbourBoard
g.Generation++
}
func (g *GameOfLifeHandler) reset() {
g.Generation = 0
g.Board = make(map[[2]int64]bool)
}
func (g *GameOfLifeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
switch {
case regexp.MustCompile(`/generation/evolve/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.evolve()
w.WriteHeader(http.StatusOK)
}
case regexp.MustCompile(`/generation/`).MatchString(path):
// GET
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.generation(w)
}
case regexp.MustCompile(`/cell/status/.*`).MatchString(path):
// GET
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
cell := parseXYQuery(r.URL.RawQuery)[0]
g.checkCell(w, cell)
}
case regexp.MustCompile(`/cells/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
if err := g.addNewCells(r); err != nil {
w.WriteHeader(http.StatusBadRequest)
} else {
w.WriteHeader(http.StatusCreated)
}
}
case regexp.MustCompile(`/reset/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.reset()
w.WriteHeader(http.StatusOK)
}
default:
- w.WriteHeader(http.StatusBadRequest)
+ w.WriteHeader(http.StatusNotFound)
_, err := w.Write(nil)
checkError(err)
}
}
func NewGameOfLifeHandler(cells [][2]int64) *GameOfLifeHandler {
board := make(map[[2]int64]bool)
neighbours := make(map[[2]int64]int)
var neighbour [2]int64
for _, cell := range cells {
board[cell] = true
for i := cell[0] - 1; i < cell[0]+1; i++ {
for j := cell[1] - 1; j < cell[1]+1; j++ {
if i == cell[0] && j == cell[1] {
continue
}
neighbour[0] = i
neighbour[1] = j
neighbours[neighbour]++
}
}
}
gameOfLifeHandler := &GameOfLifeHandler{Generation: 0, Board: board, Neighbours: neighbours}
http.Handle("/", gameOfLifeHandler)
return gameOfLifeHandler
}

Хубаво решение. Виждам, че обичаш да си написваш нещата сама. Това е хубаво, защото се научаваш да имаш пълен контрол и да не разчиташ на магически библиотеки. Справила си се с тези неща без грешка. Но сега може да направиш следващата стъпка и да започнеш да използваш полезните неща от стандартната библиотека на Go, за които вече знаеш един възможен вариант за имплементация. А дори и можеш да сравниш твоя подход с тяхния.

Мисля, че ще намериш документацията на http.Request интересна, особено в часта където се говори за .Form.

Пробвай да изпратиш JSON за добавяне на живи клетки, който не е съвсем по условието. Например [{"x": 5}]. Изобщо, съвета ми е да не работиш с map-ове, когато сериализираш и десиарилираш JSON-и. Виждам, че използваш изключително този метод и на всички други места. Вършиш много повече работа и е възможно да сгрешиш. Вероятно ти отнема и повече време да го напишеш.

Регулярните изрази, които си използвала за намиране на пътя са напълно вярни. Решават проблема, бих казал, твърде добре. Разглеждала ли си възможноста за по - просто решение проблема който решават. Например http.ServeMux?

Намерих и едно нещо, за което може би сме те подвели с не съвсем пълно условие. NewGameOfLifeHandler трябва просто да върне нот http.Handler, но не и да го "закача". Ако се извика няколко пъти NewGameOfLifeHandler ще се получи няколко закачания за пътя "/", което е грешно.

В допълнителните пояснения говорим за конкурентност. Разгледай кода си за критични секции. Знай, че http сървърите на go най - често пускат отделна го рутина за всеки рикуест.

Само тъжно, че остава доста малко време да се занимаваш със задачата.

Edit: разгледай и върнатите статус кодове за различните случаи. Най - добре си напиши прост тест, гледайки условието.

Ралица обнови решението на 24.01.2016 17:05 (преди над 2 години)

package main
import (
"encoding/json"
"io/ioutil"
- "log"
"net/http"
"regexp"
"strconv"
"strings"
)
+type Cell [2]int64
+
+type GameOfLifeHandler struct {
+ Generation int
+ Board map[Cell]bool
+ Neighbours map[Cell]int
+ RespWriter http.ResponseWriter
+}
+
/* util functions */
-func checkError(err error) {
+func (g *GameOfLifeHandler) checkError(err error) {
if err != nil {
- log.Fatal(err)
+ g.RespWriter.Header().Set("Error", err.Error())
+ g.RespWriter.WriteHeader(http.StatusBadRequest)
}
}
-func parseXYQuery(query string) (result [][2]int64) {
+func (g *GameOfLifeHandler) parseXYQuery(query string) (result []Cell) {
ampersandCount := strings.Count(query, "&")
- result = make([][2]int64, ampersandCount+1)
+ result = make([]Cell, ampersandCount+1)
queryParams := make([]string, ampersandCount+1)
queryParams = strings.Split(query, "&")
var (
param []string
index int = 0
number int64
err error
)
for _, val := range queryParams {
param = strings.Split(val, "=")
number, err = strconv.ParseInt(param[1], 10, 64)
- checkError(err)
+ g.checkError(err)
if param[0] == "x" {
result[index][0] = number
} else {
result[index][1] = number
index++
}
}
return
}
-/* url handlers */
-type GameOfLifeHandler struct {
- Generation int
- Board map[[2]int64]bool
- Neighbours map[[2]int64]int
+func updateNeighbours(cellItem Cell, neighbours *map[Cell]int) {
+ var cell Cell
+
+ index := 0
+ for _, val := range cellItem {
+ cell[index] = val
+ index++
+ }
+
+ var neighbour Cell
+
+ for i := cell[0] - 1; i < cell[0]+1; i++ {
+ for j := cell[1] - 1; j < cell[1]+1; j++ {
+
+ if i == cell[0] && j == cell[1] {
+ continue
+ }
+
+ neighbour[0] = i
+ neighbour[1] = j
+
+ (*neighbours)[neighbour]++
+ }
+ }
}
+/* url handlers */
func (g *GameOfLifeHandler) addNewCells(r *http.Request) (err error) {
var cellSlice []map[string]int64
cellSlice = make([]map[string]int64, 0, 2)
jsonBytes, _ := ioutil.ReadAll(r.Body)
err = json.Unmarshal(jsonBytes, &cellSlice)
- checkError(err)
+ g.checkError(err)
- var newCellValue [2]int64
- var neighbour [2]int64
+ var newCellValue Cell
for _, cell := range cellSlice {
newCellValue[0] = cell["x"]
newCellValue[1] = cell["y"]
g.Board[newCellValue] = true
- for i := cell["x"] - 1; i < cell["x"]+1; i++ {
- for j := cell["y"] - 1; j < cell["y"]+1; j++ {
-
- if i == cell["x"] && j == cell["y"] {
- continue
- }
-
- neighbour[0] = i
- neighbour[1] = j
-
- g.Neighbours[neighbour]++
- }
- }
+ updateNeighbours(newCellValue, &g.Neighbours)
}
return err
}
// writes json as a response
-func (g *GameOfLifeHandler) checkCell(w http.ResponseWriter, cell [2]int64) {
+func (g *GameOfLifeHandler) checkCell(w http.ResponseWriter, cell Cell) {
alive := make(map[string]bool)
alive["alive"] = g.Board[cell]
output, err := json.Marshal(alive)
- checkError(err)
+ g.checkError(err)
w.Write(output)
}
// writes json as a response
func (g *GameOfLifeHandler) generation(w http.ResponseWriter) {
- generation := make([][2]int64, 0, len(g.Board))
+ generation := make([]Cell, 0, len(g.Board))
for cell := range g.Board {
generation = append(generation, cell)
}
currentGeneration := make(map[string]interface{})
currentGeneration["generation"] = strconv.Itoa(g.Generation)
currentGeneration["living"] = generation
output, err := json.Marshal(currentGeneration)
- checkError(err)
+ g.checkError(err)
w.WriteHeader(http.StatusCreated)
w.Write(output)
}
func (g *GameOfLifeHandler) evolve() {
- newBoard := make(map[[2]int64]bool)
- newNeighbourBoard := make(map[[2]int64]int)
+ newBoard := make(map[Cell]bool)
+ newNeighbourBoard := make(map[Cell]int)
- var neighbour [2]int64
-
for cell, neighbours := range g.Neighbours {
if g.Board[cell] {
if neighbours == 2 || neighbours == 3 {
newBoard[cell] = true
- for i := cell[0] - 1; i < cell[0]+1; i++ {
- for j := cell[1] - 1; j < cell[1]+1; j++ {
-
- if i == cell[0] && j == cell[1] {
- continue
- }
-
- neighbour[0] = i
- neighbour[1] = j
-
- newNeighbourBoard[neighbour]++
- }
- }
+ updateNeighbours(cell, &newNeighbourBoard)
}
} else {
if neighbours == 3 {
newBoard[cell] = true
- for i := cell[0] - 1; i < cell[0]+1; i++ {
- for j := cell[1] - 1; j < cell[1]+1; j++ {
-
- if i == cell[0] && j == cell[1] {
- continue
- }
-
- neighbour[0] = i
- neighbour[1] = j
-
- newNeighbourBoard[neighbour]++
- }
- }
+ updateNeighbours(cell, &newNeighbourBoard)
}
}
}
g.Board = newBoard
g.Neighbours = newNeighbourBoard
g.Generation++
}
func (g *GameOfLifeHandler) reset() {
g.Generation = 0
- g.Board = make(map[[2]int64]bool)
+ g.Board = make(map[Cell]bool)
}
func (g *GameOfLifeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
switch {
case regexp.MustCompile(`/generation/evolve/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.evolve()
w.WriteHeader(http.StatusOK)
}
case regexp.MustCompile(`/generation/`).MatchString(path):
// GET
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.generation(w)
}
case regexp.MustCompile(`/cell/status/.*`).MatchString(path):
// GET
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
- cell := parseXYQuery(r.URL.RawQuery)[0]
+ cell := g.parseXYQuery(r.URL.RawQuery)[0]
g.checkCell(w, cell)
}
case regexp.MustCompile(`/cells/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
if err := g.addNewCells(r); err != nil {
w.WriteHeader(http.StatusBadRequest)
} else {
w.WriteHeader(http.StatusCreated)
}
}
case regexp.MustCompile(`/reset/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.reset()
w.WriteHeader(http.StatusOK)
}
default:
w.WriteHeader(http.StatusNotFound)
_, err := w.Write(nil)
- checkError(err)
+ g.checkError(err)
}
}
func NewGameOfLifeHandler(cells [][2]int64) *GameOfLifeHandler {
- board := make(map[[2]int64]bool)
- neighbours := make(map[[2]int64]int)
+ board := make(map[Cell]bool)
+ neighbours := make(map[Cell]int)
- var neighbour [2]int64
+ var neighbour Cell
for _, cell := range cells {
board[cell] = true
for i := cell[0] - 1; i < cell[0]+1; i++ {
for j := cell[1] - 1; j < cell[1]+1; j++ {
if i == cell[0] && j == cell[1] {
continue
}
neighbour[0] = i
neighbour[1] = j
neighbours[neighbour]++
}
}
}
gameOfLifeHandler := &GameOfLifeHandler{Generation: 0, Board: board, Neighbours: neighbours}
-
- http.Handle("/", gameOfLifeHandler)
return gameOfLifeHandler
}

Ралица обнови решението на 24.01.2016 17:43 (преди над 2 години)

package main
import (
"encoding/json"
+ "errors"
"io/ioutil"
"net/http"
"regexp"
"strconv"
"strings"
)
type Cell [2]int64
type GameOfLifeHandler struct {
Generation int
Board map[Cell]bool
Neighbours map[Cell]int
RespWriter http.ResponseWriter
}
/* util functions */
func (g *GameOfLifeHandler) checkError(err error) {
if err != nil {
g.RespWriter.Header().Set("Error", err.Error())
g.RespWriter.WriteHeader(http.StatusBadRequest)
}
}
-func (g *GameOfLifeHandler) parseXYQuery(query string) (result []Cell) {
+func (g *GameOfLifeHandler) parseXYQuery(query string) (result Cell) {
ampersandCount := strings.Count(query, "&")
- result = make([]Cell, ampersandCount+1)
queryParams := make([]string, ampersandCount+1)
queryParams = strings.Split(query, "&")
var (
param []string
- index int = 0
number int64
err error
)
+
+ // validate
+ for key, val := range queryParams {
+ if key > 1 {
+ g.checkError(errors.New("Invalid parameters query"))
+ return
+ }
+
+ param = strings.Split(val, "=")
+
+ if param[0] != "x" && param[0] != "y" {
+ g.checkError(errors.New("Invalid parameters query"))
+ return
+ }
+ }
for _, val := range queryParams {
param = strings.Split(val, "=")
number, err = strconv.ParseInt(param[1], 10, 64)
g.checkError(err)
if param[0] == "x" {
- result[index][0] = number
+ result[0] = number
} else {
- result[index][1] = number
- index++
+ result[1] = number
}
}
return
}
func updateNeighbours(cellItem Cell, neighbours *map[Cell]int) {
var cell Cell
index := 0
for _, val := range cellItem {
cell[index] = val
index++
}
var neighbour Cell
for i := cell[0] - 1; i < cell[0]+1; i++ {
for j := cell[1] - 1; j < cell[1]+1; j++ {
if i == cell[0] && j == cell[1] {
continue
}
neighbour[0] = i
neighbour[1] = j
(*neighbours)[neighbour]++
}
}
}
/* url handlers */
func (g *GameOfLifeHandler) addNewCells(r *http.Request) (err error) {
var cellSlice []map[string]int64
cellSlice = make([]map[string]int64, 0, 2)
- jsonBytes, _ := ioutil.ReadAll(r.Body)
+ jsonBytes, err := ioutil.ReadAll(r.Body)
+ g.checkError(err)
err = json.Unmarshal(jsonBytes, &cellSlice)
g.checkError(err)
var newCellValue Cell
for _, cell := range cellSlice {
newCellValue[0] = cell["x"]
newCellValue[1] = cell["y"]
g.Board[newCellValue] = true
updateNeighbours(newCellValue, &g.Neighbours)
}
return err
}
// writes json as a response
func (g *GameOfLifeHandler) checkCell(w http.ResponseWriter, cell Cell) {
alive := make(map[string]bool)
alive["alive"] = g.Board[cell]
output, err := json.Marshal(alive)
g.checkError(err)
w.Write(output)
}
// writes json as a response
func (g *GameOfLifeHandler) generation(w http.ResponseWriter) {
generation := make([]Cell, 0, len(g.Board))
for cell := range g.Board {
generation = append(generation, cell)
}
currentGeneration := make(map[string]interface{})
currentGeneration["generation"] = strconv.Itoa(g.Generation)
currentGeneration["living"] = generation
output, err := json.Marshal(currentGeneration)
g.checkError(err)
w.WriteHeader(http.StatusCreated)
w.Write(output)
}
func (g *GameOfLifeHandler) evolve() {
newBoard := make(map[Cell]bool)
newNeighbourBoard := make(map[Cell]int)
for cell, neighbours := range g.Neighbours {
if g.Board[cell] {
if neighbours == 2 || neighbours == 3 {
newBoard[cell] = true
updateNeighbours(cell, &newNeighbourBoard)
}
} else {
if neighbours == 3 {
newBoard[cell] = true
updateNeighbours(cell, &newNeighbourBoard)
}
}
}
g.Board = newBoard
g.Neighbours = newNeighbourBoard
g.Generation++
}
func (g *GameOfLifeHandler) reset() {
g.Generation = 0
g.Board = make(map[Cell]bool)
}
func (g *GameOfLifeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
switch {
case regexp.MustCompile(`/generation/evolve/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.evolve()
w.WriteHeader(http.StatusOK)
}
case regexp.MustCompile(`/generation/`).MatchString(path):
// GET
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.generation(w)
}
case regexp.MustCompile(`/cell/status/.*`).MatchString(path):
// GET
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
- cell := g.parseXYQuery(r.URL.RawQuery)[0]
+ cell := g.parseXYQuery(r.URL.RawQuery)
+
g.checkCell(w, cell)
}
case regexp.MustCompile(`/cells/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
if err := g.addNewCells(r); err != nil {
w.WriteHeader(http.StatusBadRequest)
} else {
w.WriteHeader(http.StatusCreated)
}
}
case regexp.MustCompile(`/reset/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.reset()
w.WriteHeader(http.StatusOK)
}
default:
w.WriteHeader(http.StatusNotFound)
_, err := w.Write(nil)
g.checkError(err)
}
}
func NewGameOfLifeHandler(cells [][2]int64) *GameOfLifeHandler {
board := make(map[Cell]bool)
neighbours := make(map[Cell]int)
- var neighbour Cell
-
for _, cell := range cells {
board[cell] = true
- for i := cell[0] - 1; i < cell[0]+1; i++ {
- for j := cell[1] - 1; j < cell[1]+1; j++ {
-
- if i == cell[0] && j == cell[1] {
- continue
- }
-
- neighbour[0] = i
- neighbour[1] = j
-
- neighbours[neighbour]++
- }
- }
+ updateNeighbours(cell, &neighbours)
}
gameOfLifeHandler := &GameOfLifeHandler{Generation: 0, Board: board, Neighbours: neighbours}
return gameOfLifeHandler
}

Ралица обнови решението на 25.01.2016 01:16 (преди над 2 години)

package main
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
+ "net/url"
"regexp"
"strconv"
- "strings"
+ "sync"
)
-type Cell [2]int64
+type Cell struct {
+ X int64 `json:"x"`
+ Y int64 `json:"y"`
+}
type GameOfLifeHandler struct {
- Generation int
- Board map[Cell]bool
- Neighbours map[Cell]int
- RespWriter http.ResponseWriter
+ Generation int
+ Board map[Cell]bool
+ Neighbours map[Cell]int
+ RespWriter http.ResponseWriter
+ BoardMutex sync.Mutex
+ NeighboursMutex sync.Mutex
+ RequestMutex sync.RWMutex
}
/* util functions */
-func (g *GameOfLifeHandler) checkError(err error) {
+func (g *GameOfLifeHandler) sendError(err error) {
+ g.RespWriter.Header().Set("Error", err.Error())
+ g.RespWriter.WriteHeader(http.StatusBadRequest)
+}
+
+func (g *GameOfLifeHandler) parseXYQuery(r *http.Request) (result Cell, err error) {
+ values, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
- g.RespWriter.Header().Set("Error", err.Error())
- g.RespWriter.WriteHeader(http.StatusBadRequest)
+ g.sendError(err)
+ return
}
-}
-func (g *GameOfLifeHandler) parseXYQuery(query string) (result Cell) {
- ampersandCount := strings.Count(query, "&")
-
- queryParams := make([]string, ampersandCount+1)
-
- queryParams = strings.Split(query, "&")
-
- var (
- param []string
- number int64
- err error
- )
-
// validate
- for key, val := range queryParams {
- if key > 1 {
- g.checkError(errors.New("Invalid parameters query"))
- return
- }
+ if len(values) != 2 {
+ err = errors.New("Invalid parameters query - invalid number of query parameters")
+ g.sendError(err)
+ return
+ }
- param = strings.Split(val, "=")
+ xString := values.Get("x")
+ yString := values.Get("y")
- if param[0] != "x" && param[0] != "y" {
- g.checkError(errors.New("Invalid parameters query"))
- return
- }
+ if xString == "" || yString == "" {
+ err = errors.New("Invalid parameters query - x or y not set")
+ g.sendError(err)
+ return
}
- for _, val := range queryParams {
- param = strings.Split(val, "=")
- number, err = strconv.ParseInt(param[1], 10, 64)
- g.checkError(err)
-
- if param[0] == "x" {
- result[0] = number
- } else {
- result[1] = number
- }
+ x, err := strconv.ParseInt(xString, 10, 64)
+ if err != nil {
+ g.sendError(err)
+ return
}
+ y, err := strconv.ParseInt(yString, 10, 64)
+ if err != nil {
+ g.sendError(err)
+ return
+ }
+ result.X = x
+ result.Y = y
+
return
}
-func updateNeighbours(cellItem Cell, neighbours *map[Cell]int) {
- var cell Cell
-
- index := 0
- for _, val := range cellItem {
- cell[index] = val
- index++
- }
-
+func updateNeighbours(cell Cell, neighbours *map[Cell]int) {
var neighbour Cell
- for i := cell[0] - 1; i < cell[0]+1; i++ {
- for j := cell[1] - 1; j < cell[1]+1; j++ {
+ for i := cell.X - 1; i < cell.X+1; i++ {
+ for j := cell.Y - 1; j < cell.Y+1; j++ {
- if i == cell[0] && j == cell[1] {
+ if i == cell.X && j == cell.Y {
continue
}
- neighbour[0] = i
- neighbour[1] = j
+ neighbour.X = i
+ neighbour.Y = j
(*neighbours)[neighbour]++
}
}
}
/* url handlers */
func (g *GameOfLifeHandler) addNewCells(r *http.Request) (err error) {
+ var cellSlice []Cell
+ cellSlice = make([]Cell, 0, 2)
- var cellSlice []map[string]int64
- cellSlice = make([]map[string]int64, 0, 2)
-
jsonBytes, err := ioutil.ReadAll(r.Body)
- g.checkError(err)
+ if err != nil {
+ g.sendError(err)
+ return
+ }
err = json.Unmarshal(jsonBytes, &cellSlice)
- g.checkError(err)
+ if err != nil {
+ g.sendError(err)
+ return
+ }
- var newCellValue Cell
-
for _, cell := range cellSlice {
- newCellValue[0] = cell["x"]
- newCellValue[1] = cell["y"]
+ g.BoardMutex.Lock()
+ g.Board[cell] = true
+ g.BoardMutex.Unlock()
- g.Board[newCellValue] = true
-
- updateNeighbours(newCellValue, &g.Neighbours)
+ g.NeighboursMutex.Lock()
+ updateNeighbours(cell, &g.Neighbours)
+ g.NeighboursMutex.Unlock()
}
return err
}
// writes json as a response
-func (g *GameOfLifeHandler) checkCell(w http.ResponseWriter, cell Cell) {
+func (g *GameOfLifeHandler) checkCell(w http.ResponseWriter, cell Cell) (err error) {
alive := make(map[string]bool)
alive["alive"] = g.Board[cell]
output, err := json.Marshal(alive)
- g.checkError(err)
+ if err != nil {
+ g.sendError(err)
+ return
+ }
w.Write(output)
+ return
}
// writes json as a response
func (g *GameOfLifeHandler) generation(w http.ResponseWriter) {
generation := make([]Cell, 0, len(g.Board))
for cell := range g.Board {
generation = append(generation, cell)
}
currentGeneration := make(map[string]interface{})
currentGeneration["generation"] = strconv.Itoa(g.Generation)
currentGeneration["living"] = generation
output, err := json.Marshal(currentGeneration)
- g.checkError(err)
+ if err != nil {
+ g.sendError(err)
+ return
+ }
w.WriteHeader(http.StatusCreated)
w.Write(output)
}
func (g *GameOfLifeHandler) evolve() {
newBoard := make(map[Cell]bool)
newNeighbourBoard := make(map[Cell]int)
for cell, neighbours := range g.Neighbours {
if g.Board[cell] {
if neighbours == 2 || neighbours == 3 {
newBoard[cell] = true
updateNeighbours(cell, &newNeighbourBoard)
}
} else {
if neighbours == 3 {
newBoard[cell] = true
updateNeighbours(cell, &newNeighbourBoard)
}
}
}
+ g.BoardMutex.Lock()
g.Board = newBoard
+ g.BoardMutex.Unlock()
+
+ g.NeighboursMutex.Lock()
g.Neighbours = newNeighbourBoard
+ g.NeighboursMutex.Unlock()
+
g.Generation++
}
func (g *GameOfLifeHandler) reset() {
g.Generation = 0
+ g.BoardMutex.Lock()
g.Board = make(map[Cell]bool)
+ g.BoardMutex.Unlock()
}
func (g *GameOfLifeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
switch {
case regexp.MustCompile(`/generation/evolve/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
+ g.RequestMutex.Lock()
+
g.evolve()
w.WriteHeader(http.StatusOK)
+
+ g.RequestMutex.Unlock()
}
case regexp.MustCompile(`/generation/`).MatchString(path):
// GET
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
+ g.RequestMutex.RLock()
+
g.generation(w)
+
+ g.RequestMutex.RUnlock()
}
- case regexp.MustCompile(`/cell/status/.*`).MatchString(path):
+ case regexp.MustCompile(`/cell/status/`).MatchString(path):
// GET
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
- cell := g.parseXYQuery(r.URL.RawQuery)
+ g.RequestMutex.RLock()
- g.checkCell(w, cell)
+ cell, err := g.parseXYQuery(r)
+ if err == nil {
+ g.checkCell(w, cell)
+ }
+
+ g.RequestMutex.RUnlock()
}
case regexp.MustCompile(`/cells/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
- if err := g.addNewCells(r); err != nil {
- w.WriteHeader(http.StatusBadRequest)
- } else {
+ g.RequestMutex.Lock()
+
+ if err := g.addNewCells(r); err == nil {
w.WriteHeader(http.StatusCreated)
}
+
+ g.RequestMutex.Unlock()
}
case regexp.MustCompile(`/reset/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
+ g.RequestMutex.Lock()
+
g.reset()
w.WriteHeader(http.StatusOK)
+
+ g.RequestMutex.Unlock()
}
default:
w.WriteHeader(http.StatusNotFound)
- _, err := w.Write(nil)
- g.checkError(err)
}
}
func NewGameOfLifeHandler(cells [][2]int64) *GameOfLifeHandler {
board := make(map[Cell]bool)
neighbours := make(map[Cell]int)
+ var newCell Cell
+
for _, cell := range cells {
- board[cell] = true
+ newCell.X = cell[0]
+ newCell.Y = cell[1]
- updateNeighbours(cell, &neighbours)
+ board[newCell] = true
+
+ updateNeighbours(newCell, &neighbours)
}
gameOfLifeHandler := &GameOfLifeHandler{Generation: 0, Board: board, Neighbours: neighbours}
return gameOfLifeHandler
}

Тръгнала си по правилния път, но все още има останали race condition-и. Знай, че писането и четенето от един и същи map в две различни go рутини също е проблем. В долния пример има race condition:

go routine 1:
g.Board[cell] = false

go routine 2:
if g.Board[cell] { ... }

Например (но не само), извикването на updateNeighbours трябва да е защитено с подходящия mutex. Има шансове решението ти да не мине някои тестове заради това.

Според мен ще искаш да получиш възможно най - много точки след като си се справила с трудната част. Има няколко прости неща, заради които няма да ти минат всички тестове. Това са върнатите статус кодовете и една възможност за паника. Прочети допълнителните поясненията, както и тези под всеки индивидуален endpoint. Разгледай и addNewCells. Има случаи, в които ще гръмне.

Edit: Обърни внимание и на updateNeighbours. Не съм сигурен, че прави това което искаш да прави. Пробвай я върху клетка която няма никакви съседи.

Значи правилно съм разбрал какво трябва да прави updateNeighbours. Напиши си елементарен тест за нея. Викни я за някоя клетка и след това провери дали всичките ѝ съседи са с правилно променени числа.

Пробвай да добавиш една и съща клетка няколко пъти по HTTP. Става ли нещо странно?

И накрая да се потретя. Обърни внимание на върнатите статус кодове. Ще е жалко заради тях да не ти минат много тестове.

Ралица обнови решението на 26.01.2016 11:18 (преди над 2 години)

package main
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"sync"
)
type Cell struct {
X int64 `json:"x"`
Y int64 `json:"y"`
}
+func (c *Cell) cellToArray() (cell [2]int64) {
+ cell[0] = c.X
+ cell[1] = c.Y
+
+ return
+}
+
+/*func (c *Cell) toJsonCell() (result JsonCell) {
+ x := new(int64)
+ y := new(int64)
+
+ *x = c.X
+ *y = c.Y
+
+ result.X = x
+ result.Y = y
+
+ return
+}*/
+
+type JsonCell struct {
+ X *int64 `json:"x"`
+ Y *int64 `json:"y"`
+}
+
+func (j *JsonCell) isValid() (result bool) {
+ if j.X != nil && j.Y != nil {
+ result = true
+ }
+ return
+}
+
+func (j *JsonCell) toCell() (result Cell, err error) {
+ if j.isValid() {
+ result.X = *j.X
+ result.Y = *j.Y
+ } else {
+ err = errors.New("Cannot convert JsonCell to Cell")
+ }
+
+ return
+}
+
type GameOfLifeHandler struct {
- Generation int
- Board map[Cell]bool
- Neighbours map[Cell]int
- RespWriter http.ResponseWriter
+ Generation int
+ Board map[Cell]bool
+ Neighbours map[Cell]int
+
BoardMutex sync.Mutex
NeighboursMutex sync.Mutex
RequestMutex sync.RWMutex
}
/* util functions */
-func (g *GameOfLifeHandler) sendError(err error) {
- g.RespWriter.Header().Set("Error", err.Error())
- g.RespWriter.WriteHeader(http.StatusBadRequest)
+func sendError(w http.ResponseWriter, err error) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(err.Error()))
}
-func (g *GameOfLifeHandler) parseXYQuery(r *http.Request) (result Cell, err error) {
+func parseXYQuery(w http.ResponseWriter, r *http.Request) (result Cell, err error) {
values, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
- g.sendError(err)
+ sendError(w, err)
return
}
// validate
if len(values) != 2 {
err = errors.New("Invalid parameters query - invalid number of query parameters")
- g.sendError(err)
+ sendError(w, err)
return
}
- xString := values.Get("x")
- yString := values.Get("y")
+ xString := r.FormValue("x")
+ yString := r.FormValue("y")
if xString == "" || yString == "" {
err = errors.New("Invalid parameters query - x or y not set")
- g.sendError(err)
+ sendError(w, err)
return
}
x, err := strconv.ParseInt(xString, 10, 64)
if err != nil {
- g.sendError(err)
+ sendError(w, err)
return
}
y, err := strconv.ParseInt(yString, 10, 64)
if err != nil {
- g.sendError(err)
+ sendError(w, err)
return
}
result.X = x
result.Y = y
return
}
func updateNeighbours(cell Cell, neighbours *map[Cell]int) {
var neighbour Cell
- for i := cell.X - 1; i < cell.X+1; i++ {
- for j := cell.Y - 1; j < cell.Y+1; j++ {
+ for i := cell.X - 1; i <= cell.X+1; i++ {
+ for j := cell.Y - 1; j <= cell.Y+1; j++ {
if i == cell.X && j == cell.Y {
continue
}
neighbour.X = i
neighbour.Y = j
(*neighbours)[neighbour]++
}
}
}
/* url handlers */
-func (g *GameOfLifeHandler) addNewCells(r *http.Request) (err error) {
- var cellSlice []Cell
- cellSlice = make([]Cell, 0, 2)
+func (g *GameOfLifeHandler) addNewCells(w http.ResponseWriter, r *http.Request) (err error) {
+ var cellSlice []JsonCell
+ cellSlice = make([]JsonCell, 0)
jsonBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
- g.sendError(err)
+ sendError(w, err)
return
}
err = json.Unmarshal(jsonBytes, &cellSlice)
if err != nil {
- g.sendError(err)
+ sendError(w, err)
return
}
- for _, cell := range cellSlice {
+ var cell Cell
+
+ for _, jsonCell := range cellSlice {
+ cell, err = jsonCell.toCell()
+ if err != nil {
+ sendError(w, err)
+ return
+ }
+
g.BoardMutex.Lock()
+
+ if g.Board[cell] {
+ g.BoardMutex.Unlock()
+ continue
+ }
+
g.Board[cell] = true
g.BoardMutex.Unlock()
g.NeighboursMutex.Lock()
updateNeighbours(cell, &g.Neighbours)
g.NeighboursMutex.Unlock()
}
- return err
+ return
}
// writes json as a response
func (g *GameOfLifeHandler) checkCell(w http.ResponseWriter, cell Cell) (err error) {
alive := make(map[string]bool)
+
alive["alive"] = g.Board[cell]
output, err := json.Marshal(alive)
if err != nil {
- g.sendError(err)
+ sendError(w, err)
return
}
+ w.WriteHeader(http.StatusOK)
w.Write(output)
return
}
// writes json as a response
func (g *GameOfLifeHandler) generation(w http.ResponseWriter) {
- generation := make([]Cell, 0, len(g.Board))
+ generation := make([][2]int64, 0, len(g.Board))
+ var arrayCell [2]int64
for cell := range g.Board {
- generation = append(generation, cell)
+ arrayCell = cell.cellToArray()
+
+ generation = append(generation, arrayCell)
}
currentGeneration := make(map[string]interface{})
- currentGeneration["generation"] = strconv.Itoa(g.Generation)
+ currentGeneration["generation"] = g.Generation
currentGeneration["living"] = generation
output, err := json.Marshal(currentGeneration)
if err != nil {
- g.sendError(err)
+ sendError(w, err)
return
}
- w.WriteHeader(http.StatusCreated)
+ w.WriteHeader(http.StatusOK)
w.Write(output)
}
-func (g *GameOfLifeHandler) evolve() {
+func (g *GameOfLifeHandler) evolve(w http.ResponseWriter) {
newBoard := make(map[Cell]bool)
newNeighbourBoard := make(map[Cell]int)
+ g.NeighboursMutex.Lock()
for cell, neighbours := range g.Neighbours {
+ g.BoardMutex.Lock()
if g.Board[cell] {
if neighbours == 2 || neighbours == 3 {
newBoard[cell] = true
updateNeighbours(cell, &newNeighbourBoard)
}
} else {
if neighbours == 3 {
newBoard[cell] = true
updateNeighbours(cell, &newNeighbourBoard)
}
}
+ g.BoardMutex.Unlock()
}
+ g.NeighboursMutex.Unlock()
g.BoardMutex.Lock()
g.Board = newBoard
g.BoardMutex.Unlock()
g.NeighboursMutex.Lock()
g.Neighbours = newNeighbourBoard
g.NeighboursMutex.Unlock()
g.Generation++
+
+ w.WriteHeader(http.StatusNoContent)
}
-func (g *GameOfLifeHandler) reset() {
+func (g *GameOfLifeHandler) reset(w http.ResponseWriter) {
g.Generation = 0
+
g.BoardMutex.Lock()
g.Board = make(map[Cell]bool)
g.BoardMutex.Unlock()
+
+ w.WriteHeader(http.StatusNoContent)
}
func (g *GameOfLifeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
switch {
case regexp.MustCompile(`/generation/evolve/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.Lock()
- g.evolve()
- w.WriteHeader(http.StatusOK)
+ g.evolve(w)
g.RequestMutex.Unlock()
}
case regexp.MustCompile(`/generation/`).MatchString(path):
// GET
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.RLock()
g.generation(w)
g.RequestMutex.RUnlock()
}
case regexp.MustCompile(`/cell/status/`).MatchString(path):
// GET
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.RLock()
- cell, err := g.parseXYQuery(r)
+ cell, err := parseXYQuery(w, r)
if err == nil {
g.checkCell(w, cell)
}
g.RequestMutex.RUnlock()
}
case regexp.MustCompile(`/cells/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.Lock()
- if err := g.addNewCells(r); err == nil {
+ if err := g.addNewCells(w, r); err == nil {
w.WriteHeader(http.StatusCreated)
}
g.RequestMutex.Unlock()
}
case regexp.MustCompile(`/reset/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.Lock()
- g.reset()
- w.WriteHeader(http.StatusOK)
+ g.reset(w)
g.RequestMutex.Unlock()
}
default:
w.WriteHeader(http.StatusNotFound)
}
}
func NewGameOfLifeHandler(cells [][2]int64) *GameOfLifeHandler {
board := make(map[Cell]bool)
neighbours := make(map[Cell]int)
var newCell Cell
for _, cell := range cells {
newCell.X = cell[0]
newCell.Y = cell[1]
board[newCell] = true
updateNeighbours(newCell, &neighbours)
}
gameOfLifeHandler := &GameOfLifeHandler{Generation: 0, Board: board, Neighbours: neighbours}
return gameOfLifeHandler
}

Ралица обнови решението на 26.01.2016 11:18 (преди над 2 години)

package main
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"sync"
)
type Cell struct {
X int64 `json:"x"`
Y int64 `json:"y"`
}
func (c *Cell) cellToArray() (cell [2]int64) {
cell[0] = c.X
cell[1] = c.Y
return
}
-/*func (c *Cell) toJsonCell() (result JsonCell) {
- x := new(int64)
- y := new(int64)
-
- *x = c.X
- *y = c.Y
-
- result.X = x
- result.Y = y
-
- return
-}*/
-
type JsonCell struct {
X *int64 `json:"x"`
Y *int64 `json:"y"`
}
func (j *JsonCell) isValid() (result bool) {
if j.X != nil && j.Y != nil {
result = true
}
return
}
func (j *JsonCell) toCell() (result Cell, err error) {
if j.isValid() {
result.X = *j.X
result.Y = *j.Y
} else {
err = errors.New("Cannot convert JsonCell to Cell")
}
return
}
type GameOfLifeHandler struct {
Generation int
Board map[Cell]bool
Neighbours map[Cell]int
BoardMutex sync.Mutex
NeighboursMutex sync.Mutex
RequestMutex sync.RWMutex
}
/* util functions */
func sendError(w http.ResponseWriter, err error) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
}
func parseXYQuery(w http.ResponseWriter, r *http.Request) (result Cell, err error) {
values, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
sendError(w, err)
return
}
// validate
if len(values) != 2 {
err = errors.New("Invalid parameters query - invalid number of query parameters")
sendError(w, err)
return
}
xString := r.FormValue("x")
yString := r.FormValue("y")
if xString == "" || yString == "" {
err = errors.New("Invalid parameters query - x or y not set")
sendError(w, err)
return
}
x, err := strconv.ParseInt(xString, 10, 64)
if err != nil {
sendError(w, err)
return
}
y, err := strconv.ParseInt(yString, 10, 64)
if err != nil {
sendError(w, err)
return
}
result.X = x
result.Y = y
return
}
func updateNeighbours(cell Cell, neighbours *map[Cell]int) {
var neighbour Cell
for i := cell.X - 1; i <= cell.X+1; i++ {
for j := cell.Y - 1; j <= cell.Y+1; j++ {
if i == cell.X && j == cell.Y {
continue
}
neighbour.X = i
neighbour.Y = j
(*neighbours)[neighbour]++
}
}
}
/* url handlers */
func (g *GameOfLifeHandler) addNewCells(w http.ResponseWriter, r *http.Request) (err error) {
var cellSlice []JsonCell
cellSlice = make([]JsonCell, 0)
jsonBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
sendError(w, err)
return
}
err = json.Unmarshal(jsonBytes, &cellSlice)
if err != nil {
sendError(w, err)
return
}
var cell Cell
for _, jsonCell := range cellSlice {
cell, err = jsonCell.toCell()
if err != nil {
sendError(w, err)
return
}
g.BoardMutex.Lock()
if g.Board[cell] {
g.BoardMutex.Unlock()
continue
}
g.Board[cell] = true
g.BoardMutex.Unlock()
g.NeighboursMutex.Lock()
updateNeighbours(cell, &g.Neighbours)
g.NeighboursMutex.Unlock()
}
return
}
// writes json as a response
func (g *GameOfLifeHandler) checkCell(w http.ResponseWriter, cell Cell) (err error) {
alive := make(map[string]bool)
alive["alive"] = g.Board[cell]
output, err := json.Marshal(alive)
if err != nil {
sendError(w, err)
return
}
w.WriteHeader(http.StatusOK)
w.Write(output)
return
}
// writes json as a response
func (g *GameOfLifeHandler) generation(w http.ResponseWriter) {
generation := make([][2]int64, 0, len(g.Board))
var arrayCell [2]int64
for cell := range g.Board {
arrayCell = cell.cellToArray()
generation = append(generation, arrayCell)
}
currentGeneration := make(map[string]interface{})
currentGeneration["generation"] = g.Generation
currentGeneration["living"] = generation
output, err := json.Marshal(currentGeneration)
if err != nil {
sendError(w, err)
return
}
w.WriteHeader(http.StatusOK)
w.Write(output)
}
func (g *GameOfLifeHandler) evolve(w http.ResponseWriter) {
newBoard := make(map[Cell]bool)
newNeighbourBoard := make(map[Cell]int)
g.NeighboursMutex.Lock()
for cell, neighbours := range g.Neighbours {
g.BoardMutex.Lock()
if g.Board[cell] {
if neighbours == 2 || neighbours == 3 {
newBoard[cell] = true
updateNeighbours(cell, &newNeighbourBoard)
}
} else {
if neighbours == 3 {
newBoard[cell] = true
updateNeighbours(cell, &newNeighbourBoard)
}
}
g.BoardMutex.Unlock()
}
g.NeighboursMutex.Unlock()
g.BoardMutex.Lock()
g.Board = newBoard
g.BoardMutex.Unlock()
g.NeighboursMutex.Lock()
g.Neighbours = newNeighbourBoard
g.NeighboursMutex.Unlock()
g.Generation++
w.WriteHeader(http.StatusNoContent)
}
func (g *GameOfLifeHandler) reset(w http.ResponseWriter) {
g.Generation = 0
g.BoardMutex.Lock()
g.Board = make(map[Cell]bool)
g.BoardMutex.Unlock()
w.WriteHeader(http.StatusNoContent)
}
func (g *GameOfLifeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
switch {
case regexp.MustCompile(`/generation/evolve/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.Lock()
g.evolve(w)
g.RequestMutex.Unlock()
}
case regexp.MustCompile(`/generation/`).MatchString(path):
// GET
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.RLock()
g.generation(w)
g.RequestMutex.RUnlock()
}
case regexp.MustCompile(`/cell/status/`).MatchString(path):
// GET
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.RLock()
cell, err := parseXYQuery(w, r)
if err == nil {
g.checkCell(w, cell)
}
g.RequestMutex.RUnlock()
}
case regexp.MustCompile(`/cells/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.Lock()
if err := g.addNewCells(w, r); err == nil {
w.WriteHeader(http.StatusCreated)
}
g.RequestMutex.Unlock()
}
case regexp.MustCompile(`/reset/`).MatchString(path):
// POST
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
g.RequestMutex.Lock()
g.reset(w)
g.RequestMutex.Unlock()
}
default:
w.WriteHeader(http.StatusNotFound)
}
}
func NewGameOfLifeHandler(cells [][2]int64) *GameOfLifeHandler {
board := make(map[Cell]bool)
neighbours := make(map[Cell]int)
var newCell Cell
for _, cell := range cells {
newCell.X = cell[0]
newCell.Y = cell[1]
board[newCell] = true
updateNeighbours(newCell, &neighbours)
}
gameOfLifeHandler := &GameOfLifeHandler{Generation: 0, Board: board, Neighbours: neighbours}
return gameOfLifeHandler
}