20201119のGoに関する記事は5件です。

LeetCodeに毎日挑戦してみた 9. Palindrome Number (Python、Go)

はじめに

無料英単語サイトE-tanを運営中の@ishishowです。

プログラマとしての能力を上げるために毎日leetcodeに取り組み、自分なりの解き方を挙げていきたいと思います。

Leetcodeとは

leetcode.com
ソフトウェア開発職のコーディング面接の練習といえばこれらしいです。
合計1500問以上のコーデイング問題が投稿されていて、実際の面接でも同じ問題が出されることは多いらしいとのことです。

Go言語入門+アルゴリズム脳の強化のためにGolangとPythonで解いていこうと思います。(Pythonは弱弱だが経験あり)

3問目(問題09)

Palindrome Number

  • 問題内容(日本語訳)

整数が回文であるかどうかを判別します。整数は、前方と後方で同じように読み取られる場合、回文です。

フォローアップ:整数を文字列に変換せずに解決できますか?

Example 1:

  Input: x = 121
  Output: true

Example 2:

  Input: x = -121
  Output: false
  Explanation: From left to right, it reads -121. From right to left, it becomes 121-. Therefore it is not a palindrome.

Example 3:

  Input: x = 10
  Output: false
  Explanation: Reads 01 from right to left. Therefore it is not a palindrome.

Example 4:

  Input: x = -101
  Output: false

考え方

  1. 正負判定をして正のみ処理を行う。
  2. 文字列にして逆順にして判定

説明

  1. 負の場合は回文になりません。
  2. str関数を使うとintをstrにできます。そしてスライスを使って逆順に。。。(問題で推奨されていません)
  • 解答コード
  class Solution(object):
      def isPalindrome(self, x):
          return str(x) == str(x)[::-1]

スライスを使って文字列を逆順にして判定です!
こちら@shiracamus様よりアドバイスをいただきました。
比較式は値が真偽値になるため条件式が省略できます。
以前のコード↓

 def isPalindrome(self, x):
      if x >= 0:
          if str(x) == str(x)[::-1]:
              return True
      return False
  • Goでも書いてみます!
  import "strconv"
  func isPalindrome(x int) bool {
    s := strconv.Itoa(x)
    r := []rune(s)
    for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
        if r[i] != r[j] {
            return false
        }
    }
    return true
  }

解き方は同じですが、Goの場合は文字列はイミュータブル(不変)のためにこの書き方になります。

また、strconvパッケージをインポートしてItoaを使いました。Integer To a です。

GoとPythonの実行時間

左から、RunTime, Memory, 言語です。
キャプチャ.PNG

別解

前日に解いた 9.Reverse Integer の仕組みを使って数字のまま解きました。(こちらが推奨です。)

func isPalindrome(x int) bool {
    if x<0{
        return false
    }
    new_var := x 
    rev := 0
    for x!=0{
        pop := x%10
        x = x/10

        rev = rev *10 + pop
    }
    if rev==new_var{
        return true
    }else{
        return false
    }
}
  • 自分メモ(Go)

文字列を文字単位で扱うには rune 型を使う

Stringは[]runeと[]byteにキャストできる。

runeの実態はint32になっていて、unicodeの4byte分を表現するのに十分なサイズ

stringrune 配列は相互変換できるので,文字列を切り取る場合は

  package main

  import "fmt"

  func main() {
      nihongo := "日本語"

      fmt.Printf("nihongo = %s\n", nihongo)
      fmt.Printf("nippon = %s\n", string([]rune(nihongo)[:2]))
  }

のように string[]runestring と変換していけば安全に処理できる。

実行結果

  nihongo = 日本語
  nippon = 日本

参考にした記事

はじめての Go 言語 (on Windows) その4

Goのruneを理解するためのUnicode知識

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

goのjson.Marshalで空のオブジェクトが返ってくる時の解決法

空のオブジェクトが入るとき

func A() string {
    type ResponseData struct {
        //先頭が小文字なので公開されてない
        name  string
        value int64
    }
    var rd []ResponseData
    rd = append(rd,ResponseData{
        name:"aaa",
        value:1
   })
    resRaw, _ := json.Marshal(rd)
    resultJSON := string(resRaw)
    return resultJSON
}

ResponseDataのフィールドが公開されていません。
これをすると、resultJSONに[{}]←こんなんが入ります。

OKなやつ

func A() string {
    type ResponseData struct {
        //先頭が大文字なので公開されている
        Name  string
        Value int64
    }
    var rd []ResponseData
    rd = append(rd,ResponseData{
        Name:"aaa",
        Value:1
   })
    resRaw, _ := json.Marshal(rd)
    resultJSON := string(resRaw)
    return resultJSON
}

こうするとResponseDataのフィールドがエクスポートされているので、結果的にresultJSONにちゃんと値が入ります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Elasticsearch + Sudachi + Go + echoで城検索APIを作ってみた

ElasticsearchとGo言語の学習がてらに検索APIを作ってみたので、成果物の利用方法、構成について、困ったことについてまとめてみました。

実行環境
https://github.com/takenoko-gohan/castle-search-api-environment
検索API
https://github.com/takenoko-gohan/castle-search-api

環境構築

環境構築では、dockerとdocker-composeを使用します。

git clone https://github.com/takenoko-gohan/castle-search-api-environment.git
cd castle-search-api-environment
docker-compose build --no-cache
docker-compose up -d
# elasticsearch 起動後しばらくしてから実行してください
sh es/script/es_init.sh 

利用方法

検索APIを利用する際は下記のような形でリクエストします。クエリパラメーター「keyword」では検索時のキーワードを指定します。クエリパラメーター「prefecture」では絞り込みたい都道府県を指定します。
下記コマンドは、都道府県が「福島県」でキーワード「鶴ヶ城」を含まれている城を検索します。

curl -XGET "http://localhost:8080/search?keyword=鶴ヶ城&prefecture=福島県"

構成

Elasticsearch

インデックスの設定

インデックスは下記のとおりに設定しました。
検索時、インデックス時のanalyzerは、トークンに分割するときにsearchモードを使用、品詞が助詞・助動詞・句点・読点のものは削除、トークンをSudachiNormalizedFormAttributeに変化するように設定しています。


index_settings.json
{
  "settings": {
    "index": {
      "number_of_shards": 1,
      "number_of_replicas": 0,
      "analysis": {
        "tokenizer": {
          "sudachi_tokenizer": {
            "type": "sudachi_tokenizer",
            "split_mode": "C",
            "discard_punctuation": true,
            "resources_path": "/usr/share/elasticsearch/config/sudachi",
            "settings_path": "/usr/share/elasticsearch/config/sudachi/sudachi.json"
          }
        },
        "analyzer": {
          "sudachi_analyzer": {
            "filter": [
              "my_searchfilter",
              "my_posfilter",
              "sudachi_normalizedform"
            ],
            "tokenizer": "sudachi_tokenizer",
            "type": "custom"
          }
        },
        "filter":{
          "my_searchfilter": {
            "type": "sudachi_split",
            "mode": "search"
          },
          "my_posfilter":{
            "type":"sudachi_part_of_speech",
            "stoptags":[
              "助詞",
              "助動詞",
              "補助記号,句点",
              "補助記号,読点"
            ]
          }
        }
      }
    }
  }
}


インデックスのマッピング

インデックスのマッピングは下記のとおりにしました。

フィールド タイプ 備考
name text 城の名前
prefectures keyword 都道府県
rulers text 城の城主
description text 城の概要


index_mappings.json
{
  "properties": {
    "name": {"type" : "text", "analyzer": "sudachi_analyzer"},
    "prefecture": {"type": "keyword"},
    "rulers": {"type": "text", "analyzer": "sudachi_analyzer"},
    "description": {"type": "text", "analyzer": "sudachi_analyzer"}
  }
}


ドキュメント

検索用インデックスにはWikipediaの「Category:日本100名城」をもとに作成したデータを挿入しています。

検索API

検索APIはGo言語のフレームワーク「echo」とElasticsearchクライアント「go-elasticsearch」を使用して作成しました。
APIでは最初に受け取ったパラメーターをもとにElasticsearchへのクエリを作成・実行し、検索にヒットしたドキュメントの各フィールドをそのままクライアントにレスポンスする簡単な作りになっています。

クエリパラメーター「keyword」を使用しての検索では、boostを使用して「name > rulers > description」の順でスコアの重み付けをしております。
クエリパラメーター「prefecture」を使用しての検索では、フィールド「prefecture」に対する完全一致検索を行うようにしています。


クエリの作成
package search

func createQuery(q *Query) map[string]interface{} {
    query := map[string]interface{}{}
    if q.Keyword != "" && q.Prefecture != "" {
        query = map[string]interface{}{
            "query": map[string]interface{}{
                "bool": map[string]interface{}{
                    "must": []map[string]interface{}{
                        {
                            "bool": map[string]interface{}{
                                "should": []map[string]interface{}{
                                    {
                                        "match": map[string]interface{}{
                                            "name": map[string]interface{}{
                                                "query": q.Keyword,
                                                "boost": 3,
                                            },
                                        },
                                    },
                                    {
                                        "match": map[string]interface{}{
                                            "rulers": map[string]interface{}{
                                                "query": q.Keyword,
                                                "boost": 2,
                                            },
                                        },
                                    },
                                    {
                                        "match": map[string]interface{}{
                                            "description": map[string]interface{}{
                                                "query": q.Keyword,
                                                "boost": 1,
                                            },
                                        },
                                    },
                                },
                                "minimum_should_match": 1,
                            },
                        },
                        {
                            "bool": map[string]interface{}{
                                "must": []map[string]interface{}{
                                    {
                                        "term": map[string]interface{}{
                                            "prefecture": q.Prefecture,
                                        },
                                    },
                                },
                            },
                        },
                    },
                },
            },
        }
    } else if q.Keyword != "" && q.Prefecture == "" {
        query = map[string]interface{}{
            "query": map[string]interface{}{
                "bool": map[string]interface{}{
                    "should": []map[string]interface{}{
                        {
                            "match": map[string]interface{}{
                                "name": map[string]interface{}{
                                    "query": q.Keyword,
                                    "boost": 3,
                                },
                            },
                        },
                        {
                            "match": map[string]interface{}{
                                "rulers": map[string]interface{}{
                                    "query": q.Keyword,
                                    "boost": 2,
                                },
                            },
                        },
                        {
                            "match": map[string]interface{}{
                                "description": map[string]interface{}{
                                    "query": q.Keyword,
                                    "boost": 1,
                                },
                            },
                        },
                    },
                    "minimum_should_match": 1,
                },
            },
        }
    } else if q.Keyword == "" && q.Prefecture != "" {
        query = map[string]interface{}{
            "query": map[string]interface{}{
                "bool": map[string]interface{}{
                    "must": []map[string]interface{}{
                        {
                            "term": map[string]interface{}{
                                "prefecture": q.Prefecture,
                            },
                        },
                    },
                },
            },
        }
    }

    return query
}


困ったとこ

ひとまず作成したところで動作確認をしたときに、下記のレスポンスを受け取りました。

curl -XGET "http://localhost:8080/search?keyword=若松城&prefectures=福島県"
{
    "message": "検索に成功しました。",
    "Results": [
        {
            "name": "若松城",
            "prefecture": "福島県",
            "rulers": [
                "蒲生氏、上杉氏、加藤氏、保科氏・会津松平家"
            ],
            "description": "若松城(わかまつじょう)は、福島県会津若松市追手 町1-1にあった日本の城である。地元では一般的に鶴ヶ城(つるがじょう)といい、地元以外では会津若松城と呼ばれることも多い。文献史上では黒川城(くろかわじょう)、会津城とされることもある。国の史跡としては、若松城跡(わかまつじょうあと)の名称で指定されている。"
        },
        {
            "name": "二本松城",
            "prefecture": "福島県",
            "rulers": [
                "加藤氏",
                "丹羽氏",
                "蒲生氏",
                "二本松氏",
                "上杉氏",
                "伊達氏"
            ],
            "description": "二本松城(にほんまつじょう)は、福島県二本松市郭内にある日本の城(平山城)。日本100名城の一つ。別名、霞ヶ城・白旗城と呼ばれる。平成19年(2007年)7月26日、二本松城跡として国の史跡に指定された。「霞ヶ城公園」として日本さくら名所100 選に選定されている。"
        },
        {
            "name": "白河小峰城",
            "prefecture": "福島県",
            "rulers": [
                "松平氏",
                "丹羽氏",
                "白河結城氏",
                "蒲生氏",
                "阿部氏_(徳川譜代)"
            ],
            "description": "白河小峰城(しらかわこみねじょう)は、福島県白河市(陸奥国白河郡白河)にあった日本の城。単に白河城または小峰城ともいう。国の史跡に指定されている。ほか、日本100名城のひとつに数えられる。"
        }
    ]
}

検索結果は若松城のみヒットすること想定したのですが、福島県に存在する他の城もヒットしていました。
そこで下記のコマンドどのように解析されるか確認したところ、若松城は「若松/城」で分かち書きされるようです。
そのため、検索時に分かち書きされた「城」で他の城もヒットしてしまったようです。

curl -XGET 'http://localhost:9200/castle/_analyze?pretty' -H 'Content-Type: application/json' -d '
{
  "text": "若松城",
  "analyzer": "sudachi_analyzer"
}'
{
  "tokens" : [
    {
      "token" : "若松",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "城",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "word",
      "position" : 1
    }
  ]
}

そこで、こちらの記事を参考に下記のような形でCSVファイルを作成し、analyzerに各城の名前が記載されいるユーザー辞書を登録しました。

若松城,4786,4786,5000,若松城,名詞,固有名詞,一般,*,*,*,ワカマツジョウ,若松城,*,*,*,*,*

ユーザー辞書登録後、解析結果を確認しましたが、今度は固有名詞「若松城」となりました。

curl -XGET 'http://localhost:9200/castle/_analyze?pretty' -H 'Content-Type: application/json' -d '
{
  "text": "若松城",
  "analyzer": "sudachi_analyzer"
}'
{
  "tokens" : [
    {
      "token" : "若松城",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "word",
      "position" : 0
    }
  ]
}

再度、検索APIで検索したところ想定どおり若松城のみヒットするようになりました。

curl -XGET "http://localhost:8080/search?keyword=若松城&prefecture=福島県"
{
    "message": "検索に成功しました。",
    "Results": [
        {
            "name": "若松城",
            "prefecture": "福島県",
            "rulers": [
                "蒲生氏、上杉氏、加藤氏、保科氏・会津松平家"
            ],
            "description": "若松城(わかまつじょう)は、福島県会津若松市追手 町1-1にあった日本の城である。地元では一般的に鶴ヶ城(つるがじょう)といい、地元以外では会津若松城と呼ばれることも多い。文献史上では黒川城(くろかわじょう)、会津城とされることもある。国の史跡としては、若松城跡(わかまつじょうあと)の名称で指定されている。"
        }
    ]
}

しかし、別の問題が発生しました。
今度は、keywordを若松、prefectureを福島県で検索したところ何もヒットしませんでした。
ユーザー辞書を登録したことによって、若松城が「若松/城」で分かち書きされなくなったためヒットしなかったようです。

curl -XGET "http://localhost:8080/search?keyword=若松&prefecture=福島県"
{
    "message": "検索に成功しました。",
    "Results": null
}

こちらによるとCSVファイルの16列目にA単位に分割するための情報を記載することができるようです。
そこでCSVファイルを下記の形で修正し、searchモードでC単位とA単位で分割できるようにしました。(若松城、二本松城、白河小峰城だけですが...)

若松城,4786,4786,5000,若松城,名詞,固有名詞,一般,*,*,*,ワカマツジョウ,若松城,*,C,650091/368637,*,*
二本松城,4786,4786,5000,二本松城,名詞,固有名詞,一般,*,*,*,ニホンマツジョウ,二本松城,*,C,281483/368637,*,*
白河小峰城,4786,4786,5000,白河小峰城,名詞,固有名詞,一般,*,*,*,シラカワコミネジョウ,白河小峰城,*,C,584799/394859/368637,*,*
curl -XGET 'http://localhost:9200/castle/_analyze?pretty' -H 'Content-Type: application/json' -d '
{
  "text": "若松城",
  "analyzer": "sudachi_analyzer"
}'
{
  "tokens" : [
    {
      "token" : "若松城",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "word",
      "position" : 0,
      "positionLength" : 2
    },
    {
      "token" : "若松",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "城",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "word",
      "position" : 1
    }
  ]
}

これで、keywordを若松、prefectureを福島県で検索しても若松城がヒットするようになりました。
思ったとおりに検索できるようにするのは中々大変ですね。

curl  -XGET "http://localhost:8080/search?keyword=若松城&prefecture=福島県"
{
    "message": "検索に成功しました。",
    "Results": [
        {
            "name": "若松城",
            "prefecture": "福島県",
            "rulers": [
                "蒲生氏、上杉氏、加藤氏、保科氏・会津松平家"
            ],
            "description": "若松城(わかまつじょう)は、福島県会津若松市追手 町1-1にあった日本の城である。地元では一般的に鶴ヶ城(つるがじょう)といい、地元以外では会津若松城と呼ばれることも多い。文献史上では黒川城(くろかわじょう)、会津城とされることもある。国の史跡としては、若 松城跡(わかまつじょうあと)の名称で指定されている。"
        }
    ]
}

参考

Elasticsearch + Sudachi + Docker でユーザー辞書を作ってみるハンズオン
Sudachi ユーザー辞書作成方法
elasticsearch-sudachi README
go-elasticsearch README

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】標準入力から受け取ったポケモンの種族値を返すツールを作成

こんにちは、ヤマウチです!
2020/11/14に弊社の福岡支店にてデブキャンを行いました。
泊りがけでなんかしてそうな名前ですが、1年半くらい前から最低月1以上継続して行っているものの、未だに日帰りしかありません。。。
まあそれは置いとくとして、今回作ったツールを紹介していこうと思います!
ちなみにGo言語に関してはスーパー初学者でして、ツールみたいなのを作ったのも初めてなので、コードは色々と問題があると思いますw

今回のツールをざっくり紹介

今回のツールですが、APIとそれを叩く側を別で作成しました。
これがリポジトリです。
API:https://github.com/y-keisuke/pokemon
叩く側:https://github.com/y-keisuke/pokemon_command
レビューしてやるかってくらいの気持ちで見てくださいw

本当は一つのリポジトリにまとめたかったんですが、それが可能なのかもよくわからずにリポジトリを分けたので、あとから使ったコマンドの方だけリポジトリにcommandってついてます。っていうどうでもいい話。

ということでざっくり紹介ということで動きとしてはこんな感じです↓
(動きがもっさりしてて申し訳、計4回叩いてます)
pokemon.gif

すごいざっくりですが、こんな感じです。

API側の紹介

まずAPI側ですが、こちら↓のリポジトリからjsonを拝借しました。
https://github.com/fanzeyi/pokemon.json/blob/master/pokedex.json

db.go

本当はDBを用意したかったんですが、面倒だったのでjsonをそのまま使いました←
それがdb.goです。

package db

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "os"
)

type PokemonCollection struct {
    Pokemons []PokemonData
}

type PokemonData struct {
    Id   int         `json:"id"`
    Name PokemonName `json:"name"`
    Type []string    `json:"type"`
    Base PokemonBase `json:"base"`
}

type PokemonName struct {
    English  string `json:"english"`
    Japanese string `json:"japanese"`
    Chinese  string `json:"chinese"`
    French   string `json:"french"`
}

type PokemonBase struct {
    HP        int `json:"hp"`
    Attack    int `json:"attack"`
    Defense   int `json:"defense"`
    SpAttack  int `json:"spattack"`
    SpDefense int `json:"spdefense"`
    Speed     int `json:"speed"`
}

func GetPokemonCollection() PokemonCollection {
    raw, err := ioutil.ReadFile("./pokedex.json")
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }
    var pokemonCollection PokemonCollection
    json.Unmarshal(raw, &pokemonCollection)

    return pokemonCollection
}

上でいくつか定義している構造体はjsonの構造とまったく同じにしています。
拝借したjsonから少しだけ構造は変わってます。
ここで定義しているGetPokemonCollection()でまるっと構造体にjsonのデータをブチ込んでいます。
具体的には

json.Unmarshal(raw, &pokemonCollection)

この部分で構造体に詰めています。
この構造体をなんちゃってDBとして使います。

main.go

続いてmain.goです。

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    pokemon2 "pokemon/pokemon"
)

func pokemonToJson(w http.ResponseWriter, r *http.Request) {
    name := r.FormValue("name")
    pokemon, err := pokemon2.GetPokemonBy(name)

    // ここでいい感じにエラー情報のjsonを返す方法がわからなかった
    if err != nil {
        log.Writer()
        http.Error(w, fmt.Sprintf("{\"err\":\"%s\"}", err), 200)
        return
    }
    pokemonJson, _ := json.Marshal(pokemon)
    fmt.Fprint(w, fmt.Sprintf("%+v", string(pokemonJson)))
}

func handleRequests() {
    http.HandleFunc("/", pokemonToJson)
    log.Fatal(http.ListenAndServe(":18888", nil))
}

func main() {
    handleRequests()
}

ここでやっていることは、「ポート18888でアクセスを待ち受けて、そのアクセスのパラメータを見て、ポケモンのデータを取得し、jsonで種族値を返す」ということです。

まずポート18888で待ち受けている部分

// ポート18888をlisten
http.ListenAndServe(":18888", nil)

続いて、そのアクセスのパラメータを見て

// nameというkeyのパラメータを取得
name := r.FormValue("name")

さらに、ポケモンのデータを取得し

// pokemon2(詳細後述)のGetPokemonByでポケモンを取得
pokemon, err := pokemon2.GetPokemonBy(name)

最後に、jsonで種族値を返す

// 構造体をjsonに変換してから返す
pokemonJson, _ := json.Marshal(pokemon)
fmt.Fprint(w, fmt.Sprintf("%+v", string(pokemonJson)))

Go?なにそれ食えんの?な人から「返さずプリントしてるやん!」っていう声も聞こえそうなので補足しておくと、Fprintというのが書き込み先を指定できる関数で、今回はw(http.ResponseWriter)に書き込んでいるので、結果レスポンスとして返るって感じです。

pokemon.go

pokemon2と書いてあったのがこいつです。
プロジェクト名と被せちゃったので2です。
かなりNGな命名です。
golandがよしなにしてくれたんで…

package pokemon

import (
    "errors"
    "pokemon/db"
)

type Pokemon struct {
    Name      string `json:"name"`
    HP        int    `json:"hp"`
    Attack    int    `json:"attack"`
    Defense   int    `json:"defense"`
    SpAttack  int    `json:"sp_attack"`
    SpDefense int    `json:"sp_defense"`
    Speed     int    `json:"speed"`
}

func GetPokemonBy(name string) (*Pokemon, error) {
    pokemonCollection := getPokemonCollection()
    for _, pokemon := range pokemonCollection.Pokemons {
        if pokemon.Name.Japanese == name {
            return getPokemonStruct(pokemon), nil
        }
    }
    return nil, errors.New("ポケモンが見つかりません")
}

func getPokemonCollection() db.PokemonCollection {
    return db.GetPokemonCollection()
}

func getPokemonStruct(pokemon db.PokemonData) *Pokemon {
    return &Pokemon{
        Name: pokemon.Name.Japanese,
        HP: pokemon.Base.HP,
        Attack: pokemon.Base.Attack,
        Defense: pokemon.Base.Defense,
        SpAttack: pokemon.Base.SpAttack,
        SpDefense: pokemon.Base.SpDefense,
        Speed: pokemon.Base.Speed}
}

とりあえずmain.goから呼び出していたGetPokemonByという関数の説明だけします。

// なんちゃってDBを取得している
pokemonCollection := getPokemonCollection()

なんかプライペート関数経由して取得してますが、なんでこうしたのかは覚えていません。

for _, pokemon := range pokemonCollection.Pokemons {
    if pokemon.Name.Japanese == name {
        return getPokemonStruct(pokemon), nil
    }
}

取得したDBのポケモンの名前と、パラメータで受け取ったポケモンの名前が一致した場合は、そのポケモンを新しい構造体に詰めて返しています。

ざっくりですが、API側はこんな感じです。

叩く側

次にAPIを叩く側です。

main.go

とりあえずソースコードを。

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "pokemon_command/input"
    "pokemon_command/pokemon"
)

func main() {
    url := input.CreateUrl()

    resp, _ := http.Get(url)
    defer resp.Body.Close()

    byteArray, _ := ioutil.ReadAll(resp.Body)

    var errCheck map[string]string
    json.Unmarshal(byteArray, &errCheck)
    if val, ok := errCheck["err"]; ok {
        fmt.Println(val)
        return
    }

    pokemonStruct := pokemon.JsonToPokemon(byteArray)

    pokemon.PrintPokemon(pokemonStruct)
}

main関数の中身の説明ですが、

// inputの詳細は後述
url := input.CreateUrl()

ここで標準入力を受け取って、それをもとにAPIを叩くためのURLを生成しています。

resp, _ := http.Get(url)
defer resp.Body.Close()

次に生成したURLにアクセスして、その結果を受け取ります。

byteArray, _ := ioutil.ReadAll(resp.Body)

受け取ったレスポンスのBodyを取得します。

var errCheck map[string]string
json.Unmarshal(byteArray, &errCheck)
if val, ok := errCheck["err"]; ok {
    fmt.Println(val)
    return
}

ここではエラーハンドリングをしていますが、最適解がわからず。。。

// pokemonは後述
pokemonStruct := pokemon.JsonToPokemon(byteArray)
pokemon.PrintPokemon(pokemonStruct)

最後に受け取ったjsonを構造体に格納してから出力しています。

main.goはこんな感じです。

input.go

package input

import (
    "bufio"
    "fmt"
    "os"
)

func CreateUrl() string {
    fmt.Print("Please enter the name of the Pokemon.\n>> ")
    scanner := bufio.NewScanner(os.Stdin)
    scanner.Scan()
    return "http://localhost:18888/?name=" + scanner.Text()
}

ここは大したことはなく標準入力を受け付けて、その入力をもとにURLを生成しています。

fmt.Print("Please enter the name of the Pokemon.\n>> ")
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()

ここ3行が入力を促す文章を表示して、bufioパッケージの関数にて標準入力を取得しています。

return "http://localhost:8081/?name=" + scanner.Text()

取得した文字列を結合してURLを生成しています。
なんかもうちょっといい感じに書きたい。

pokemon.go

package pokemon

import (
    "encoding/json"
    "fmt"
    "log"
)

type Pokemon struct {
    Name string `json:"name"`
    HP int `json:"hp"`
    Attack int `json:"attack"`
    Defense int `json:"defense"`
    SpAttack int `json:"sp_attack"`
    SpDefense int `json:"sp_defense"`
    Speed int `json:"speed"`
}

func JsonToPokemon(pokemonJson []byte) *Pokemon {
    pokemon := new(Pokemon)
    err := json.Unmarshal(pokemonJson, pokemon)
    if err != nil {
        log.Fatal(err)
    }
    return pokemon
}

func PrintPokemon(pokemon *Pokemon) {
    fmt.Println("なまえ : ", pokemon.Name)
    fmt.Println("HP     : ", pokemon.HP)
    fmt.Println("こうげき : ", pokemon.Attack)
    fmt.Println("ぼうぎょ : ", pokemon.Defense)
    fmt.Println("とくこう : ", pokemon.SpAttack)
    fmt.Println("とくぼう : ", pokemon.SpDefense)
    fmt.Println("すばやさ : ", pokemon.Speed)
}

ここでも大したことはしておらず、受け取ったjsonを構造体に詰める関数と、その構造体をもとに出力する関数があるのみです。
ほんとにたいしたことは書いてないので、詳細は割愛ですw

感想

かなり書きなぐった感じの記事になりましたが←
GOで何か作ってみるということで、今回は「標準入力で入力を受け付ける」ということをしたいと思っていたので、とりあえずとしては、そんな感じで実装できたので良しとします。
理想はpokemonコマンドみたいなものを作って、オプションによって表示が変わる。
みたいなものを作りたいですが、それはまた別の機会でがんばります。

ローカルだけじゃなくて、いつでもどこでもAPI叩きたいなぁと思って、さくらVPSを契約してそこにAPIをおいたのでw、今度その辺も記事に書こうと思います。

ということで以上です!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeetCodeに毎日挑戦してみた 7.Reverse Integer (Python、Go)

はじめに

無料英単語サイトE-tanを運営中の@ishishowです。

プログラマとしての能力を上げるために毎日leetcodeに取り組み、自分なりの解き方を挙げていきたいと思います。

Leetcodeとは

leetcode.com
ソフトウェア開発職のコーディング面接の練習といえばこれらしいです。
合計1500問以上のコーデイング問題が投稿されていて、実際の面接でも同じ問題が出されることは多いらしいとのことです。

Go言語入門+アルゴリズム脳の強化のためにGolangとPythonで解いていこうと思います。(Pythonは弱弱だが経験あり)

2日目(問題07)

Reverse Integer

  • 問題内容(日本語訳)

32ビットの符号付き整数を指定すると、整数の逆桁になります。

注:
我々は、32ビット符号付き整数の範囲内の整数を格納することができる環境を扱っていると仮定:[-2 31、2 31 - 1]。この問題の目的のために、逆整数がオーバーフローしたときに関数が0を返すと仮定します。

Example 1:

  Input: x = 123
  Output: 321

Example 2:

  Input: x = -123
  Output: -321

Example 3:

  Input: x = 120
  Output: 21

Example 4:

  Input: x = 0
  Output: 0

#### 考え方

  1. 正負判定をして負だったら正にする。
  2. ループ処理で数字を逆にしていく
  3. INTMAX,INTMINの処理を記述する。(オーバーフロー処理)

#### 説明

  1. マイナスをかけるだけでした。
  2. 10で割った余りをansという変数に代入→そして次に代入する前にansを10倍する(この方法で逆になります!)
  3. Goのときはmathパッケージを使ってみました!
  • 解答コード
  class Solution(object):
      def reverse(self, x):
          if x < 0:
              return (self.roop_func(-x) * -1)
          else:
              return self.roop_func(x)

      def roop_func(self,x):
          ans = 0
          while x > 0:
              ans = ans*10 + x % 10
              x /= 10
          if -(ans) < -(2**31) or ans > 2**31 - 1:
              return 0
          return ans
  • Goでも書いてみます!
  import "math"

  func reverse(x int) int {
    if x < 0 {
        return -(roop_int(-x))
    } else {
        return roop_int(x)
    }
  }

  func roop_int(x int) int {
    ans := 0
    for x > 0 {
        ans = ans*10 + x%10
        x /= 10
    }
    if -(ans) < math.MinInt32 || ans > math.MaxInt32 {
        return 0
    }
    return ans
  }

GoとPythonの実行時間

左から、RunTime, Memory, 言語です。
間違えているのは気にしないでください。

キャプチャ.PNG

別解

文字を逆にする処理での解き方もあるみたいです。こちらの方も取り組みましたが簡単ですね。。

class Solution(object):
    def reverse(self, x):
        s = (x > 0) - (x < 0)
        r = int(str(x*s)[::-1])
        return s*r * (r < 2**31)

文字列操作はpythonは楽ですね!

  • 自分メモ(Go)

未使用の変数,パッケージがある場合Goはコンパイル失敗する

文字列をnilにできない

参考にした記事

【初級編】Go言語を始める方のための落とし穴、問題の解決方法やよくある間違い

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む