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

Go で複数桁の数字を slice に入れたい時、そして reverse させたい時

Go で int と slice をいい感じに扱いたい

こんなん考えたらわかるやろって感じの内容なのかもしれませんが、ググってもいい感じのが出なかったのでメモとして残しておきます。

競技プログラミングとかで使えるかも!

Go で int を1桁ずつ slice に入れる

見てみればなんだこんなもんかって感じです。除算と余りをいい感じに使う再帰関数になってます。

func digit(i int, list []int) []int {
    if i > 0 {
        return digit(i/10, append(list, i%10))
    }
    return list
}

Go で int の slice を逆転したい

作った int の slice を reverse したいときもありますよね?
そういうときはこれです。ループで頑張って入れ替えます。

func reverse(numbers []int) []int {
    for i := 0; i < len(numbers)/2; i++ {
        j := len(numbers) - i - 1
        numbers[i], numbers[j] = numbers[j], numbers[i]
    }
    return numbers
}

int を 1 桁ずつ slice にして逆転する

上記の2つの関数を合わせてこんな感じで使います。
地道にやればなんでもいけますね!

package main

import (
    "fmt"
)

func main() {
    result := 708
    var list []int
    fmt.Println(digit(result, list))
}

func digit(i int, list []int) []int {
    if i > 0 {
        return digit(i/10, append(list, i%10))
    }
    return reverse(list)
}

func reverse(numbers []int) []int {
    for i := 0; i < len(numbers)/2; i++ {
        j := len(numbers) - i - 1
        numbers[i], numbers[j] = numbers[j], numbers[i]
    }
    return numbers
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング言語Go 第6章 メモ

「メソッド」

書籍:プログラミング言語Go
第6章 「メソッド」 の要点と思われる箇所を自分のメモ用にまとめました。

6.1 メソッド宣言

  • この本では、オブジェクトはメソッドを持つ単なる値あるいは変数で、メソッドは特定の方に関連付けられた関数。
  • メソッドは普通の関数宣言の変形で宣言され、そこでは関数名の前に追加のパラメータが書かれる。そのパラメータが当のパラメータの型へ関数を結びつける。
  type Point struct { X, Y float64 }

  func Distance(p, q Point) float64 { // 昔ながらの関数
      return math.Hypot(q.X-p.X, q.Y-p.Y)
  }

  func (p Point) Distance(q Point) float64 // Point型のメソッド
      return math.Hypot(q.X-p.X, q.Y-p.Y)
  }
  p := Point{1, 2}
  q := Point{4, 6}
  fmt.Println(p.Distance(q)) // "5", メソッド呼び出し。
  • 上記メソッドの p はレシーバと呼ばれる。Goではレシーバに対し、this や self などの特別な名前は使わない。
  • レシーバ名は頻繁に使われるので短くメソッド間で一貫するのが望ましく、Pointに対する p など、型名の頭文字などよく利用される。
  • メソッドとフィールドは同じ名前空間に存在するため、メソッド名とフィールド名が同一のものを定義すると曖昧となりコンパイルエラーとなる。
  • スライス型などもtype宣言で名前を付けることができ、メソッドを関連付けることができる。この点は他言語と異なる点。
  type Path []Point
  func (path Path) Distance() float64 { // pathに沿って進んだ距離を返す。
      sum := 0.0
      for i := range path {
          if i > 0 {
              sum += path[i-1].Distance(path[i])
          }
      }
      return sum
  }
  • マップや関数も名前付き型にすればメソッド定義できる。基底型がポインタかインタフェースでない限り同一パッケージ内の名前付き方にはメソッド宣言できる。
  • 関数でなくメソッドを使う利点の一つは型の名前があるのでメソッド名を短くできる点。パッケージ外からの呼び出しでこの利点は増大する。

6.2 ポインタレシーバを持つメソッド

  • Pointなどのポインタ型にもメソッドを結び付けられる。以下のメソッドの名前は(Point).ScaleBy(()をつけないと*(Point.ScaleBy)と解釈されるので注意)。
  func (p *Point) ScaleBy(factor float64) {
      p.X *= factor
      p.Y *= factor
  }
  • 慣習として、Pointのどれかのメソッドがポインタレシーバを持つ場合、Pointの すべて のメソッドはポインタレシーバを持つべき。
  • 名前付き型 (Point) とその型へのポインタ (Point) は、レシーバ宣言に書くことができる。曖昧さを避けるため、それ自身がポインタの名前付き型はメソッド宣言できない。*
  type P *int
  func (P) f() { ... } // コンパイルエラー: 不正なレシーバ型
  • ポインタのレシーバの利用方法は以下。最後の呼び出しもOKで、コンパイラが暗黙で&pを行う。Pointのメソッドを*Pointで呼び出すことも可能。
  r := &Point{1, 2}
  r.ScaleBy(2)

  p := Point{1, 2}
  pptr := &p
  pptr.ScaleBy(2)

  (&p).ScaleBy(2)

  p.ScaleBy(2) // コンパイラが暗黙で&pを利用してくれる。変数 (アドレス化可能) の場合のみ暗黙の変換をしてくれる。
  • アドレス化可能でない Point レシーバに対して *Point を呼び出すことはできない、
  Point{1, 2}.ScaleBy(2) // コンパイルエラー: Pointリテラルのアドレスは得られない。
  • 以下が成立すればメソッド呼び出しが可能。
    • レシーバ引数がレシーバパラメータと同じ型。
    • レシーバ引数が型 T の変数で、レシーバパラメータが型 *T である。(コンパイラが暗黙でその変数のアドレスを得る。)
    • レシーバ引数の型が *T で、レシーバパラメータは型 T である。(コンパイラは暗黙的にレシーバの指す値を参照する。)
  • 名前付き型 T のすべてのメソッドが T (*Tでなく)のレシーバを持つならその型のインスタンスをコピーすることは安全。メソッド呼び出しではコピーが行われるから。
  • どれかのメソッドがポインタレシーバをもっていると、その名前付き型のインスタンスコピーは避けるべき。内部の不変式を破る可能性があるから。(コピーしたインスタンスがコピー元のインスタンス操作で変更されうるので。)

6.2.1 nil は正当なレシーバ値

  • 特にマップやスライスのように nil がその型のゼロ値を意味する場合などあるため、nil をレシーバとして利用することは許されている。
  func (list *IntList) Sum() int {
      if list == nil {
          return 0
      }
      ...
  • レシーバ値として nil を許すメソッドを持つ型を定義する場合、ドキュメンテーションコメントで明示すべき。

■6.3 構造体埋め込みによる型の合成
- 構造体を埋め込んだ(無名フィールドを利用)構造体は、埋め込まれた構造体のフィールドに直接アクセスできる。埋め込まれた構造体のメソッドは埋め込んだ構造体に 格上げ される。

  type Point struct{ X, Y float64 }
  func (p *Point) ScaleBy(factor float64) {
      p.X *= factor
      p.Y *= factor
  }
  type ColoredPoint struct {
      Point // 無名フィールドとして Point を利用。
      Color color.RGBA
  }
  var cp ColoredPoint
  cp.X = 1 // OK
  fmt.Println(cp.Point.X) // "1"
  cp.ScaleBy(2) // OK
  • ColoredPoint と Point の関係は "is a"(継承関係) ではなく "has a"。Point を引数にする関数やメソッドに ColoredPoint は使えない。
  • 無名フィールドの型は名前付き型へのポインタでもよい。その場合もフィールドとメソッドは格上げされる。
  • 埋め込みのおかげで名前なし構造体型がメソッドを持つことができる。以下の例は機能的に同じ実装だが、後者のほうが可読性が高くなる。
  var (
      mu sync.Mutex
      mapping = make(map[string]string)
  )
  func Lookup(key string) string {
      mu.Lock()
      v := mapping[key]
      mu.Unlock()
      return v
  }

  var cache = struct { // 名前なし構造体
      sync.Mutex // sync.Mutex を埋め込み。
      mapping map[string]string
  } {
      mapping: make(map[string]string)
  }
  func Lookup(key string) string {
      cache.Lock()
      v := cache.mapping[key]
      cache.Unlock()
      return v
  }

6.4 メソッド値とメソッド式

  • p.ScaleByなどは特定のレシーバ p に結びついたメソッド値と呼ぶ。メソッド値を利用するとレシーバ値なしで関数呼び出しできる。
  distanceFromP = p.Distance
  fmt.Println(distanceFromP(q)) // p.Distance(q) と同じ呼び出し。
  • 名前付き構造体 T があり、fというメソッドを持つ場合、T.f または (*T).f をメソッド式と呼ぶ。レシーバの代わりをする普通の第一パラメータを持つ関数値を生成する。
  distance := Point.Distance
  fmt.Println(distance(p, q)) // p.Distance(q) と同じ呼び出し。

6.5 例:ビットベクタ型

  • ビットベクタは符号なし整数値のスライス「複数ワード」を使う。個々のビットはセット内の可能な要素を表す。そのセットは i 番目のビットが設定されていれば i を含む。
  • 演習問題 6.5 「64で割る代わりにunitの」は uint の間違い??

6.6 カプセル化

  • Goは名前の可視性を制御する仕組みは一つしか持たない。大文字始まりだとパッケージ外へ公開され、大文字で始まっていない場合は公開されない。
  • カプセル化は三つの利点を提供する。
    • クライアントがオブジェクトの変数を直接修正できないためオブジェクトの変数が取り得る値を理解するために調べるコードが少なくなる。
    • 詳細な実装の隠蔽によりクライアントは変更されるかもしれない事柄に依存しなくなり、APIの互換性を破ることなく実装を発展させる大きな自由を設計者へ与える。
    • クライアントがオブジェクトの変数を勝手に設定するのを防ぐ。関数経由で変数を設定させるので、オブジェクトの内部的な不変式を維持することが保証できる。
  • フィールドが一旦公開されたらAPIに対する互換性を失うことなく非公開にすることはできない。そのため最初の選択は慎重に検討されるべき。

練習問題解答例

https://github.com/tsuyuzaki/go/tree/master/GoProgrammingLanguage/chapter06

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

はてなブログの歴代一位をまとめて見れるサイトを作った

はじめに

はてなブログのテクノロジーカテゴリをよく見ています。
今までの歴代一位がどのようなものだったか気になって一覧で見れるサイトを作りました。

https://hatena-hotentry.netlify.com/

使った技術

  • serverlessFramework
  • go
  • goquery
  • RealtimeDatabase
  • lambda
  • vue

バッチ

はてなブログのデータを取得するスクリプトをバッチで動かしています。
はてなブログをスクレイピングするコード(go)を書き、ServerlessFrameworkを使ってlambdaにアップロードしています。
ServerlessFrameworkは serverless.yml にcronを定義しておけば、cloudWatchを使ったlambdaのスケジュール実行を設定してくれるのでお手軽にバッチ処理が作れます。

参考:https://dev.classmethod.jp/etc/serverless-framework-lambda-cron-execute/

スクレイピングしたデータを保存する場所としてはfirebaseのRealtimeDatabaseを使いました。

初めはCloudFirestoreの方に保存していたのですが、保存しているドキュメント数が5000近くになり、無料プランでの一日のドキュメント読み取り数が50000だったため、10回全件取得すると、その日はデータ取得ができなくなる状態になりました。
そのため、RealtimeDatabaseに乗り換えましたが、NoSQLにデータを保存する際の設計がわかっていれば、CloudFirestoreでもうまくできたような気がします。

フロント

vueを使いました。vue-cliで雛形を作り、それをいじって作っています。
デプロイにはnetlifyを使っています。

最後に

goとvueを勉強するために作りましたが、こういう小さいアプリケーションを色々作っていきたい気持ちです。

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

阿部寛をWebDriverでいじくる

なにこれ

GoでWebDriverを使いたくて、試しに阿部寛のホームページでも回遊するかー。と軽いノリで手をつけたらちょいハマったのでメモ。
最終的に?みたいなプログラムができた。

あべひろし.gif

この記事読んで得られるモノ

  • 阿部寛のホームページメンズノンノ時代の阿部寛のスクショを取れるようになる
  • framesetおよびframeが利用されているwebページを操作できるようになる
  • Go言語でブラウザ操作を行えるようになる

必要なもの

macなら以下でChromeDriverとAgoutiをインストールできます。簡単ね。

brew install chromedriver
go get github.com/sclevine/agouti

回遊する方法

以下コードで回遊できます。

package main

import (
    "log"

    "github.com/sclevine/agouti"
)

func main() {
    driver := agouti.ChromeDriver()

    // headlessにする場合はこっちを使う
    // driver := agouti.ChromeDriver(
    //  agouti.ChromeOptions("args", []string{"--headless", "--disable-gpu", "--no-sandbox"}),
    // )
    if err := driver.Start(); err != nil {
        log.Fatalf("driverの起動に失敗しました : %v", err)
    }
    defer driver.Stop()

    page, err := driver.NewPage(agouti.Browser("chrome"))
    if err != nil {
        log.Fatalf("セッション作成に失敗しました : %v", err)
    }

    // 阿部寛のウェブページに遷移する
    if err := page.Navigate("http://abehiroshi.la.coocan.jp/"); err != nil {
        log.Fatalf("阿部寛になにかあったかもしれません : %v", err)
    }

    // 写真集のリンクを検索する
    // framesetの中の要素を検索するには一旦該当のフレームにフォーカスしなければならない
    // 左側のフレームにフォーカスする
    if err := page.FindByXPath("/html/frameset/frame[1]").SwitchToFrame(); err != nil {
        log.Fatalf("阿部寛の左側frameにフォーカスできませんでした : %v", err)
    }
    // 「写真集」をクリック
    if err := page.FindByXPath("html/body/table/tbody/tr[10]/td[3]/p/a").Click(); err != nil {
        log.Fatalf("阿部寛の写真集が見つかりませんでした : %v", err)
    }

    // フレームのフォーカス外すためrootにもどる
    if err := page.SwitchToRootFrame(); err != nil {
        log.Fatalf("しょうも無いエラーが発生しました : %v", err)
    }

    // 右側のフレームにフォーカスする
    if err := page.FindByXPath("/html/frameset/frame[2]").SwitchToFrame(); err != nil {
        log.Fatalf("阿部寛の右側frameにフォーカスできませんでした : %v", err)
    }

    // 「メンズノンノ阿部寛」をクリック
    if err := page.FindByXPath("/html/body/table/tbody/tr[8]/td[2]/strong/a").Click(); err != nil {
        log.Fatalf("阿部寛のメンズノンノがみつかりませんでした : %v", err)
    }

    // スクショとる
    if err := page.Screenshot("./abe_hiroshi.jpg"); err != nil {
        log.Fatalf("スクショ取れまへん : %v", err)
    }
}

ハマったこと

frame内部の要素にアクセスできないよぉぉぉ?

コードで言うと// 写真集のリンクを検索するのコメント以下でハマってた。。。

トップページから写真集のリンクをクリックしようとGoogle ChromeのDeveloper ToolsでXPathをコピーしてFindByXPathに貼り付けたのですが、要素が見つからないエラーが発生した。

failed to select elements from selection 'XPath: html/body/table/tbody/tr[10]/td[3]/p/a [single]': element not found

なんで。。。

ちゃんとHTMLを読んで見るとframesetおよびframeの存在を見つけました。
スクリーンショット 2019-03-30 17.21.51.png

あれ?Developer ToolsでコピーしたXPathはhtml/body/table/tbody/tr[10]/td[3]/p/aですね。これじゃ見つからないですね。
html/frameset/frame/.......って書けば良いのかな?
ん?#documentってなにこれ?っていうかframeってなに?わからぬ。。。

...

どうやら阿部寛のホームページは以下のように2つのframeで構成されているということがわかった。
スクリーンショット 2019-03-30 17.58.33.png

解決方法

AgoutiのドキュメントでFrameで検索してみるとSwitchToFrame()ってメソッドを見つけた。

func (s *Selection) SwitchToFrame() error
SwitchToFrame focuses on the frame specified by the selection. All new and existing selections will refer to the new frame. All further Page methods will apply to this frame as well.

https://godoc.org/github.com/sclevine/agouti#Selection.SwitchToFrame
どうやらframeにフォーカスを当てる必要があるらしい。
そのためframeを指定してから要素を検索すればOK。
またframeのフォーカスを外すSwitchToRootFrame()ってメソッドも用意されていた。

なので、以下方針でリンクをクリックすることにする

  1. トップページにアクセス
  2. 左側のフレームにフォーカスする
  3. 写真集リンクをクリックする
  4. フレームのフォーカスをRootに戻す
  5. 右側のフレームにフォーカスする
  6. メンズノンノ阿部寛リンクをクリックする
// 写真集のリンクを検索する
// frameの中の要素を検索するには一旦該当のフレームにフォーカスしなければならない
// 左側のフレームにフォーカスする
page.FindByXPath("/html/frameset/frame[1]").SwitchToFrame()

// 「写真集」をクリック
page.FindByXPath("html/body/table/tbody/tr[10]/td[3]/p/a").Click()

// 左側フレームのフォーカス外すためrootにもどる
page.SwitchToRootFrame()

// 右側のフレームにフォーカスする
page.FindByXPath("/html/frameset/frame[2]").SwitchToFrame()

// 「メンズノンノ阿部寛」をクリック
page.FindByXPath("/html/body/table/tbody/tr[8]/td[2]/strong/a").Click()

これでframeを利用しているWebページに回遊できるようになりました?

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

TCPのLISTEN見て、ヘルスチェック応答させる

背景

AWSのALB(Application Load Balancer)を使いたかったんですけど、アプリがヘルスチェックにうまく反応しない。。
(ALBは正常のHTTPステータスコードとして、200 ~ 499の範囲で指定可能ですが、アプリが500を正常で応答する仕様 orz)

そしてアプリ側の改修して頂ける雰囲気はないので、ヘルスチェックを書きました。

サマリ

最終的に出来たコード

・ net/httpでヘルスチェック用webを起動
・ health check requestを受けたら、net.Listen でサービスが稼働しているポート20000番のポートをListenを試みる
・ Listen出来た場合(サービス停止) → 404を応答
  or 
・ Listen出来なかった場合(サービス稼働) → 200を応答

handler作成

ヘルスチェックのリクエスト受けた時に、特定ポート(今回は20000port)の Listen を試みる handler です。
listen出来たら、404 NotFoundで、Listen出来なかったら、200OKを応答する。

var (
    svcProtocol = "tcp4"
    svcPort     = ":20000"
    okMsg       = "healthy"
    ngMsg       = "unhealthy..."
)

func checker(writer http.ResponseWriter, request *http.Request) {
    // Try to listen on svcProtocol and svcPort.
    listenchecker, err := net.Listen(svcProtocol, svcPort)

    if err == nil {
    // able to listen it... Which means, the service has stopped...ng
        writer.WriteHeader(http.StatusNotFound)
        fmt.Fprint(writer, ngMsg)
        defer listenchecker.Close()
        return
    }
    // not able to listen it. Which means, the service is working...ok!!!
    fmt.Fprint(writer, okMsg)
    return
}

handler登録してヘルスチェック応答web起動

ヘルスチェックのパスとポートを決めて、 handler を登録して web を起動

health.go
var (
    healthPath  = "/health"
    healthPort  = ":80"
)
func main() {
    http.HandleFunc(healthPath, checker)
    http.ListenAndServe(healthPort, nil)
}

windows 用にコンパイル

-ldflags="-H windowsgui" はプロンプト起動させない様にするとか。

GOOS=windows GOARCH=386 go build -ldflags="-H windowsgui" health.go

AWS側

リスナー設定

まずはサービスで利用するポートを指定する。
image.png

ヘルスチェック設定

次にヘルスチェックの指定
ポイントはヘルスチェックのポートを トラフィックポート ではなく、 上書き を指定しヘルスチェック専用ポートを指定すること。

image.png

実験開始

まずは、windowsサーバでコンパイルした自作ヘルスチェックを起動

c:\>health.exe

ヘルスチェック(80)と、サービス(20000)共に起動(LISTEN)している

c:\>netstat -aon | find "80"
  TCP         0.0.0.0:80             0.0.0.0:0              LISTENING       3552
c:\>netstat -aon | find "20000"
  TCP         0.0.0.0:20000          0.0.0.0:0              LISTENING       3636

curlでヘルスチェック先を叩くと200が返ってくる。

~ ❯❯❯ curl -i http://ec2-xx-xx-xx-xx.ap-northeast-1.compute.amazonaws.com/health
HTTP/1.1 200 OK
Date: Sat, 30 Mar 2019 03:33:21 GMT
Content-Length: 7
Content-Type: text/plain; charset=utf-8

healthy

サービスを停止し、再度curlで確認するとちゃんと連動してるのが確認できた。

~ ❯❯❯ curl -i http://ec2-xx-xx-xx-xx.ap-northeast-1.compute.amazonaws.com/health
HTTP/1.1 404 Not Found
Date: Sat, 30 Mar 2019 03:34:17 GMT
Content-Length: 12
Content-Type: text/plain; charset=utf-8

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