Skip to content
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

問題1-2を解いた #2

Merged
merged 12 commits into from Jun 25, 2019
57 changes: 57 additions & 0 deletions README.md
@@ -0,0 +1,57 @@
# pfn-intern-task-2019

## 概要

PFN夏季インターンシップのコーディング課題公開されているので、バックエンドの課題をやってみました。

[2019年 PFN夏季インターンシップのコーディング課題公開](https://research.preferred.jp/2019/06/internship-coding-task-2019/)

## 事前準備
```bash
git clone https://github.com/naoki-kishi/pfn-intern-task-2019.git

```

## 実行方法

### サーバ
```bash
cd pfn-intern-task-2019/job_server
go run main.go
```

```bash
$ curl "localhost:8080?time=00:00:00" -i
HTTP/1.1 200 OK
Date: Tue, 25 Jun 2019 13:19:35 GMT
Content-Length: 63
Content-Type: text/plain; charset=utf-8

[JobID]
0

[Created]
00:00:00

[Priority]
Low

[Tasks]
7
3
6
6

```

### ワーカー
```bash
cd pfn-intern-task-2019/worker
go run main.go > executing_point.csv
```

### グラフ生成
```bash
cd pfn-intern-task-2019/worker
python generate_graph.py
```
6 changes: 3 additions & 3 deletions job_server/tests/sample_data/test_00001.job
Expand Up @@ -8,7 +8,7 @@
Low

[Tasks]
7
3
6
6
8
10
1
Binary file added worker/1-2.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
85 changes: 85 additions & 0 deletions worker/client/client.go
@@ -0,0 +1,85 @@
package client

import (
"bufio"
"fmt"
"net/http"
"net/url"
"strconv"
"time"

"github.com/naoki-kishi/pfn-intern-task-2019/worker/domain"
)

type Client struct {
addr string
}

func NewClient(addr string) *Client {
return &Client{addr}
}

func (c *Client) GetJob(t time.Time) (*domain.Job, error) {

timeStr := t.Format("15:04:05")
query := url.Values{}
query.Add("time", timeStr)
resp, err := http.Get(c.addr + "?" + query.Encode())
if err != nil {
return nil, fmt.Errorf("failed to request job: %s", err)
}

if resp.StatusCode == http.StatusNotFound {
return nil, &domain.JobNotFoundError{}
} else if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("response is not OK status=%d", resp.StatusCode)
}

defer resp.Body.Close()
scanner := bufio.NewScanner(resp.Body)

job := &domain.Job{}

for scanner.Scan() {
switch scanner.Text() {
case "[JobID]":
scanner.Scan()
idStr := scanner.Text()
id, err := strconv.Atoi(idStr)
if err != nil {
return nil, fmt.Errorf("failed to paser job id: %s", err)
}
job.ID = id

case "[Created]":
scanner.Scan()
timeStr := scanner.Text()
t, err := time.Parse("15:04:05", timeStr)
if err != nil {
return nil, fmt.Errorf("failed to paser created time: %s", err)
}
job.Created = t

case "[Priority]":
scanner.Scan()
p := scanner.Text()
if p == "Low" {
job.Priority = domain.Low
} else if p == "High" {
job.Priority = domain.High
}

case "[Tasks]":
for scanner.Scan() {
taskStr := scanner.Text()
task, err := strconv.Atoi(taskStr)
if err != nil {
return nil, fmt.Errorf("failed to paser task: %s", err)
}
job.Tasks = append(job.Tasks, task)
}
}

}
return job, nil
}
93 changes: 93 additions & 0 deletions worker/client/client_test.go
@@ -0,0 +1,93 @@
package client

import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"time"

"github.com/naoki-kishi/pfn-intern-task-2019/worker/domain"
)

func TestClient_GetJob(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("time")

if q == "05:13:10" {
data, err := ioutil.ReadFile("../tests/sample_data/test_00001.job")
if err != nil {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "なし")
}

w.Write(data)
} else {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "なし")
}
}))

defer testServer.Close()

type fields struct {
port int
addr string
}
type args struct {
t time.Time
}
tests := []struct {
name string
fields fields
args args
want *domain.Job
wantErr bool
}{
{
name: "存在するJob",
fields: fields{
addr: testServer.URL,
},
args: args{
t: time.Date(0, 1, 1, 5, 13, 10, 0, time.UTC),
},
want: &domain.Job{
ID: 0,
Created: time.Date(0, 1, 1, 5, 13, 10, 0, time.UTC),
Priority: domain.Low,
Tasks: []int{3, 8, 10, 1},
},
wantErr: false,
},
{
name: "存在しないJob",
fields: fields{
addr: testServer.URL,
},
args: args{
t: time.Date(0, 1, 1, 5, 14, 10, 0, time.UTC),
},
want: nil,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Client{
addr: tt.fields.addr,
}
got, err := c.GetJob(tt.args.t)
if (err != nil) != tt.wantErr {
t.Errorf("Client.GetJob() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Client.GetJob() = %v, want %v", got, tt.want)
}
})
}
}
8 changes: 8 additions & 0 deletions worker/domain/error.go
@@ -0,0 +1,8 @@
package domain

type JobNotFoundError struct {
}

func (e *JobNotFoundError) Error() string {
return "job not found"
}
37 changes: 37 additions & 0 deletions worker/domain/job.go
@@ -0,0 +1,37 @@
package domain

import (
"time"
)

type Priority int

const (
Low Priority = iota
High
)

type Job struct {
ID int
Created time.Time
Priority Priority
Tasks []int
CurrentTask int
}

func (j *Job) Work(secs int) (point int, done bool) {
for i := 0; i < secs; i++ {
j.Tasks[j.CurrentTask]--

if j.Tasks[j.CurrentTask] == 0 {
j.CurrentTask++
}

//すべてのタスクが完了したので削除する (TODO 計算量がO(n)なので別の本心を考える)
if j.CurrentTask == len(j.Tasks) {
return 0, true
}
}
return j.Tasks[j.CurrentTask], false

}