Skip to content

Mu : The Go Server for Challenge-16 #196

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

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
50 changes: 50 additions & 0 deletions INSTRUCTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Instructions

```code
go mod tidy
go build -o bin/qube ./src && ./bin/qube
```

The application is a REST server and runs on Port 8000

### Endpoint
```
GET /execute?command={commandString}
```

#### Commands
- Every command is a binary operation.
- The left operand is always a Distributor.
- The right operand can either be a Distributor or a Region.
- The format : `<left-operand>.<operator>.<right-operand>`

#### Operands
- A Distributor must be D<integers>
- A Region must be `<Country-Code>-<Province-Code>-<City-Code>`
- The codes shall adhere to those used in cities.csv

#### Operators
- Include : +
- Applicable to the both types of the right operand
- When the right is Distributor : D1.+.D2, it means D1 extends distributorship to D2
- When the right is Region : D1.+.IN-TN, it means D1 can distribute in IN-TN

- Exclude : -
- Applicable to the both types of the right operand
- When the right is Distributor : D1.-.D2, it means unless a Region is added in the future, D2 can't distribute in D1's Regions
- When the right is Region : D1.-.IN-TN, it means D2 cannot distribute in IN-TN
- D1.-.TN-IN means D1 cannot distribute in TN-IN

- Print : @
- The left operand must be a Distributor
- The right operand must be a Region
- The endpoint will answer the question of whether the distributor can operate in that region by "YES" or "NO"

#### Examples for Curl
```
curl "http://localhost:8000/execute?command=D1.%2B.TN-IN"
curl "http://localhost:8000/execute?command=D1.-.TN-IN"
curl "http://localhost:8000/[email protected]"
```

The command can be URL Encoded and hence, + is %2B
Binary file added bin/qube
Binary file not shown.
7 changes: 7 additions & 0 deletions commands.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
D1.+.IN
D1.+.D2
D2.+.IN-TN-KNGLM
D2.+.IN-TG
D2.+.D3
D3.+.IN-TG
D3.-.IN-TG-HYDER
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module qube-challenge-16

go 1.24.0
44 changes: 44 additions & 0 deletions src/commands/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package commands

import (
"bufio"
"log"
"os"
"qube-challenge-16/src/dist"
"qube-challenge-16/src/geo"
"sync"
)

type CommandExecutor struct {
GeoMap map[string]*geo.GeoNode
DistMap dist.DistMap
mu *sync.RWMutex
}

func GenerateCommandExecutor() *CommandExecutor {
return &CommandExecutor{
GeoMap: geo.GenerateGeoMap(),
DistMap: *dist.GenerateDistMap(),
mu: &sync.RWMutex{},
}
}

func ReadCommandsFromFile(fileName string) (commands []Command) {
file, err := os.Open(fileName)
if err != nil {
log.Fatalln("Error opening file:", err)
return
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
if command, err := GenerateCommand(scanner.Text()); err == nil {
commands = append(commands, command)
} else {
break
}
}

return commands
}
85 changes: 85 additions & 0 deletions src/commands/commands_controllers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package commands

import (
"fmt"
"log"
)

func (ce *CommandExecutor) ExecuteFromFile() {
pre_commands := ReadCommandsFromFile("commands.txt")
for _, command := range pre_commands {
log.Println("Preset command executed : ", ce.Execute(command))
}
}

func (ce *CommandExecutor) Execute(command Command) string {
switch command.Where {
case Geo:
if _, exists := ce.DistMap.Map[command.LeftOperand]; !exists {
ce.mu.Lock()
ce.DistMap.AddDistNode(command.LeftOperand)
ce.mu.Unlock()
}
switch command.Operation {
case Include:
parentDists := []string{}

ce.mu.RLock()
for parentDist := range ce.DistMap.Map[command.LeftOperand].ParentIds {
parentDists = append(parentDists, parentDist)
}
ce.mu.RUnlock()

ce.mu.Lock()
ce.GeoMap[command.RightOperand].IncludeDist(command.LeftOperand, parentDists)
ce.mu.Unlock()

return fmt.Sprintf("%s included for %s", command.LeftOperand, command.RightOperand)

case Exclude:
ce.mu.Lock()
ce.GeoMap[command.RightOperand].ExcludeDist(command.LeftOperand)
for _, childrenDist := range ce.DistMap.ChildrenMap[command.LeftOperand] {
ce.GeoMap[command.RightOperand].ExcludeDist(childrenDist)
}
ce.mu.Unlock()

return fmt.Sprintf("%s excluded for %s", command.LeftOperand, command.RightOperand)

default:
flag := "NO"
ce.mu.RLock()
parentDists := ce.DistMap.Map[command.LeftOperand].ParentIds
for dist := range ce.GeoMap[command.RightOperand].List {
if dist == command.LeftOperand {
for parentDist := range parentDists {
if _, exists := ce.GeoMap[command.RightOperand].List[parentDist]; !exists {
ce.GeoMap[command.RightOperand].ExcludeDist(command.LeftOperand)
break
}
flag = "YES"
}
break
}
}
ce.mu.RUnlock()
return flag
}
default:
switch command.Operation {
case Include:
ce.mu.Lock()
ce.DistMap.AddDistNodes(command.LeftOperand, command.RightOperand)
ce.mu.Unlock()

return fmt.Sprintf("%s included to %s", command.RightOperand, command.LeftOperand)
case Exclude:
ce.mu.Lock()
ce.DistMap.RemoveChildDist(command.LeftOperand, command.RightOperand)
ce.mu.Unlock()

return fmt.Sprintf("%s excluded from %s", command.RightOperand, command.LeftOperand)
}
}
return ""
}
83 changes: 83 additions & 0 deletions src/commands/commands_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package commands

import (
"errors"
"regexp"
"strings"
)

const DistributorIDPattern = `^D\d+$`
const GeoPattern = `^([A-Z]{0,6})(-[A-Z]{0,6}){0,2}$`

type OperationMode string

const (
Include OperationMode = "+"
Exclude OperationMode = "-"
Print OperationMode = "@"
)

type WhichTree string

const (
Geo WhichTree = "Geo"
Dist WhichTree = "Dist"
)

type Command struct {
LeftOperand string
Operation OperationMode
RightOperand string
Where WhichTree
}

func validateDist(operand string) error {
DistributorRegex := regexp.MustCompile(DistributorIDPattern)
if valid := DistributorRegex.MatchString(operand); !valid {
return errors.New("invalid distributor")
}
return nil
}

func validateGeo(operand string) error {
GeoRegex := regexp.MustCompile(GeoPattern)
if valid := GeoRegex.MatchString(operand); !valid {
return errors.New("invalid geo")
}
return nil
}

func GenerateCommand(commandString string) (Command, error) {
parts := strings.Split(commandString, ".")

var command Command

if err := validateDist(parts[0]); err == nil {
command.LeftOperand = parts[0]
} else {
return Command{}, err
}

switch parts[1] {
case "+":
command.Operation = Include
case "-":
command.Operation = Exclude
case "@":
command.Operation = Print
default:
return Command{}, errors.New("invalid operation")
}

if err := validateDist(parts[2]); err == nil {
command.Where = Dist
command.RightOperand = parts[2]
} else if err := validateGeo(parts[2]); err == nil {
command.Where = Geo
command.RightOperand = parts[2]
} else {
return Command{}, errors.New("invalid dist/geo")
}

return command, nil
}
40 changes: 40 additions & 0 deletions src/csv/csv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package csv

import (
"encoding/csv"
"log"
"os"
)

type GeoCSVRecord struct {
Country string
Province string
City string
}

func ReadFromCSV(filename string) [][]string {
file, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer file.Close()

reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
log.Fatal(err)
}
return records
}

func ReadGeoFromCSV() (resp []GeoCSVRecord) {
records := ReadFromCSV("cities.csv")
for _, record := range records[1:] {
resp = append(resp, GeoCSVRecord{
City: record[0],
Province: record[1],
Country: record[2],
})
}
return resp
}
32 changes: 32 additions & 0 deletions src/dist/dist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package dist

import (
"qube-challenge-16/src/tree"
)

type DistNode struct {
tree.TreeNode
Id string
ParentIds map[string]bool
}

func createDistNode(id string, parentId string) (dn *DistNode) {
dn = &DistNode{
Id: id,
ParentIds: make(map[string]bool),
TreeNode: tree.TreeNode{
Children: make(map[string]tree.Node),
},
}
dn.ParentIds[parentId] = true
return dn
}

func (d *DistNode) Traverse(operation func(tree.Node)) {
operation(d)
d.TreeNode.Traverse(operation)
}

func (d *DistNode) RemoveBranch(childId string) {
delete(d.Children, childId)
}
12 changes: 12 additions & 0 deletions src/dist/dist_operations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package dist

import "qube-challenge-16/src/tree"

func (d *DistNode) ListChildren() []string {
ChildrenList := []string{}
listOperation := func(di tree.Node) {
ChildrenList = append(ChildrenList, di.(*DistNode).Id)
}
d.Traverse(listOperation)
return ChildrenList
}
Loading