20200914のGoに関する記事は9件です。

A Tour of Go メモ 【4】二日目

A Tour of Go

Range

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
    }
    for i, a := range hiragana {
        fmt.Printf("「%s」は、%d番目\n", a, i+1)
    }
}
> 2**0 = 1
> 2**1 = 2
> 2**2 = 4
> 2**3 = 8
> 2**4 = 16
> 2**5 = 32
> 2**6 = 64
> 2**7 = 128
> 「あ」は、1番目
> 「い」は、2番目
> 「う」は、3番目
> 「え」は、4番目
> 「お」は、5番目

rangeと"_(アンダーバー)"

func main() {
    pow := make([]int, 10)
    for i := range pow {
        pow[i] = 1 << uint(i) // == 2**i
    }
    for _, value := range pow {
        fmt.Printf("%d\n", value)
    }
    s := "hello"
    for i, _ := range s {
        fmt.Printf("%d回目の%s\n", i+1, s)
    }
}

> 1
> 2
> 4
> 8
> 16
> 32
> 64
> 128
> 256
> 512
> 1回目のhello
> 2回目のhello
> 3回目のhello
> 4回目のhello
> 5回目のhello

Maps

make(map["keyになる値の型"]"valueになる値の型")
例えば、
・ keyも値もint型
=> make(map[int]int)
.keyも値もstring型
=> make(map[string]string)

type Vertex struct {
    Lat, Long float64
}

var m map[string]Vertex
//変数を定義するときにキーと値を設定できる
var n = map[string]Vertex{
    "key1" : Vertex {
        10.0, 10.000,
    },
    "key2" : Vertex {
         20.0, 20.000,      
    },
}
var z = map[string]Vertex{
    "key1" : {333.33, 44.444},
    "key2" : {88.888, 999.999},
}

func main() {
    fmt.Println(m)
    //エラー。 初期化していないので、追加できない。
    m["insert"] = Vertex{
       11.111, 22.222,

    }

    fmt.Println(n)
    // 初期化
    m = make(map[string]Vertex)
    fmt.Println(m)
    //キーの追加
    m["Bell Labs"] = Vertex{
        40.68433, -74.39967,
    }
    m["test"] = Vertex{
        22222, 2020202,
    }
    fmt.Println(m)
    fmt.Println(m["Bell Labs"])
}

> map[]
> map[key1:{10 10} key2:{20 20}]
> map[]
> map[Bell Labs:{40.68433 -74.39967} test:{22222 2.020202e+06}]
> {40.68433 -74.39967}

Mapの操作

func main() {
    m := make(map[string]int)

    m["Answer"] = 42
    fmt.Println(m)
    //keyを指定すると対応した要素(elem)を取得でkりう
    fmt.Println("The value:", m["Answer"])

    m["Answer"] = 48
    fmt.Println("The value:", m["Answer"])

    //要素の削除
    delete(m, "Answer")
    fmt.Println("The value:", m["Answer"])

    //keyに対応する値があるか
    //もしもkeyがあれば、対応する値がvに入る、keyがなければ、map作成時に指定した型のゼロ値が入る
    //okには、keyがあれば、true, なければfalseが入る
    v, ok := m["Answer"]
    fmt.Println("The value:", v, "Present?", ok)
}

> map[Answer:42]
> The value: 42
> The value: 48
> The value: 0
> The value: 0 Present? false

関数

関数の引数に関数を渡せる

import (
    "fmt"
    "math"
)

//引数に関数を渡すときに渡す関数の引数と戻り値の型を書かなくてはいけない
//引数に渡された関数の引数に3,4をいれる関数
func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }
    fmt.Println(hypot(5, 12))

    // hypot関数の引数に3,4をいれる
    fmt.Println(compute(hypot))
    //3の4乗を返す
    fmt.Println(compute(math.Pow))
}

> 13
> 5
> 81

クロージャー

・複数の関数がセットとなり、その関数内で変数を共有できる
・オブジェクトの変数やメソッドを容易に変更できないようにカプセル化できる

func adder() func(int) int {
   //sumの値を維持できる
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GolangでDynamoDBとJSONのマッピングを同時に使って躓いた話

Golang x Lambdaで小規模なAPIを作った際、一つの構造体にDynamoDBとJSONのマッピングを同時に使って躓いたのでメモを残しておく。

うまく行かない方法

DynamoDBから取得したデータに対して以下のような構造体でマッピングをして、それをそのままJSON形式で返そうとした。
雰囲気としては以下の感じ(エラーハンドリングなどは省略)

main.go
type UserInfo struct {
    UserName string   `dynamodbav:"userName", json:"userName"`
    ContactNumber  int   `dynamodbav:"contactNumber", json:"contactNumber"`
}

func SuccessResponse(body string) events.APIGatewayProxyResponse {
    return events.APIGatewayProxyResponse{
        StatusCode:      200,
        IsBase64Encoded: false,
        Body:            body,
        Headers: map[string]string{
            "Content-Type":                "application/json",
            "Access-Control-Allow-Origin": "*",
        },
    }
}

func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
  // いろいろ...

  var dbResult DbResult
  // dynamoDBからQueryでデータ取得
  result, _ := svc.Query(fetchQueryParams(userId))

  // dynamoDBのデータを構造体にマッピング
  dynamodbattribute.UnmarshalMap(result.Items[0], &dbResult)

  responseBody, _ := json.Marshal(&dbResult)

return SuccessResponse(string(responseBody)), nil
}

すると、レスポンスは以下のようになった。

JSONのレスポンス
{
  "UserName": "やまだたろう",
  "ContactNumber": 1234567890
}

なぜかkeyの先頭が大文字になった。どうも構造体に定義した json: での指定がうまく効いていないようだ。

うまくいく方法

エラーが出るわけでもなく完全に原因不明で小一時間ほど詰まった挙げ句にたどり着いたのがこちら。
DynamoDB Marshal: "dynamodbav" struct tag is not respected when json tag is present unless it is first #748

You need a space instead of a comma between multiple tags I think. Comma didn't work for me.

とのことで、 dynamodbavjson の区切りは , ではなく (スペース)にしないとイケないらしい。
コンマにしていたせいでjsonのkey名が構造体のフィールド名と同じ形式になっていたようだ(指定がない場合には構造体のフィールド名がJSONのkey名に入る仕様らしい)。

以下のように修正することで、意図通りのレスポンス形式となった。

修正版の構造体
type UserInfo struct {
    UserName string   `dynamodbav:"userName" json:"userName"` // スペースで区切る
    ContactNumber  int   `dynamodbav:"contactNumber" json:"contactNumber"`// スペースで区切る
}
JSONのレスポンス(修正版)
{
  "userName": "やまだたろう",
  "contactNumber": 1234567890
}

所感

せめてエラーか何か出してほしかった....。

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

Golang チュートリアル その2

概要

「処理が速い」「並行処理」が得意ということから、
今後大規模webサービスなどで重宝されると思いましたので、
学習していきたいと思います。

今回は、「For」や「if」といったところをみていきます。

参考サイト

以下を実施してみました。

Welcome to a tour of Go

環境

GoogleChrome
※ブラウザ操作だけで学習できるため、 エディタを用意する必要がありません。
※私は、ローカルにCentOS8環境を用意して、チュートリアルを行います。
※Goインストール手順です。よろしければ、ご活用ください。
CentOS8へのGo言語インストール手順

For文

  • 書き方

forループはセミコロン ; で3つの部分に分かれている。

・初期化ステートメント: 最初のイテレーション(繰り返し)の前に初期化が実行される。
・条件式: イテレーション毎に評価される。
・後処理ステートメント: イテレーション毎の最後に実行される。
・初期化ステートメントは、短い変数宣言によく利用される。
※その変数は for ステートメントのスコープ内でのみ有効。

※他の言語、C言語やJava、JavaScriptのfor ループとは異なり、
 for ステートメントの3つの部分を括る括弧 ( ) はない。
 しかし、中括弧 { } は常に必要。

for.go
package main

import "fmt"

func main(){
    sum := 0
    for i := 0; i < 10; i++{
        sum += i
    }
    fmt.Println(sum)
}

//実行
go run for.go

//実行結果
45
  • For 続き

初期化と後処理ステートメントの記述は任意である。
※for.goファイルの中身を以下に書き換える

for.go
package main

import "fmt"

func main(){
    sum := 1
    for ; sum < 10; {
        sum += sum
    }
    fmt.Println(sum)
}

//実行
go run for.go

//実行結果
16
  • For 続き whileの代わりにForを使う

セミコロン(;)も省略可能。
C言語などにある while は、Goではforだけを使う。

for.go
package main

import "fmt"

func main(){
    sum := 1
        //10の後ろの;を省略している
    for sum < 10 {
        sum += sum
    }
    fmt.Println(sum)
}

//実行
go run for.go

//実行結果
16
  • 無限ループ

forのループ条件を省略すれば無限ループが可能となる。

for.go
package main

import "fmt"

func main(){
    i := 1
    for{
        if i < 10 {
            i += i
        }else{
            break
        }
        fmt.Println(i)
    }
}

//実行
go run for.go

//実行結果
2
4
8
16

If文

ifステートメントは、forループと同様に、
括弧 ( ) は不要で、中括弧 { } は必要。

if.go
package main

import (
    "fmt"
    "math"
)

func sqrt(x float64) string{
    if x < 0 {
        return sqrt(-x) + "i"
    }
    return fmt.Sprint(math.Sqrt(x))
}

func main(){
    fmt.Println(sqrt(2), sqrt(-4))
}

//実行
go run if.go

//実行結果
1.4142135623730951 2i
  • If内で宣言した変数

上記は、elseブロック内でも使うことができる

Switch

switchステートメントはif-elseステートメントのシーケンスを短く書く方法。

Goでは選択されたcaseだけを実行してそれに続く全てのcaseは実行されない。
これらの言語の各caseの最後に必要なbreakステートメントがGoでは自動的に提供されるため。
もう一つの重要な違いはGoのswitchのcaseは定数である必要はなく、
関係する値は整数である必要はないということ。

switch.go
package main

import (
    "fmt"
    "runtime"
)

func main(){

    fmt.Print("Go runs on ")
    switch os := runtime.GOOS; os{

    case "darwin":
        fmt.Println("OS X.")
    case "linux":
        fmt.Println("Linux.")
    default:
        fmt.Printf("%s.\n", os)
    }
}

//実行
switch.go
  • 条件なしのSwitch

条件のないswitchは、switch true と書くことと同じに意味合いです。
このswitchの構造は、長くなりがちな"if-then-else"のつながりをシンプルに表現できます。

switch-with-no-condition.go
package main

import (
    "fmt"
    "time"
)

func main(){

    t := time.Now()

    switch {
    case t.Hour() < 12:
        fmt.Println("Good morning!")
    case t.Hour() < 17:
        fmt.Print("Good afternoon")
    default:
        fmt.Println("Good evening.")
    }
}

//実行
go run switch-with-no-condition.go 

Defer

deferステートメントは、 defer へ渡した関数の実行を、
呼び出し元の関数の終わり(returnする)まで遅延させるもの。

deferへ渡した関数の引数は、すぐに評価されますが、
その関数自体は呼び出し元の関数がreturnするまで実行されません。

defer.go
package main

import "fmt"

func main(){
    defer fmt.Println("World")

    fmt.Println("Hello")
}

//実行
go run defer.go

//結果
Hello
World
  • deferを複数使用した場合

defer へ渡した関数が複数ある場合、
その呼び出しはスタック( stack )される。
呼び出し元の関数がreturnするとき、
defer へ渡した関数は LIFO(last-in-first-out) の順番で実行される。

defer-multi.go
package main

import "fmt"

func main(){
    fmt.Println("counting")

    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }
    fmt.Println("done")
}

//実行
go run defer-multi.go

//結果
counting
done
9
8
7
6
5
4
3
2
1
0

まとめ

今回は、「For」「If」などみていきました。
次回は、ポインタを見ていきたいと思います。

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

GoでQiitaのトレンドを取得するAPI①作ってみた

はじめに

Qiitaのトレンド記事を取得したいと思ったのですが、公式でAPIが提供されていないので自分で作ってみました。

Qiitaトップページのdivタグにトレンド記事のJSONが入った属性があるので、これをスクレイピングして取得します。

スクレイピングには、goquery
フレームワークは Gin を使用しています。

リポジトリはこちら

コード

まず、トレンド記事一覧のJSONを取得します。

func qiitaTrend() (string, error) {
    doc, err := goquery.NewDocument("https://qiita.com/")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return "", err
    }
    trend := doc.Find("div[data-hyperapp-app='Trend']")
    val, exists := trend.Attr("data-hyperapp-props")
    if !exists {
        return "", errors.New("Internal Server Error")
    }
    return val, nil
}

順番に見ていきます。

まずはgoquery.NewDocument(url)でドキュメントを取得します

doc, err := goquery.NewDocument("https://qiita.com/")

次にdoc.Find(セレクタ)でセレクションを取得します。
セレクタでフィルタリングして一致した要素を取得します。

今回取得しようとしている要素は、以下のようになっているのでdata-hyperapp-app="Trend"を指定しています。

<div data-hyperapp-app="Trend" data-hyperapp-props="トレンド記事のJSON" data-hyperapp-mounted="true">
...
</div>
trend := doc.Find("div[data-hyperapp-app='Trend']")

最後は欲しいデータを抜き取ります。
trend.Attr(属性)でその属性の値を取得します。
existsはその値が存在するかboolで返します。

記事一覧のJSONはdata-hyperapp-propsに入っているのでそれを指定します。

val, exists := trend.Attr("data-hyperapp-props")

以上でトレンド記事のJSONを取得することができました。
最終的なコードは以下のようになります。

package main

import (
    "errors"
    "fmt"
    "net/http"
    "os"

    "github.com/PuerkitoBio/goquery"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.New()

    r.Use(gin.Logger())
    r.Use(gin.Recovery())

    r.GET("/trend", func(c *gin.Context) {
        trend, err := qiitaTrend()
        if err != nil {
            c.JSON(http.StatusInternalServerError, err)
            return
        }
        c.String(http.StatusOK, trend)
        return
    })

    r.Run(":8080")
}

func qiitaTrend() (string, error) {
    doc, err := goquery.NewDocument("https://qiita.com/")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return "", err
    }
    trend := doc.Find("div[data-hyperapp-app='Trend']")
    val, exists := trend.Attr("data-hyperapp-props")
    if !exists {
        return "", errors.New("Internal Server Error")
    }
    return val, nil
}

Ginでのhttpサーバーについては今回は省略します。

実行してcurlで取得すると、

$ curl http://localhost:8080/trend
{"trend":{"edges":[{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-11T07:53:22Z","likesCount":338,"title":"オブジェクト指向は単なる【整理術】だよ","uuid":"2aa9859d4da41991bbb7","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/profile-images/1567829792","urlName":"br_branch"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T03:08:08Z","likesCount":104,"title":"間違えて違うブランチで作業を始めてしまったときの対処法【Git】","uuid":"dc21de47c617b0d0eebf","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675596/profile-images/1598685340","urlName":"paleo_engineer"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-11T17:26:06Z","likesCount":82,"title":"Pythonでコンソールに綺麗なグラフを描画できる「AsciichartPy」使ってみた。","uuid":"aff5c301aacb8513a11d","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/535270/3e59522dfca791fd0ce7f98d863a9a796ca95a16/large.png?1574159780","urlName":"kkent030315"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-11T13:59:19Z","likesCount":90,"title":"CPUのキーワード(ブランド、命令セット、マイクロアーキテクチャ、拡張命令セット)の説明","uuid":"656bac4c3871031a4448","author":{"profileImageUrl":"https://qiita-image-store.s3.amazonaws.com/0/79744/profile-images/1501125720","urlName":"kaityo256"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T10:57:35Z","likesCount":79,"title":"スプラトゥーン2のプレイ動画から、やられたシーンだけをディープラーニングで自動抽出する","uuid":"acbc0906046bb7b2b1db","author":{"profileImageUrl":"https://avatars3.githubusercontent.com/u/16898831?v=4","urlName":"tfandkusu"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":false,"node":{"createdAt":"2020-09-11T09:35:58Z","likesCount":63,"title":"様々な日本語文章を、目線移動無く早く読めるオープンソースツール(Riot Shield)を公開しました","uuid":"c7e48f9f9b3e937c5dad","author":{"profileImageUrl":"https://qiita-image-store.s3.amazonaws.com/0/85111/profile-images/1499606550","urlName":"am_nimitz3"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T13:59:38Z","likesCount":55,"title":"Kubernetesの負荷試験で絶対に担保したい13のチェックリスト","uuid":"56a5442c1fc4d714c941","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45875/profile-images/1595854294","urlName":"taisho6339"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":false,"node":{"createdAt":"2020-09-11T13:14:11Z","likesCount":42,"title":"《コロナに負けず!!》29歳で営業職からジョブチェンジ。未経験で受託開発企業から内定を頂けました。","uuid":"3eafbcc52c04241788a5","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/635167/4df257c74aac0acdbb03f0e7f63e276aa42830a0/x_large.png?1591368347","urlName":"ki-ku"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":true,"hasCodeBlock":true,"node":{"createdAt":"2020-09-13T11:10:37Z","likesCount":93,"title":"kubernetesでもぷよぷよがしたいので同じ色のPodが4個くっついたらdeleteされるcustom controller「くべくべ」を作った","uuid":"c8ff9c824b65ca371cd9","author":{"profileImageUrl":"https://qiita-image-store.s3.amazonaws.com/0/38448/profile-images/1473687769","urlName":"yoitomakenouta"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T10:57:47Z","likesCount":36,"title":"データ構造 Fenwick tree (binary indexed tree, BIT) にどっぷりと入門し、その美しき構造に心を洗われたい方のための紹介記事です!","uuid":"7d50ff180a4e5c294cb7","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352395/profile-images/1557632578","urlName":"ngtkana"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-11T08:22:19Z","likesCount":46,"title":"TypeScriptリポジトリの歩き方","uuid":"a63882c4c42c6ccf8c66","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54872/profile-images/1573246604","urlName":"sisisin"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":false,"node":{"createdAt":"2020-09-12T00:49:28Z","likesCount":32,"title":"【翻訳】wordpressのプラグインで毎月5万収益を得ている話","uuid":"ab1b2b6d5676dc55144b","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705937/profile-images/1599573315","urlName":"kirishima_app"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T09:04:26Z","likesCount":24,"title":"Ractor超入門","uuid":"f9d1917fda770bcdbe2a","author":{"profileImageUrl":"https://qiita-image-store.s3.amazonaws.com/0/191521/profile-images/1531325180","urlName":"S_H_"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":false,"node":{"createdAt":"2020-09-13T01:57:50Z","likesCount":23,"title":"音声分析におけるフーリエ変換とスペクトログラムを理解する","uuid":"6af2cc4c4be0c57bef06","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651198/profile-images/1591512068","urlName":"shirowanisan"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T11:29:02Z","likesCount":18,"title":"production-ready なGo製2Dゲームライブラリ Ebiten の紹介 \u0026 リンク集","uuid":"1fd6077327f99245b807","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348791/profile-images/1565355993","urlName":"eihigh"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T08:11:54Z","likesCount":23,"title":"自分のQiita記事をインクリメンタル検索するAlfred Workflowを作ってみた","uuid":"6909b534a59d3550093e","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/profile-images/1596162182","urlName":"ryo2132"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":false,"node":{"createdAt":"2020-09-11T06:43:05Z","likesCount":33,"title":"SQLアンチパターン ファントムファイル","uuid":"cc811929485471897c89","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257405/profile-images/1585098105","urlName":"fktnkit"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-11T14:56:56Z","likesCount":20,"title":"たった3行でVSCode(codeserver)をGoogle Colabで使う方法","uuid":"ebcc8d51476f6f7c5b72","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24723/profile-images/1574818910","urlName":"kojikanao"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T13:07:56Z","likesCount":15,"title":"Raspberry PiにVS Code Insider版をインストールする方法","uuid":"fb50b6ac5c175b9b1045","author":{"profileImageUrl":"https://qiita-image-store.s3.amazonaws.com/0/81919/profile-images/1549284868","urlName":"karaage0703"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":true,"hasCodeBlock":true,"node":{"createdAt":"2020-09-13T05:42:09Z","likesCount":24,"title":"論文と公共データベースを使って無料で始めるAI創薬","uuid":"a12f7bf594a56a2b22ba","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/329816/ba6cbc8b71c92411f763dc9334212db0a2d0f08f/x_large.png?1589641830","urlName":"kimisyo"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-11T23:36:29Z","likesCount":18,"title":"Docker × Laravel 8 Jetstream でログイン、ユーザー登録、2要素認証を実装する","uuid":"7824d1293fef4698c212","author":{"profileImageUrl":"https://qiita-image-store.s3.amazonaws.com/0/52879/profile-images/1540380605","urlName":"ucan-lab"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T13:32:05Z","likesCount":19,"title":"SwiftUIとCombineの練習がてらmacOS向けのピアノアプリ作ってみた","uuid":"0230a14ec6c3a8174568","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/67153/e88c7b2790238820700627c60482ade9228609f7/x_large.png?1575435001","urlName":"Kyome"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-10T22:39:44Z","likesCount":29,"title":" scikit-learnで機械学習パイプラインをインタラクティブに描画、HTML保存する方法","uuid":"55c7f567ca79b81c36ab","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/191401/185398f529f8156fed141d7b2e1b0a78fc114172/large.png?1599774000","urlName":"sugulu_Ogawa_ISID"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":false,"node":{"createdAt":"2020-09-11T14:02:08Z","likesCount":19,"title":"【技術書典9】ゲーム関連技術頒布物(新刊)まとめ (2020/9/11 22:00 時点)","uuid":"5f535164ee46f7e0c52d","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258608/profile-images/1566747392","urlName":"takehara-ryo"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":true,"hasCodeBlock":true,"node":{"createdAt":"2020-09-13T04:57:19Z","likesCount":26,"title":"FlutterにFirebase(Firestore)を導入したら、iOSのビルドが遅くなった","uuid":"290d61a1731e50201a61","author":{"profileImageUrl":"https://avatars1.githubusercontent.com/u/38914807?v=4","urlName":"sakaihiroaki"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T01:56:00Z","likesCount":14,"title":"PythonでDICOM画像を扱う","uuid":"f145675055796c69e228","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/680528/4c5642d036920f85daf6640335027d9a0387cecc/x_large.png?1595678226","urlName":"asparagasu"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T08:48:31Z","likesCount":13,"title":"Prism の Prism Full App (.NET Core) テンプレートを見てみよう","uuid":"cfbf5c9eaea6c5aed4e1","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/70288/edf16d4d4fdd1abcb3e61b06730b0609cdc77adb/x_large.png?1585128349","urlName":"okazuki"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":true,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T23:07:03Z","likesCount":15,"title":"C++上でPythonのコードを実行する(Boost.Pythonの利用)","uuid":"1e2c24e80212a4c4c584","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192805/profile-images/1556565825","urlName":"m4saka"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":true,"hasCodeBlock":true,"node":{"createdAt":"2020-09-10T23:04:14Z","likesCount":9,"title":"sprintf()でSQL文を生成する際、%は%でエスケープできる","uuid":"8df42e2b070bd387f1ee","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/588983/270ed08b6d978f0cf81a04772f20a27246151dda/x_large.png?1589982712","urlName":"Shigeyoshi_Koyama"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":true,"hasCodeBlock":false,"node":{"createdAt":"2020-09-13T09:55:28Z","likesCount":15,"title":"micro:bitプログラミング入門講座テキスト","uuid":"df4b502b43fde7eba35b","author":{"profileImageUrl":"https://qiita-image-store.s3.amazonaws.com/0/26536/profile-images/1473684616","urlName":"noanoa07"}}}]},"scope":"daily"}

すごく見にくいので整形すると、

{
    "trend": {
        "edges": [
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-11T07:53:22Z",
                    "likesCount": 338,
                    "title": "オブジェクト指向は単なる【整理術】だよ",
                    "uuid": "2aa9859d4da41991bbb7",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/profile-images/1567829792",
                        "urlName": "br_branch"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T03:08:08Z",
                    "likesCount": 104,
                    "title": "間違えて違うブランチで作業を始めてしまったときの対処法【Git】",
                    "uuid": "dc21de47c617b0d0eebf",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675596/profile-images/1598685340",
                        "urlName": "paleo_engineer"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-11T17:26:06Z",
                    "likesCount": 82,
                    "title": "Pythonでコンソールに綺麗なグラフを描画できる「AsciichartPy」使ってみた。",
                    "uuid": "aff5c301aacb8513a11d",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/535270/3e59522dfca791fd0ce7f98d863a9a796ca95a16/large.png?1574159780",
                        "urlName": "kkent030315"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-11T13:59:19Z",
                    "likesCount": 90,
                    "title": "CPUのキーワード(ブランド、命令セット、マイクロアーキテクチャ、拡張命令セット)の説明",
                    "uuid": "656bac4c3871031a4448",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.amazonaws.com/0/79744/profile-images/1501125720",
                        "urlName": "kaityo256"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T10:57:35Z",
                    "likesCount": 79,
                    "title": "スプラトゥーン2のプレイ動画から、やられたシーンだけをディープラーニングで自動抽出する",
                    "uuid": "acbc0906046bb7b2b1db",
                    "author": {
                        "profileImageUrl": "https://avatars3.githubusercontent.com/u/16898831?v=4",
                        "urlName": "tfandkusu"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": false,
                "node": {
                    "createdAt": "2020-09-11T09:35:58Z",
                    "likesCount": 63,
                    "title": "様々な日本語文章を、目線移動無く早く読めるオープンソースツール(Riot Shield)を公開しました",
                    "uuid": "c7e48f9f9b3e937c5dad",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.amazonaws.com/0/85111/profile-images/1499606550",
                        "urlName": "am_nimitz3"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T13:59:38Z",
                    "likesCount": 55,
                    "title": "Kubernetesの負荷試験で絶対に担保したい13のチェックリスト",
                    "uuid": "56a5442c1fc4d714c941",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45875/profile-images/1595854294",
                        "urlName": "taisho6339"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": false,
                "node": {
                    "createdAt": "2020-09-11T13:14:11Z",
                    "likesCount": 42,
                    "title": "《コロナに負けず!!》29歳で営業職からジョブチェンジ。未経験で受託開発企業から内定を頂けました。",
                    "uuid": "3eafbcc52c04241788a5",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/635167/4df257c74aac0acdbb03f0e7f63e276aa42830a0/x_large.png?1591368347",
                        "urlName": "ki-ku"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": true,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-13T11:10:37Z",
                    "likesCount": 93,
                    "title": "kubernetesでもぷよぷよがしたいので同じ色のPodが4個くっついたらdeleteされるcustom controller「くべくべ」を作った",
                    "uuid": "c8ff9c824b65ca371cd9",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.amazonaws.com/0/38448/profile-images/1473687769",
                        "urlName": "yoitomakenouta"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T10:57:47Z",
                    "likesCount": 36,
                    "title": "データ構造 Fenwick tree (binary indexed tree, BIT) にどっぷりと入門し、その美しき構造に心を洗われたい方のための紹介記事です!",
                    "uuid": "7d50ff180a4e5c294cb7",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352395/profile-images/1557632578",
                        "urlName": "ngtkana"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-11T08:22:19Z",
                    "likesCount": 46,
                    "title": "TypeScriptリポジトリの歩き方",
                    "uuid": "a63882c4c42c6ccf8c66",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54872/profile-images/1573246604",
                        "urlName": "sisisin"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": false,
                "node": {
                    "createdAt": "2020-09-12T00:49:28Z",
                    "likesCount": 32,
                    "title": "【翻訳】wordpressのプラグインで毎月5万収益を得ている話",
                    "uuid": "ab1b2b6d5676dc55144b",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705937/profile-images/1599573315",
                        "urlName": "kirishima_app"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T09:04:26Z",
                    "likesCount": 24,
                    "title": "Ractor超入門",
                    "uuid": "f9d1917fda770bcdbe2a",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.amazonaws.com/0/191521/profile-images/1531325180",
                        "urlName": "S_H_"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": false,
                "node": {
                    "createdAt": "2020-09-13T01:57:50Z",
                    "likesCount": 23,
                    "title": "音声分析におけるフーリエ変換とスペクトログラムを理解する",
                    "uuid": "6af2cc4c4be0c57bef06",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651198/profile-images/1591512068",
                        "urlName": "shirowanisan"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T11:29:02Z",
                    "likesCount": 18,
                    "title": "production-ready なGo製2Dゲームライブラリ Ebiten の紹介 & リンク集",
                    "uuid": "1fd6077327f99245b807",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348791/profile-images/1565355993",
                        "urlName": "eihigh"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T08:11:54Z",
                    "likesCount": 23,
                    "title": "自分のQiita記事をインクリメンタル検索するAlfred Workflowを作ってみた",
                    "uuid": "6909b534a59d3550093e",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/profile-images/1596162182",
                        "urlName": "ryo2132"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": false,
                "node": {
                    "createdAt": "2020-09-11T06:43:05Z",
                    "likesCount": 33,
                    "title": "SQLアンチパターン ファントムファイル",
                    "uuid": "cc811929485471897c89",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257405/profile-images/1585098105",
                        "urlName": "fktnkit"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-11T14:56:56Z",
                    "likesCount": 20,
                    "title": "たった3行でVSCode(codeserver)をGoogle Colabで使う方法",
                    "uuid": "ebcc8d51476f6f7c5b72",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24723/profile-images/1574818910",
                        "urlName": "kojikanao"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T13:07:56Z",
                    "likesCount": 15,
                    "title": "Raspberry PiにVS Code Insider版をインストールする方法",
                    "uuid": "fb50b6ac5c175b9b1045",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.amazonaws.com/0/81919/profile-images/1549284868",
                        "urlName": "karaage0703"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": true,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-13T05:42:09Z",
                    "likesCount": 24,
                    "title": "論文と公共データベースを使って無料で始めるAI創薬",
                    "uuid": "a12f7bf594a56a2b22ba",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/329816/ba6cbc8b71c92411f763dc9334212db0a2d0f08f/x_large.png?1589641830",
                        "urlName": "kimisyo"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-11T23:36:29Z",
                    "likesCount": 18,
                    "title": "Docker × Laravel 8 Jetstream でログイン、ユーザー登録、2要素認証を実装する",
                    "uuid": "7824d1293fef4698c212",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.amazonaws.com/0/52879/profile-images/1540380605",
                        "urlName": "ucan-lab"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T13:32:05Z",
                    "likesCount": 19,
                    "title": "SwiftUIとCombineの練習がてらmacOS向けのピアノアプリ作ってみた",
                    "uuid": "0230a14ec6c3a8174568",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/67153/e88c7b2790238820700627c60482ade9228609f7/x_large.png?1575435001",
                        "urlName": "Kyome"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-10T22:39:44Z",
                    "likesCount": 29,
                    "title": " scikit-learnで機械学習パイプラインをインタラクティブに描画、HTML保存する方法",
                    "uuid": "55c7f567ca79b81c36ab",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/191401/185398f529f8156fed141d7b2e1b0a78fc114172/large.png?1599774000",
                        "urlName": "sugulu_Ogawa_ISID"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": false,
                "node": {
                    "createdAt": "2020-09-11T14:02:08Z",
                    "likesCount": 19,
                    "title": "【技術書典9】ゲーム関連技術頒布物(新刊)まとめ (2020/9/11 22:00 時点)",
                    "uuid": "5f535164ee46f7e0c52d",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258608/profile-images/1566747392",
                        "urlName": "takehara-ryo"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": true,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-13T04:57:19Z",
                    "likesCount": 26,
                    "title": "FlutterにFirebase(Firestore)を導入したら、iOSのビルドが遅くなった",
                    "uuid": "290d61a1731e50201a61",
                    "author": {
                        "profileImageUrl": "https://avatars1.githubusercontent.com/u/38914807?v=4",
                        "urlName": "sakaihiroaki"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T01:56:00Z",
                    "likesCount": 14,
                    "title": "PythonでDICOM画像を扱う",
                    "uuid": "f145675055796c69e228",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/680528/4c5642d036920f85daf6640335027d9a0387cecc/x_large.png?1595678226",
                        "urlName": "asparagasu"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T08:48:31Z",
                    "likesCount": 13,
                    "title": "Prism の Prism Full App (.NET Core) テンプレートを見てみよう",
                    "uuid": "cfbf5c9eaea6c5aed4e1",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/70288/edf16d4d4fdd1abcb3e61b06730b0609cdc77adb/x_large.png?1585128349",
                        "urlName": "okazuki"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": true,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T23:07:03Z",
                    "likesCount": 15,
                    "title": "C++上でPythonのコードを実行する(Boost.Pythonの利用)",
                    "uuid": "1e2c24e80212a4c4c584",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192805/profile-images/1556565825",
                        "urlName": "m4saka"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": true,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-10T23:04:14Z",
                    "likesCount": 9,
                    "title": "sprintf()でSQL文を生成する際、%は%でエスケープできる",
                    "uuid": "8df42e2b070bd387f1ee",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/588983/270ed08b6d978f0cf81a04772f20a27246151dda/x_large.png?1589982712",
                        "urlName": "Shigeyoshi_Koyama"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": true,
                "hasCodeBlock": false,
                "node": {
                    "createdAt": "2020-09-13T09:55:28Z",
                    "likesCount": 15,
                    "title": "micro:bitプログラミング入門講座テキスト",
                    "uuid": "df4b502b43fde7eba35b",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.amazonaws.com/0/26536/profile-images/1473684616",
                        "urlName": "noanoa07"
                    }
                }
            }
        ]
    },
    "scope": "daily"
}

こんな感じでちゃんと取得できています。

さいごに

goqueryには他にも様々な取得の仕方があるので気になったからは見てみてください。(ドキュメント)

qiitaのトレンド記事を取得したいと思った方は是非使ってみてください!

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

GoでQiitaのトレンドを取得するAPI

はじめに

Qiitaのトレンド記事を取得したいと思ったのですが、公式でAPIが提供されていないので自分で作ってみました。

Qiitaトップページのdivタグにトレンド記事のJSONが入った属性があるので、これをスクレイピングして取得します。

スクレイピングには、goquery
フレームワークは Gin を使用しています。

(https://github.com/PuerkitoBio/goquery)
リポジトリはこちら

コード

まず、トレンド記事一覧のJSONを取得します。

func qiitaTrend() (string, error) {
    doc, err := goquery.NewDocument("https://qiita.com/")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return "", err
    }
    trend := doc.Find("div[data-hyperapp-app='Trend']")
    val, exists := trend.Attr("data-hyperapp-props")
    if !exists {
        return "", errors.New("Internal Server Error")
    }
    return val, nil
}

順番に見ていきます。

まずはgoquery.NewDocument(url)でドキュメントを取得します

doc, err := goquery.NewDocument("https://qiita.com/")

次にdoc.Find(セレクタ)でセレクションを取得します。
セレクタでフィルタリングして一致した要素を取得します。

今回取得しようとしている要素は、以下のようになっているのでdata-hyperapp-app="Trend"を指定しています。

<div data-hyperapp-app="Trend" data-hyperapp-props="トレンド記事のJSON" data-hyperapp-mounted="true">
...
</div>
trend := doc.Find("div[data-hyperapp-app='Trend']")

最後は欲しいデータを抜き取ります。
trend.Attr(属性)でその属性の値を取得します。
existsはその値が存在するかboolで返します。

記事一覧のJSONはdata-hyperapp-propsに入っているのでそれを指定します。

val, exists := trend.Attr("data-hyperapp-props")

以上でトレンド記事のJSONを取得することができました。
最終的なコードは以下のようになります。

package main

import (
    "errors"
    "fmt"
    "net/http"
    "os"

    "github.com/PuerkitoBio/goquery"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.New()

    r.Use(gin.Logger())
    r.Use(gin.Recovery())

    r.GET("/trend", func(c *gin.Context) {
        trend, err := qiitaTrend()
        if err != nil {
            c.JSON(http.StatusInternalServerError, err)
            return
        }
        c.String(http.StatusOK, trend)
        return
    })

    r.Run(":8080")
}

func qiitaTrend() (string, error) {
    doc, err := goquery.NewDocument("https://qiita.com/")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return "", err
    }
    trend := doc.Find("div[data-hyperapp-app='Trend']")
    val, exists := trend.Attr("data-hyperapp-props")
    if !exists {
        return "", errors.New("Internal Server Error")
    }
    return val, nil
}

Ginでのhttpサーバーについては今回は省略します。

実行してcurlで取得すると、

$ curl http://localhost:8080/trend
{"trend":{"edges":[{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-11T07:53:22Z","likesCount":338,"title":"オブジェクト指向は単なる【整理術】だよ","uuid":"2aa9859d4da41991bbb7","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/profile-images/1567829792","urlName":"br_branch"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T03:08:08Z","likesCount":104,"title":"間違えて違うブランチで作業を始めてしまったときの対処法【Git】","uuid":"dc21de47c617b0d0eebf","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675596/profile-images/1598685340","urlName":"paleo_engineer"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-11T17:26:06Z","likesCount":82,"title":"Pythonでコンソールに綺麗なグラフを描画できる「AsciichartPy」使ってみた。","uuid":"aff5c301aacb8513a11d","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/535270/3e59522dfca791fd0ce7f98d863a9a796ca95a16/large.png?1574159780","urlName":"kkent030315"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-11T13:59:19Z","likesCount":90,"title":"CPUのキーワード(ブランド、命令セット、マイクロアーキテクチャ、拡張命令セット)の説明","uuid":"656bac4c3871031a4448","author":{"profileImageUrl":"https://qiita-image-store.s3.amazonaws.com/0/79744/profile-images/1501125720","urlName":"kaityo256"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T10:57:35Z","likesCount":79,"title":"スプラトゥーン2のプレイ動画から、やられたシーンだけをディープラーニングで自動抽出する","uuid":"acbc0906046bb7b2b1db","author":{"profileImageUrl":"https://avatars3.githubusercontent.com/u/16898831?v=4","urlName":"tfandkusu"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":false,"node":{"createdAt":"2020-09-11T09:35:58Z","likesCount":63,"title":"様々な日本語文章を、目線移動無く早く読めるオープンソースツール(Riot Shield)を公開しました","uuid":"c7e48f9f9b3e937c5dad","author":{"profileImageUrl":"https://qiita-image-store.s3.amazonaws.com/0/85111/profile-images/1499606550","urlName":"am_nimitz3"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T13:59:38Z","likesCount":55,"title":"Kubernetesの負荷試験で絶対に担保したい13のチェックリスト","uuid":"56a5442c1fc4d714c941","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45875/profile-images/1595854294","urlName":"taisho6339"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":false,"node":{"createdAt":"2020-09-11T13:14:11Z","likesCount":42,"title":"《コロナに負けず!!》29歳で営業職からジョブチェンジ。未経験で受託開発企業から内定を頂けました。","uuid":"3eafbcc52c04241788a5","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/635167/4df257c74aac0acdbb03f0e7f63e276aa42830a0/x_large.png?1591368347","urlName":"ki-ku"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":true,"hasCodeBlock":true,"node":{"createdAt":"2020-09-13T11:10:37Z","likesCount":93,"title":"kubernetesでもぷよぷよがしたいので同じ色のPodが4個くっついたらdeleteされるcustom controller「くべくべ」を作った","uuid":"c8ff9c824b65ca371cd9","author":{"profileImageUrl":"https://qiita-image-store.s3.amazonaws.com/0/38448/profile-images/1473687769","urlName":"yoitomakenouta"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T10:57:47Z","likesCount":36,"title":"データ構造 Fenwick tree (binary indexed tree, BIT) にどっぷりと入門し、その美しき構造に心を洗われたい方のための紹介記事です!","uuid":"7d50ff180a4e5c294cb7","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352395/profile-images/1557632578","urlName":"ngtkana"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-11T08:22:19Z","likesCount":46,"title":"TypeScriptリポジトリの歩き方","uuid":"a63882c4c42c6ccf8c66","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54872/profile-images/1573246604","urlName":"sisisin"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":false,"node":{"createdAt":"2020-09-12T00:49:28Z","likesCount":32,"title":"【翻訳】wordpressのプラグインで毎月5万収益を得ている話","uuid":"ab1b2b6d5676dc55144b","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705937/profile-images/1599573315","urlName":"kirishima_app"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T09:04:26Z","likesCount":24,"title":"Ractor超入門","uuid":"f9d1917fda770bcdbe2a","author":{"profileImageUrl":"https://qiita-image-store.s3.amazonaws.com/0/191521/profile-images/1531325180","urlName":"S_H_"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":false,"node":{"createdAt":"2020-09-13T01:57:50Z","likesCount":23,"title":"音声分析におけるフーリエ変換とスペクトログラムを理解する","uuid":"6af2cc4c4be0c57bef06","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651198/profile-images/1591512068","urlName":"shirowanisan"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T11:29:02Z","likesCount":18,"title":"production-ready なGo製2Dゲームライブラリ Ebiten の紹介 \u0026 リンク集","uuid":"1fd6077327f99245b807","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348791/profile-images/1565355993","urlName":"eihigh"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T08:11:54Z","likesCount":23,"title":"自分のQiita記事をインクリメンタル検索するAlfred Workflowを作ってみた","uuid":"6909b534a59d3550093e","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/profile-images/1596162182","urlName":"ryo2132"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":false,"node":{"createdAt":"2020-09-11T06:43:05Z","likesCount":33,"title":"SQLアンチパターン ファントムファイル","uuid":"cc811929485471897c89","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257405/profile-images/1585098105","urlName":"fktnkit"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-11T14:56:56Z","likesCount":20,"title":"たった3行でVSCode(codeserver)をGoogle Colabで使う方法","uuid":"ebcc8d51476f6f7c5b72","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24723/profile-images/1574818910","urlName":"kojikanao"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T13:07:56Z","likesCount":15,"title":"Raspberry PiにVS Code Insider版をインストールする方法","uuid":"fb50b6ac5c175b9b1045","author":{"profileImageUrl":"https://qiita-image-store.s3.amazonaws.com/0/81919/profile-images/1549284868","urlName":"karaage0703"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":true,"hasCodeBlock":true,"node":{"createdAt":"2020-09-13T05:42:09Z","likesCount":24,"title":"論文と公共データベースを使って無料で始めるAI創薬","uuid":"a12f7bf594a56a2b22ba","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/329816/ba6cbc8b71c92411f763dc9334212db0a2d0f08f/x_large.png?1589641830","urlName":"kimisyo"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-11T23:36:29Z","likesCount":18,"title":"Docker × Laravel 8 Jetstream でログイン、ユーザー登録、2要素認証を実装する","uuid":"7824d1293fef4698c212","author":{"profileImageUrl":"https://qiita-image-store.s3.amazonaws.com/0/52879/profile-images/1540380605","urlName":"ucan-lab"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T13:32:05Z","likesCount":19,"title":"SwiftUIとCombineの練習がてらmacOS向けのピアノアプリ作ってみた","uuid":"0230a14ec6c3a8174568","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/67153/e88c7b2790238820700627c60482ade9228609f7/x_large.png?1575435001","urlName":"Kyome"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-10T22:39:44Z","likesCount":29,"title":" scikit-learnで機械学習パイプラインをインタラクティブに描画、HTML保存する方法","uuid":"55c7f567ca79b81c36ab","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/191401/185398f529f8156fed141d7b2e1b0a78fc114172/large.png?1599774000","urlName":"sugulu_Ogawa_ISID"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":false,"node":{"createdAt":"2020-09-11T14:02:08Z","likesCount":19,"title":"【技術書典9】ゲーム関連技術頒布物(新刊)まとめ (2020/9/11 22:00 時点)","uuid":"5f535164ee46f7e0c52d","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258608/profile-images/1566747392","urlName":"takehara-ryo"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":true,"hasCodeBlock":true,"node":{"createdAt":"2020-09-13T04:57:19Z","likesCount":26,"title":"FlutterにFirebase(Firestore)を導入したら、iOSのビルドが遅くなった","uuid":"290d61a1731e50201a61","author":{"profileImageUrl":"https://avatars1.githubusercontent.com/u/38914807?v=4","urlName":"sakaihiroaki"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T01:56:00Z","likesCount":14,"title":"PythonでDICOM画像を扱う","uuid":"f145675055796c69e228","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/680528/4c5642d036920f85daf6640335027d9a0387cecc/x_large.png?1595678226","urlName":"asparagasu"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":false,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T08:48:31Z","likesCount":13,"title":"Prism の Prism Full App (.NET Core) テンプレートを見てみよう","uuid":"cfbf5c9eaea6c5aed4e1","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/70288/edf16d4d4fdd1abcb3e61b06730b0609cdc77adb/x_large.png?1585128349","urlName":"okazuki"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":true,"hasCodeBlock":true,"node":{"createdAt":"2020-09-12T23:07:03Z","likesCount":15,"title":"C++上でPythonのコードを実行する(Boost.Pythonの利用)","uuid":"1e2c24e80212a4c4c584","author":{"profileImageUrl":"https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192805/profile-images/1556565825","urlName":"m4saka"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":true,"hasCodeBlock":true,"node":{"createdAt":"2020-09-10T23:04:14Z","likesCount":9,"title":"sprintf()でSQL文を生成する際、%は%でエスケープできる","uuid":"8df42e2b070bd387f1ee","author":{"profileImageUrl":"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/588983/270ed08b6d978f0cf81a04772f20a27246151dda/x_large.png?1589982712","urlName":"Shigeyoshi_Koyama"}}},{"followingLikers":[],"isLikedByViewer":false,"isNewArrival":true,"hasCodeBlock":false,"node":{"createdAt":"2020-09-13T09:55:28Z","likesCount":15,"title":"micro:bitプログラミング入門講座テキスト","uuid":"df4b502b43fde7eba35b","author":{"profileImageUrl":"https://qiita-image-store.s3.amazonaws.com/0/26536/profile-images/1473684616","urlName":"noanoa07"}}}]},"scope":"daily"}

すごく見にくいので整形すると、

{
    "trend": {
        "edges": [
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-11T07:53:22Z",
                    "likesCount": 338,
                    "title": "オブジェクト指向は単なる【整理術】だよ",
                    "uuid": "2aa9859d4da41991bbb7",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/profile-images/1567829792",
                        "urlName": "br_branch"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T03:08:08Z",
                    "likesCount": 104,
                    "title": "間違えて違うブランチで作業を始めてしまったときの対処法【Git】",
                    "uuid": "dc21de47c617b0d0eebf",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675596/profile-images/1598685340",
                        "urlName": "paleo_engineer"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-11T17:26:06Z",
                    "likesCount": 82,
                    "title": "Pythonでコンソールに綺麗なグラフを描画できる「AsciichartPy」使ってみた。",
                    "uuid": "aff5c301aacb8513a11d",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/535270/3e59522dfca791fd0ce7f98d863a9a796ca95a16/large.png?1574159780",
                        "urlName": "kkent030315"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-11T13:59:19Z",
                    "likesCount": 90,
                    "title": "CPUのキーワード(ブランド、命令セット、マイクロアーキテクチャ、拡張命令セット)の説明",
                    "uuid": "656bac4c3871031a4448",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.amazonaws.com/0/79744/profile-images/1501125720",
                        "urlName": "kaityo256"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T10:57:35Z",
                    "likesCount": 79,
                    "title": "スプラトゥーン2のプレイ動画から、やられたシーンだけをディープラーニングで自動抽出する",
                    "uuid": "acbc0906046bb7b2b1db",
                    "author": {
                        "profileImageUrl": "https://avatars3.githubusercontent.com/u/16898831?v=4",
                        "urlName": "tfandkusu"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": false,
                "node": {
                    "createdAt": "2020-09-11T09:35:58Z",
                    "likesCount": 63,
                    "title": "様々な日本語文章を、目線移動無く早く読めるオープンソースツール(Riot Shield)を公開しました",
                    "uuid": "c7e48f9f9b3e937c5dad",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.amazonaws.com/0/85111/profile-images/1499606550",
                        "urlName": "am_nimitz3"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T13:59:38Z",
                    "likesCount": 55,
                    "title": "Kubernetesの負荷試験で絶対に担保したい13のチェックリスト",
                    "uuid": "56a5442c1fc4d714c941",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45875/profile-images/1595854294",
                        "urlName": "taisho6339"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": false,
                "node": {
                    "createdAt": "2020-09-11T13:14:11Z",
                    "likesCount": 42,
                    "title": "《コロナに負けず!!》29歳で営業職からジョブチェンジ。未経験で受託開発企業から内定を頂けました。",
                    "uuid": "3eafbcc52c04241788a5",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/635167/4df257c74aac0acdbb03f0e7f63e276aa42830a0/x_large.png?1591368347",
                        "urlName": "ki-ku"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": true,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-13T11:10:37Z",
                    "likesCount": 93,
                    "title": "kubernetesでもぷよぷよがしたいので同じ色のPodが4個くっついたらdeleteされるcustom controller「くべくべ」を作った",
                    "uuid": "c8ff9c824b65ca371cd9",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.amazonaws.com/0/38448/profile-images/1473687769",
                        "urlName": "yoitomakenouta"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T10:57:47Z",
                    "likesCount": 36,
                    "title": "データ構造 Fenwick tree (binary indexed tree, BIT) にどっぷりと入門し、その美しき構造に心を洗われたい方のための紹介記事です!",
                    "uuid": "7d50ff180a4e5c294cb7",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352395/profile-images/1557632578",
                        "urlName": "ngtkana"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-11T08:22:19Z",
                    "likesCount": 46,
                    "title": "TypeScriptリポジトリの歩き方",
                    "uuid": "a63882c4c42c6ccf8c66",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54872/profile-images/1573246604",
                        "urlName": "sisisin"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": false,
                "node": {
                    "createdAt": "2020-09-12T00:49:28Z",
                    "likesCount": 32,
                    "title": "【翻訳】wordpressのプラグインで毎月5万収益を得ている話",
                    "uuid": "ab1b2b6d5676dc55144b",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705937/profile-images/1599573315",
                        "urlName": "kirishima_app"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T09:04:26Z",
                    "likesCount": 24,
                    "title": "Ractor超入門",
                    "uuid": "f9d1917fda770bcdbe2a",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.amazonaws.com/0/191521/profile-images/1531325180",
                        "urlName": "S_H_"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": false,
                "node": {
                    "createdAt": "2020-09-13T01:57:50Z",
                    "likesCount": 23,
                    "title": "音声分析におけるフーリエ変換とスペクトログラムを理解する",
                    "uuid": "6af2cc4c4be0c57bef06",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651198/profile-images/1591512068",
                        "urlName": "shirowanisan"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T11:29:02Z",
                    "likesCount": 18,
                    "title": "production-ready なGo製2Dゲームライブラリ Ebiten の紹介 & リンク集",
                    "uuid": "1fd6077327f99245b807",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348791/profile-images/1565355993",
                        "urlName": "eihigh"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T08:11:54Z",
                    "likesCount": 23,
                    "title": "自分のQiita記事をインクリメンタル検索するAlfred Workflowを作ってみた",
                    "uuid": "6909b534a59d3550093e",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/profile-images/1596162182",
                        "urlName": "ryo2132"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": false,
                "node": {
                    "createdAt": "2020-09-11T06:43:05Z",
                    "likesCount": 33,
                    "title": "SQLアンチパターン ファントムファイル",
                    "uuid": "cc811929485471897c89",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257405/profile-images/1585098105",
                        "urlName": "fktnkit"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-11T14:56:56Z",
                    "likesCount": 20,
                    "title": "たった3行でVSCode(codeserver)をGoogle Colabで使う方法",
                    "uuid": "ebcc8d51476f6f7c5b72",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24723/profile-images/1574818910",
                        "urlName": "kojikanao"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T13:07:56Z",
                    "likesCount": 15,
                    "title": "Raspberry PiにVS Code Insider版をインストールする方法",
                    "uuid": "fb50b6ac5c175b9b1045",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.amazonaws.com/0/81919/profile-images/1549284868",
                        "urlName": "karaage0703"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": true,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-13T05:42:09Z",
                    "likesCount": 24,
                    "title": "論文と公共データベースを使って無料で始めるAI創薬",
                    "uuid": "a12f7bf594a56a2b22ba",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/329816/ba6cbc8b71c92411f763dc9334212db0a2d0f08f/x_large.png?1589641830",
                        "urlName": "kimisyo"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-11T23:36:29Z",
                    "likesCount": 18,
                    "title": "Docker × Laravel 8 Jetstream でログイン、ユーザー登録、2要素認証を実装する",
                    "uuid": "7824d1293fef4698c212",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.amazonaws.com/0/52879/profile-images/1540380605",
                        "urlName": "ucan-lab"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T13:32:05Z",
                    "likesCount": 19,
                    "title": "SwiftUIとCombineの練習がてらmacOS向けのピアノアプリ作ってみた",
                    "uuid": "0230a14ec6c3a8174568",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/67153/e88c7b2790238820700627c60482ade9228609f7/x_large.png?1575435001",
                        "urlName": "Kyome"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-10T22:39:44Z",
                    "likesCount": 29,
                    "title": " scikit-learnで機械学習パイプラインをインタラクティブに描画、HTML保存する方法",
                    "uuid": "55c7f567ca79b81c36ab",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/191401/185398f529f8156fed141d7b2e1b0a78fc114172/large.png?1599774000",
                        "urlName": "sugulu_Ogawa_ISID"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": false,
                "node": {
                    "createdAt": "2020-09-11T14:02:08Z",
                    "likesCount": 19,
                    "title": "【技術書典9】ゲーム関連技術頒布物(新刊)まとめ (2020/9/11 22:00 時点)",
                    "uuid": "5f535164ee46f7e0c52d",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258608/profile-images/1566747392",
                        "urlName": "takehara-ryo"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": true,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-13T04:57:19Z",
                    "likesCount": 26,
                    "title": "FlutterにFirebase(Firestore)を導入したら、iOSのビルドが遅くなった",
                    "uuid": "290d61a1731e50201a61",
                    "author": {
                        "profileImageUrl": "https://avatars1.githubusercontent.com/u/38914807?v=4",
                        "urlName": "sakaihiroaki"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T01:56:00Z",
                    "likesCount": 14,
                    "title": "PythonでDICOM画像を扱う",
                    "uuid": "f145675055796c69e228",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/680528/4c5642d036920f85daf6640335027d9a0387cecc/x_large.png?1595678226",
                        "urlName": "asparagasu"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": false,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T08:48:31Z",
                    "likesCount": 13,
                    "title": "Prism の Prism Full App (.NET Core) テンプレートを見てみよう",
                    "uuid": "cfbf5c9eaea6c5aed4e1",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/70288/edf16d4d4fdd1abcb3e61b06730b0609cdc77adb/x_large.png?1585128349",
                        "urlName": "okazuki"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": true,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-12T23:07:03Z",
                    "likesCount": 15,
                    "title": "C++上でPythonのコードを実行する(Boost.Pythonの利用)",
                    "uuid": "1e2c24e80212a4c4c584",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192805/profile-images/1556565825",
                        "urlName": "m4saka"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": true,
                "hasCodeBlock": true,
                "node": {
                    "createdAt": "2020-09-10T23:04:14Z",
                    "likesCount": 9,
                    "title": "sprintf()でSQL文を生成する際、%は%でエスケープできる",
                    "uuid": "8df42e2b070bd387f1ee",
                    "author": {
                        "profileImageUrl": "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/588983/270ed08b6d978f0cf81a04772f20a27246151dda/x_large.png?1589982712",
                        "urlName": "Shigeyoshi_Koyama"
                    }
                }
            },
            {
                "followingLikers": [],
                "isLikedByViewer": false,
                "isNewArrival": true,
                "hasCodeBlock": false,
                "node": {
                    "createdAt": "2020-09-13T09:55:28Z",
                    "likesCount": 15,
                    "title": "micro:bitプログラミング入門講座テキスト",
                    "uuid": "df4b502b43fde7eba35b",
                    "author": {
                        "profileImageUrl": "https://qiita-image-store.s3.amazonaws.com/0/26536/profile-images/1473684616",
                        "urlName": "noanoa07"
                    }
                }
            }
        ]
    },
    "scope": "daily"
}

こんな感じでちゃんと取得できています。

さいごに

goqueryには他にも様々な取得の仕方があるので気になったからは見てみてください。(ドキュメント)

qiitaのトレンド記事を取得したいと思った方は是非使ってみてください!

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

Firebase AuthenticationとIDトークンと更新トークンとセキュリティの話

初めに

Firebase Authenticationを使用すると簡単にアプリにユーザー認証の仕組みを実装することができます。
自前のWebサーバーでFirebase Authenticationのユーザー認証を検証したい場合、IDトークンをクライアントから送ることで実現できます。
このIDトークンとセキュリティについて記載します。

以下の記事も参考にしてください。
JWTでセッション管理してはいけない

登場する単語

  • IDトークン

ユーザーの情報が含まれたJWT。
有効期限が1時間しかない。

  • 更新トークン

IDトークンを取得するために使用するトークン。

Webサーバーでのユーザー認証の流れ

  1. クライアントはFirebase Backendから更新トークンを使ってIDトークンを取得する。
  2. クライアントはWebサーバーにIDトークンを送る。
  3. WebサーバーはIDトークンを検証し、ユーザーを認証する。

IDトークンが漏洩した場合

漏洩したトークンが1時間以上前のものなら期限が切れているのでユーザー認証には使用できません。
1時間以内のものならユーザー認証に使用できてしまうので無効にする必要があります。
更新トークンを無効にすることで間接的にIDトークンも無効になります。

注意が必要なのはIDトークンをWebサーバー側でVerifyIDTokenを使用して検証している場合です。
この場合はIDトークンが無効であっても検証に成功してしまいます。
無効かどうかも含めて判別するためにはVerifyIDTokenAndCheckRevokedを使用する必要があります。
両者の違いはIDトークンの無効状態をチェックするかどうかですが、無効状態のチェックにはコストがかかります。
(この関数はgoのものですがほかの言語ではチェックするかどうかをフラグで制御します。)

// This function uses VerifyIDToken() internally to verify the ID token JWT. However, unlike
// VerifyIDToken() this function must make an RPC call to perform the revocation check.
// Developers are advised to take this additional overhead into consideration when including this
// function in an authorization flow that gets executed often.

-- firebase-admin-go のauth.go VerifyIDTokenAndCheckRevokedのコメントより引用

更新トークンが漏洩した場合

更新トークンは有効期限がほぼないのですぐに無効にする必要があります。
RevokeRefreshTokensを使用することでユーザー単位で更新トークンを無効にすることができます。
更新トークンを無効にした場合でも前述の通りVerifyIDTokenでIDトークンを検証していると成功してしまう点には注意してください。

まとめ

IDトークンは期限が1時間ととても短い
更新トークンを無効にすることでIDトークンも無効にできる
無効にした場合でもVerifyIDTokenで検証している場合は成功してしまう。
無効状態もチェックする場合はVerifyIDTokenAndCheckRevokedを使用する。

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

ISUCON10 予選でやったこと、失敗したこと

初めてのISUCON参加して680点あたりで終わった話です。
eqvalという3人チームでGo言語で挑みまして、僕はアプリ側を担当しました。

? 予選前の準備

自分はインフラ周りはあんまり詳しくなかったので, チームメンバーのnoraさんが全部準備しました。

秘伝のタレとも呼ばれているもの

環境変数, エイリアス, MySQL, nginxの設定ファイルなど

Makefileに各種コマンドのまとめ

  • 各種設定ファイルの変更を適
  • アプリのbuild, deploy
  • slow query log, access logをSlackに送る
    • pt-query-digest, alpでlogを集計した結果を送ってます

??‍? 予選でやったこと

Google Cloud Profiler

pprofを使うとベンチマークが回るタイミングに合わせて, プロファイリングかけることや
結果ファイルの変換処理などにも手間がかかると判断して, Google Cloud Profilerを使いました。
ISUCON9予選問題で模擬テストをしたときに, かなりわかりやすかったので今回も迷わず入れました。

ISUCON10のちょっと改善した結果もちょっと混じっているけどこんな感じ
isucon10q_gcp.png
なぞって検索と物件, 椅子検索処理が累計時間が多いことはわかりますし, SQLを2回以上実行していることがわかります。
(キャプチャでは分かりづらいですが, 詳細を見るとわかります)
ただ, 実行時間はアクセスログでも把握できましたので, Profilerはソースコードのどこを先に見るべきかの目安として使いました。

Goのバージョン上げ

既存の1.14.7から1.15.1に上げましたが, 特にベンチマーク点数は変動しませんでした。

DBのインスタンス分け

  • Server 1 : Webサーバー, アプリ
  • Server 2 : DB
  • Server 3 : 予備

アプリ修正

なぞって検索の修正 (ベンチマーク結果680)

  • GIS関連メソッド呼び出しを1回のみに変更
  • 検索結果の上限
変更前
for _, estate := range estatesInBoundingBox {
    ...
    // 改善 : coordinates.coordinatesToText()は同じ結果なのでforのそとで処理し変数で渡す
    query := fmt.Sprintf(`SELECT * FROM estate WHERE id = ? AND ST_Contains(ST_PolygonFromText(%s), ST_GeomFromText(%s))`, coordinates.coordinatesToText(), point)
    err = db.Get(&validatedEstate, query, estate.ID)
    if err != nil {
        ...
    } else {
        estatesInPolygon = append(estatesInPolygon, validatedEstate)
    }
}

var re EstateSearchResponse
re.Estates = []Estate{}
// 改善 : NazotteLimitが50固定なので, forで50件上限を達したらbreakするように変更
if len(estatesInPolygon) > NazotteLimit { 
    re.Estates = estatesInPolygon[:NazotteLimit]
} else {
    re.Estates = estatesInPolygon
}

JSONエンコーダーをfrancoispqt/gojayに変更したが, やめた

Profilerを見た感じ, DBからのデータ取得よりJSON Encodeに時間がかかっていたので
searchEstatessearchChairsにGoJayでEncodeするように変更しましたが, ベンチマークとProfilerの変動がなかったので取り消しました。

もっとパフォーマンスが良いJSONエンコーダーを知っておくべきでした。

DB connection数上限削除

connection数が10になっていたので, 削除して無制限にしましたが, 効果が得られませんでした。

db.SetMaxOpenConns(10)

アプリのインスタンス分け

  • Server 1 : Webサーバー, アプリ
  • Server 2 : DB
  • Server 3 : アプリ

一部処理は3番サーバーでするように、アプリの負荷を分散しましたが、ベンチマークの結果は変わりませんでした。

? 失敗したこと

DB replication

DBのCPU使用率が非常に高く、Server 2Server 3でレプリカし3番はSelect専用にしました。
ベンチマークをまわした結果、
post処理のCSVデータinsert、在庫を一つ減らすupdate処理のあとにデータ同期が終わる前に
get処理でのSlave DBからのselectが走り、不正データになりました。

INSERT時はレスポンス前に0.5秒sleepさせるなど色々と試してみましたが, 結果は同じでした。

安い椅子一覧のキャッシュ

getLowPricedChair()もアクセス数が多かったので、レスポンスを早くするために
安い椅子一覧の結果をメモリ上にキャッシュしました。
insert, update処理があるときにスライスに再格納するようにしました。

var StockChairs []Chair
func loadChairs() {
    StockChairs = make([]Chair, 0)
    query := `SELECT * FROM chair WHERE stock > 0 ORDER BY price ASC, id ASC LIMIT ?`
    db.Select(&StockChairs, query, Limit)
}

アプリが動いてWeb上では結果が確認できましたが、ベンチマークでは不正データになり、0点の結果でした。

COUNT(*) SQLの改善

物件, 椅子の検索結果で該当ページデータと, 全ページ数計算用のためにcount(*)を返していたのを一つのSQLにしました。

変更前
SELECT ... FROM estate WHERE {同じ条件} LIMIT ? OFFSET ?;
SELECT COUNT(*) FROM estate WHERE {同じ条件};
変更後
SELECT ..., estate_cnt.cnt
FROM estate 
JOIN (SELECT COUNT(*) cnt FROM estate WHERE {同じ条件}) estate_cnt
WHERE {同じ条件} LIMIT ? OFFSET ?;

このときも、アプリは動いてたので、これでよし!と思いましたが
ベンチマークでは不正データと判断されて0点になってしまいました。

ついでに、SQLは既存に戻し、COUNT(*)だけ goroutineで並行処理してみてもアプリが動きましたが、ベンチマークは0点になりました。

SQLのOR条件を 5つから1つに減らす (対応できなかった)

自分がcount(*)問題に時間を使いすぎてしまい、
メンバーから分析して改善余地がある部分だと共有はもらった、
椅子にあうおすすめ物件のORを一つに減らすところに対応できませんでした。

もっと早い段階で、自分が手こずっていたところを諦めて、先にこの対応をすれば良かったなと後悔します・・

変更前
var estates []Estate
    w := chair.Width
    h := chair.Height
    d := chair.Depth
    query = `SELECT * FROM estate WHERE (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) ORDER BY popularity DESC, id ASC LIMIT ?`
    err = db.Select(&estates, query, w, h, w, d, h, w, h, d, d, w, d, h, Limit)

? 感想

初めての参加で、自分の知識と力不足をものすごく感じて悔しかったですが、それ以上にすごくドキドキして楽しかった一日でした!

テーブルも2つですごくシンプルで、0.1秒基準のSlow queryもなかったので、ボトルネック探すのが大変でした。
また、本格的にアプリ側で手を動かし始めたのが中盤が超えてからだったので、慌てたり頭が回らなかったりもしました。
競技がおわったあとに気づきましたが、アプリ側でやれることは多かったと思います。
まだまだ、自分がgoになれてなかったのも原因かと思います。

インフラの知識をつけることと、もっとgoをすらすら書けるようにして、来年のISUCON11にも参加します!

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

GoでAtCoder Library (ACL)

GoでAtCoder Library (ACL)のスニペットを作りました。
コピペするだけで使えると思います。
各コードの使い方はAtCoder Library Practice Contestの提出を見てください。

注意

動作保証はできないので、使用は自己責任でお願いします。
modintはありません。他にもところどころ実装していないものがあります。
AtCoder Library Practice Contestの全ての問題でACしましたが、ここで使用していない機能は動作確認していないものがあります。

実装に問題があったらご指摘いただけると幸いです。

目次

・DSU (Union-Find)
・Fenwick Tree (BIT)
・math (floor sumなど)
・MaxFlow
・MinCostFlow
・Convolution
・SCC
・Two SAT
・strings
・Segtree
・LazySegtree
・bits

Disjoint Set Union (DSU, Union-Find)

UnionFind.go
type UnionFind struct {
    n   int
    par []int
}

func newUnionFind(n int) *UnionFind {
    uf := new(UnionFind)
    uf.n = n
    uf.par = make([]int, n)
    for i, _ := range uf.par {
        uf.par[i] = -1
    }
    return uf
}
func (uf UnionFind) root(x int) int {
    if uf.par[x] < 0 {
        return x
    }
    uf.par[x] = uf.root(uf.par[x])
    return uf.par[x]
}
func (uf UnionFind) unite(x, y int) {
    rx, ry := uf.root(x), uf.root(y)
    if rx != ry {
        if uf.size(rx) > uf.size(ry) {
            rx, ry = ry, rx
        }
        uf.par[ry] += uf.par[rx]
        uf.par[rx] = ry
    }
}
func (uf UnionFind) same(x, y int) bool {
    return uf.root(x) == uf.root(y)
}
func (uf UnionFind) size(x int) int {
    return -uf.par[uf.root(x)]
}
func (uf UnionFind) groups() [][]int {
    rootBuf, groupSize := make([]int, uf.n), make([]int, uf.n)
    for i := 0; i < uf.n; i++ {
        rootBuf[i] = uf.root(i)
        groupSize[rootBuf[i]]++
    }
    res := make([][]int, uf.n)
    for i := 0; i < uf.n; i++ {
        res[i] = make([]int, 0, groupSize[i])
    }
    for i := 0; i < uf.n; i++ {
        res[rootBuf[i]] = append(res[rootBuf[i]], i)
    }
    result := make([][]int, 0, uf.n)
    for i := 0; i < uf.n; i++ {
        if len(res[i]) != 0 {
            r := make([]int, len(res[i]))
            copy(r, res[i])
            result = append(res, r)
        }
    }
    return result
}

groups()の動作確認はしていません。
AtCoder Library Practice Contestでの提出

・参考
https://qiita.com/ofutonfuton/items/c17dfd33fc542c222396
https://qiita.com/haru1843/items/2295d0ec1f5002bd5c33

Fenwick Tree (BIT)

BIT.go
type BIT struct {
    v []int
}

func newBIT(n int) *BIT {
    b := new(BIT)
    b.v = make([]int, n)
    return b
}
func (b BIT) sum(a int) int {
    ret := 0
    for i := a + 1; i > 0; i -= i & -i {
        ret += b.v[i-1]
    }
    return ret
}
func (b BIT) rangeSum(x, y int) int {
    if y == 0 {
        return 0
    }
    y--
    if x == 0 {
        return b.sum(y)
    } else {
        return b.sum(y) - b.sum(x-1)
    }
}
func (b BIT) add(a, w int) {
    n := len(b.v)
    for i := a + 1; i <= n; i += i & -i {
        b.v[i-1] += w
    }
}

ACLのコードをあまり見ていないので、挙動が異なる部分があるかもしれません。
AtCoder Library Practice Contestでの提出

・参考
http://hos.ac/slides/20140319_bit.pdf

math

math.go
func floorSum(n, m, a, b int) int {
    ret := 0
    if a >= m {
        ret += (n - 1) * n * (a / m) / 2
        a %= m
    }
    if b >= m {
        ret += n * (b / m)
        b %= m
    }
    ymx := (a*n + b) / m
    xmx := ymx*m - b
    if ymx == 0 {
        return ret
    }
    ret += (n - (xmx+a-1)/a) * ymx
    ret += floorSum(ymx, a, m, (a-xmx%a)%a)
    return ret
}
func crt(r, m []int) [2]int {
    n := len(r)
    r0, m0 := 0, 1
    for i := 0; i < n; i++ {
        r1 := safeMod(r[i], m[i])
        m1 := m[i]
        if m0 < m1 {
            r0, r1 = r1, r0
            m0, m1 = m1, m0
        }
        if m0%m1 == 0 {
            if r0%m1 != r1 {
                return [2]int{0, 0}
            }
            continue
        }
        tmp := invGcd(m0, m1)
        g, im := tmp[0], tmp[1]
        u1 := m1 / g
        if (r1-r0)%g != 0 {
            return [2]int{0, 0}
        }
        x := (r1 - r0) / g % u1 * im % u1
        r0 += x * m0
        m0 *= u1
        if r0 < 0 {
            r0 += m0
        }
    }
    return [2]int{r0, m0}
}
func powMod(x, n, m int) int {
    if m == 1 {
        return 0
    }
    r := 1
    y := x % m
    if y < 0 {
        y += m
    }
    for n != 0 {
        if (n & 1) == 1 {
            r = (r * y) % m
        }
        y = (y * y) % m
        n >>= 1
    }
    return r
}
func safeMod(x, d int) int {
    x %= d
    if x < 0 {
        x += d
    }
    return x
}
func invMod(x, m int) int {
    z := invGcd(x, m)
    return z[1]
}
func invGcd(a, b int) [2]int {
    a = a % b
    if a < 0 {
        a += b
    }
    s, t := b, a
    m0, m1 := 0, 1
    for t != 0 {
        u := s / t
        s -= t * u
        m0 -= m1 * u
        tmp := s
        s = t
        t = tmp
        tmp = m0
        m0 = m1
        m1 = tmp
    }
    if m0 < 0 {
        m0 += b / s
    }
    return [2]int{s, m0}
}
func primitiveRoot(m int) int {
    if m == 2 {
        return 1
    } else if m == 167772161 {
        return 3
    } else if m == 469762049 {
        return 3
    } else if m == 754974721 {
        return 11
    } else if m == 998244353 {
        return 3
    }
    divs := make([]int, 20)
    divs[0] = 2
    cnt := 1
    x := (m - 1) / 2
    for (x % 2) == 0 {
        x /= 2
    }
    for i := 3; i*i <= x; i += 2 {
        if x%i == 0 {
            divs[cnt] = i
            cnt++
            for x%i == 0 {
                x /= i
            }
        }
    }
    if x > 1 {
        divs[cnt] = x
        cnt++
    }
    for g := 2; ; g++ {
        ok := true
        for i := 0; i < cnt; i++ {
            if powMod(g, (m-1)/divs[i], m) == 1 {
                ok = false
                break
            }
        }
        if ok {
            return g
        }
    }
}

AtCoder Library Practice Contestでの提出

MaxFlow

MaxFlow.go
type Edge struct {
    from int
    to   int
    capa int
    flow int
}
type _Edge struct {
    to   int
    rev  int
    capa int
}
type MaxFlow struct {
    n   int
    pos [][2]int
    g   [][]_Edge
}

func newMaxFlow(n int) *MaxFlow {
    return &MaxFlow{
        n: n,
        g: make([][]_Edge, n),
    }
}
func (mf *MaxFlow) smaller(a, b int) int {
    if a < b {
        return a
    }
    return b
}
func (mf *MaxFlow) AddEdge(from, to, capa int) int {
    m := len(mf.pos)
    mf.pos = append(mf.pos, [2]int{from, len(mf.g[from])})
    mf.g[from] = append(mf.g[from], _Edge{to, len(mf.g[to]), capa})
    mf.g[to] = append(mf.g[to], _Edge{from, len(mf.g[from]) - 1, 0})
    return m
}
func (mf *MaxFlow) GetEdge(i int) Edge {
    _e := mf.g[mf.pos[i][0]][mf.pos[i][1]]
    _re := mf.g[_e.to][_e.rev]
    return Edge{mf.pos[i][0], _e.to, _e.capa + _re.capa, _re.capa}
}
func (mf *MaxFlow) EdgesList() []Edge {
    m := len(mf.pos)
    result := make([]Edge, 0, m)
    for i := 0; i < m; i++ {
        result = append(result, mf.GetEdge(i))
    }
    return result
}
func (mf *MaxFlow) ChangeEdge(i, newCapa, newFlow int) {
    _e := &mf.g[mf.pos[i][0]][mf.pos[i][1]]
    _re := &mf.g[_e.to][_e.rev]
    _e.capa = newCapa - newFlow
    _re.capa = newFlow
}
func (mf *MaxFlow) Flow(s, t int) int {
    return mf.FlowL(s, t, int(1e+18))
}
func (mf *MaxFlow) FlowL(s, t, flowLim int) int {
    level := make([]int, mf.n)
    iter := make([]int, mf.n)
    bfs := func() {
        for i, _ := range level {
            level[i] = -1
        }
        level[s] = 0
        q := make([]int, 0, mf.n)
        q = append(q, s)
        for len(q) != 0 {
            v := q[0]
            q = q[1:]
            for _, e := range mf.g[v] {
                if e.capa == 0 || level[e.to] >= 0 {
                    continue
                }
                level[e.to] = level[v] + 1
                if e.to == t {
                    return
                }
                q = append(q, e.to)
            }
        }
    }
    var dfs func(v, up int) int
    dfs = func(v, up int) int {
        if v == s {
            return up
        }
        res := 0
        lv := level[v]
        for ; iter[v] < len(mf.g[v]); iter[v]++ {
            e := &mf.g[v][iter[v]]
            if lv <= level[e.to] || mf.g[e.to][e.rev].capa == 0 {
                continue
            }
            d := dfs(e.to, mf.smaller(up-res, mf.g[e.to][e.rev].capa))
            if d <= 0 {
                continue
            }
            mf.g[v][iter[v]].capa += d
            mf.g[e.to][e.rev].capa -= d
            res += d
            if res == up {
                break
            }
        }
        return res
    }
    flow := 0
    for flow < flowLim {
        bfs()
        if level[t] == -1 {
            break
        }
        for i, _ := range iter {
            iter[i] = 0
        }
        for flow < flowLim {
            f := dfs(t, flowLim-flow)
            if f == 0 {
                break
            }
            flow += f
        }
    }
    return flow
}
func (mf *MaxFlow) MinCut(s int) []bool {
    visited := make([]bool, mf.n)
    q := make([]int, 0, mf.n)
    q = append(q, s)
    for len(q) != 0 {
        p := q[0]
        q = q[1:]
        visited[p] = true
        for _, e := range mf.g[p] {
            if e.capa > 0 && !visited[e.to] {
                visited[e.to] = true
                q = append(q, e.to)
            }
        }
    }
    return visited
}

AtCoder Library Practice Contestでの提出

MinCostFlow

MinCostFlow.go
import "container/heap"

type MinCostFlow struct {
    n   int
    pos [][2]int
    g   [][]_Edge
}
type _Edge struct {
    to   int
    rev  int
    capa int
    cost int
}
type Edge struct {
    from int
    to   int
    capa int
    flow int
    cost int
}

func newMinCostFlow(n int) *MinCostFlow {
    return &MinCostFlow{n: n, g: make([][]_Edge, n)}
}
func (mcf *MinCostFlow) AddEdge(from, to, capa, cost int) int {
    m := len(mcf.pos)
    mcf.pos = append(mcf.pos, [2]int{from, len(mcf.g[from])})
    mcf.g[from] = append(mcf.g[from], _Edge{to, len(mcf.g[to]), capa, cost})
    mcf.g[to] = append(mcf.g[to], _Edge{from, len(mcf.g[from]) - 1, 0, -cost})
    return m
}
func (mcf *MinCostFlow) GetEdge(i int) Edge {
    e := mcf.g[mcf.pos[i][0]][mcf.pos[i][1]]
    re := mcf.g[e.to][e.rev]
    return Edge{mcf.pos[i][0], e.to, e.capa + re.capa, re.capa, e.cost}
}
func (mcf *MinCostFlow) Edges() []Edge {
    m := len(mcf.pos)
    res := make([]Edge, m)
    for i := 0; i < m; i++ {
        res[i] = mcf.GetEdge(i)
    }
    return res
}
func (mcf *MinCostFlow) Flow(s, t int) [2]int {
    res := mcf.Slope(s, t)
    return res[len(res)-1]
}
func (mcf *MinCostFlow) FlowL(s, t, flowLim int) [2]int {
    res := mcf.SlopeL(s, t, flowLim)
    return res[len(res)-1]
}
func (mcf *MinCostFlow) Slope(s, t int) [][2]int {
    return mcf.SlopeL(s, t, int(1e+18))
}
func (mcf *MinCostFlow) SlopeL(s, t, flowLim int) [][2]int {
    dual, dist := make([]int, mcf.n), make([]int, mcf.n)
    pv, pe := make([]int, mcf.n), make([]int, mcf.n)
    vis := make([]bool, mcf.n)
    dualRef := func() bool {
        for i := 0; i < mcf.n; i++ {
            dist[i], pv[i], pe[i] = int(1e+18), -1, -1
            vis[i] = false
        }
        pq := make(PriorityQueue, 0)
        heap.Init(&pq)
        item := &Item{value: s, priority: 0}
        dist[s] = 0
        heap.Push(&pq, item)
        for pq.Len() != 0 {
            v := heap.Pop(&pq).(*Item).value
            if vis[v] {
                continue
            }
            vis[v] = true
            if v == t {
                break
            }
            for i := 0; i < len(mcf.g[v]); i++ {
                e := mcf.g[v][i]
                if vis[e.to] || e.capa == 0 {
                    continue
                }
                cost := e.cost - dual[e.to] + dual[v]
                if dist[e.to]-dist[v] > cost {
                    dist[e.to] = dist[v] + cost
                    pv[e.to] = v
                    pe[e.to] = i
                    item := &Item{value: e.to, priority: dist[e.to]}
                    heap.Push(&pq, item)
                }
            }
        }
        if !vis[t] {
            return false
        }
        for v := 0; v < mcf.n; v++ {
            if !vis[v] {
                continue
            }
            dual[v] -= dist[t] - dist[v]
        }
        return true
    }
    flow, cost, prevCost := 0, 0, -1
    res := make([][2]int, 0, mcf.n)
    res = append(res, [2]int{flow, cost})
    for flow < flowLim {
        if !dualRef() {
            break
        }
        c := flowLim - flow
        for v := t; v != s; v = pv[v] {
            c = mcf.Min(c, mcf.g[pv[v]][pe[v]].capa)
        }
        for v := t; v != s; v = pv[v] {
            mcf.g[pv[v]][pe[v]].capa -= c
            mcf.g[v][mcf.g[pv[v]][pe[v]].rev].capa += c
        }
        d := -dual[s]
        flow += c
        cost += c * d
        if prevCost == d {
            res = res[:len(res)-1]
        }
        res = append(res, [2]int{flow, cost})
        prevCost = cost
    }
    return res
}
func (mcf *MinCostFlow) Min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

type Item struct {
    value    int
    priority int
    index    int
}
type PriorityQueue []*Item

func (pq PriorityQueue) Len() int { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool {
    return pq[i].priority < pq[j].priority
}
func (pq PriorityQueue) Swap(i, j int) {
    pq[i], pq[j] = pq[j], pq[i]
    pq[i].index = i
    pq[j].index = j
}
func (pq *PriorityQueue) Push(x interface{}) {
    n := len(*pq)
    item := x.(*Item)
    item.index = n
    *pq = append(*pq, item)
}
func (pq *PriorityQueue) Pop() interface{} {
    old := *pq
    n := len(old)
    item := old[n-1]
    old[n-1] = nil
    item.index = -1
    *pq = old[0 : n-1]
    return item
}
func (pq *PriorityQueue) update(item *Item, value int, priority int) {
    item.value = value
    item.priority = priority
    heap.Fix(pq, item.index)
}

AtCoder Library Practice Contestでの提出

Convolution

Convolution.go
import "math/bits"

func ceilPow2(n int) int {
    x := 0
    for (1 << uint(x)) < n {
        x++
    }
    return x
}
func bsf(n uint) int {
    return bits.TrailingZeros(n)
}
func primitiveRoot(m int) int {
    if m == 2 {
        return 1
    } else if m == 167772161 {
        return 3
    } else if m == 469762049 {
        return 3
    } else if m == 754974721 {
        return 11
    } else if m == 998244353 {
        return 3
    }
    divs := make([]int, 20)
    divs[0] = 2
    cnt := 1
    x := (m - 1) / 2
    for (x % 2) == 0 {
        x /= 2
    }
    for i := 3; i*i <= x; i += 2 {
        if x%i == 0 {
            divs[cnt] = i
            cnt++
            for x%i == 0 {
                x /= i
            }
        }
    }
    if x > 1 {
        divs[cnt] = x
        cnt++
    }
    for g := 2; ; g++ {
        ok := true
        for i := 0; i < cnt; i++ {
            if powMod(g, (m-1)/divs[i], m) == 1 {
                ok = false
                break
            }
        }
        if ok {
            return g
        }
    }
}
func powMod(x, n, m int) int {
    if m == 1 {
        return 0
    }
    r := 1
    y := x % m
    if y < 0 {
        y += m
    }
    for n != 0 {
        if (n & 1) == 1 {
            r = (r * y) % m
        }
        y = (y * y) % m
        n >>= 1
    }
    return r
}
func butterfly(a []int, prm int) {
    g := primitiveRoot(prm)
    n := len(a)
    h := ceilPow2(n)
    first := true
    se := make([]int, 30)
    if first {
        first = false
        es, ies := make([]int, 30), make([]int, 30)
        cnt2 := bsf(uint(prm - 1))
        e := powMod(g, (prm-1)>>uint(cnt2), prm)
        ie := invGcd(e, prm)[1]
        for i := cnt2; i >= 2; i-- {
            es[i-2] = e
            ies[i-2] = ie
            e *= e
            e %= prm
            ie *= ie
            ie %= prm
        }
        now := 1
        for i := 0; i <= cnt2-2; i++ {
            se[i] = es[i] * now
            se[i] %= prm
            now *= ies[i]
            now %= prm
        }
    }
    for ph := 1; ph <= h; ph++ {
        w := 1 << uint(ph-1)
        p := 1 << uint(h-ph)
        now := 1
        for s := 0; s < w; s++ {
            offset := s << uint(h-ph+1)
            for i := 0; i < p; i++ {
                l := a[i+offset]
                r := a[i+offset+p] * now % prm
                a[i+offset] = l + r
                a[i+offset+p] = l - r
                a[i+offset] %= prm
                a[i+offset+p] %= prm
                if a[i+offset+p] < 0 {
                    a[i+offset+p] += prm
                }
            }
            now *= se[bsf(^(uint(s)))]
            now %= prm
        }
    }
}
func butterflyInv(a []int, prm int) {
    g := primitiveRoot(prm)
    n := len(a)
    h := ceilPow2(n)
    first := true
    sie := make([]int, 30)
    if first {
        first = false
        es, ies := make([]int, 30), make([]int, 30)
        cnt2 := bsf(uint(prm - 1))
        e := powMod(g, (prm-1)>>uint(cnt2), prm)
        ie := invGcd(e, prm)[1]
        for i := cnt2; i >= 2; i-- {
            es[i-2] = e
            ies[i-2] = ie
            e *= e
            e %= prm
            ie *= ie
            ie %= prm
        }
        now := 1
        for i := 0; i <= cnt2-2; i++ {
            sie[i] = ies[i] * now
            sie[i] %= prm
            now *= es[i]
            now %= prm
        }
    }
    for ph := h; ph >= 1; ph-- {
        w := 1 << uint(ph-1)
        p := 1 << uint(h-ph)
        inow := 1
        for s := 0; s < w; s++ {
            offset := s << uint(h-ph+1)
            for i := 0; i < p; i++ {
                l := a[i+offset]
                r := a[i+offset+p]
                a[i+offset] = l + r
                a[i+offset+p] = (prm + l - r) * inow
                a[i+offset] %= prm
                a[i+offset+p] %= prm
            }
            inow *= sie[bsf(^uint(s))]
            inow %= prm
        }
    }
}
func convMin(a, b int) int {
    if a < b {
        return a
    }
    return b
}
func convolution(p, q []int, prm int) []int {
    n, m := len(p), len(q)
    if n == 0 || m == 0 {
        return []int{}
    }
    if convMin(n, m) <= 60 {
        var a, b []int
        if n < m {
            n, m = m, n
            a = make([]int, n)
            b = make([]int, m)
            copy(a, q)
            copy(b, p)
        } else {
            a = make([]int, n)
            b = make([]int, m)
            copy(a, p)
            copy(b, q)
        }
        ans := make([]int, n+m-1)
        for i := 0; i < n; i++ {
            for j := 0; j < m; j++ {
                ans[i+j] += a[i] * b[j] % prm
                ans[i+j] %= prm
            }
        }
        return ans
    }
    z := 1 << uint(ceilPow2(n+m-1))
    a, b := make([]int, z), make([]int, z)
    for i := 0; i < n; i++ {
        a[i] = p[i]
    }
    for i := 0; i < m; i++ {
        b[i] = q[i]
    }
    butterfly(a, prm)
    butterfly(b, prm)
    for i := 0; i < z; i++ {
        a[i] *= b[i]
        a[i] %= prm
    }
    butterflyInv(a, prm)
    a = a[:n+m-1]
    iz := invGcd(z, prm)[1]
    for i := 0; i < n+m-1; i++ {
        a[i] *= iz
        a[i] %= prm
    }
    return a
}
func convolutionLL(a, b []int, prm int) []int {
    n, m := len(a), len(b)
    for n != 0 || m != 0 {
        return []int{}
    }
    MOD1 := 754974721
    MOD2 := 167772161
    MOD3 := 469762049
    M2M3 := MOD2 * MOD3
    M1M3 := MOD1 * MOD3
    M1M2 := MOD1 * MOD2
    M1M2M3 := MOD1 * MOD2 * MOD3
    i1 := invGcd(MOD2*MOD3, MOD1)[1]
    i2 := invGcd(MOD1*MOD3, MOD2)[1]
    i3 := invGcd(MOD1*MOD2, MOD3)[1]
    c1 := convolution(a, b, MOD1)
    c2 := convolution(a, b, MOD2)
    c3 := convolution(a, b, MOD3)
    c := make([]int, n+m-1)
    for i := 0; i < n+m-1; i++ {
        x := 0
        x += (c1[i] * i1) % MOD1 * M2M3
        x += (c2[i] * i2) % MOD2 * M1M3
        x += (c3[i] * i3) % MOD3 * M1M2
        t := x % MOD1
        if t < 0 {
            t += MOD1
        }
        diff := c1[i] - t
        if diff < 0 {
            diff += MOD1
        }
        offset := []int{0, 0, M1M2M3, 2 * M1M2M3, 3 * M1M2M3}
        x -= offset[diff%5]
        c[i] = x
    }
    return c
}
func invGcd(a, b int) [2]int {
    a = a % b
    if a < 0 {
        a += b
    }
    s, t := b, a
    m0, m1 := 0, 1
    for t != 0 {
        u := s / t
        s -= t * u
        m0 -= m1 * u
        tmp := s
        s = t
        t = tmp
        tmp = m0
        m0 = m1
        m1 = tmp
    }
    if m0 < 0 {
        m0 += b / s
    }
    return [2]int{s, m0}
}

AtCoder Library Practice Contestでの提出

SCC

SCC.go
type SccGraph struct {
    n     int
    edges [][2]int
}
type Csr struct {
    start []int
    elist []int
}

func newSccGraph(n int) *SccGraph {
    scc := new(SccGraph)
    scc.n = n
    return scc
}
func (scc *SccGraph) NumVertices() int {
    return scc.n
}
func (scc *SccGraph) AddEdge(from int, to int) {
    scc.edges = append(scc.edges, [2]int{from, to})
}
func (c *Csr) csr(n int, edges [][2]int) {
    c.start = make([]int, n+1)
    c.elist = make([]int, len(edges))
    for _, e := range edges {
        c.start[e[0]+1]++
    }
    for i := 1; i <= n; i++ {
        c.start[i] += c.start[i-1]
    }
    counter := make([]int, n+1)
    copy(counter, c.start)
    for _, e := range edges {
        c.elist[counter[e[0]]] = e[1]
        counter[e[0]]++
    }
}
func (scc *SccGraph) SccIds() (int, []int) {
    g := new(Csr)
    g.csr(scc.n, scc.edges)
    nowOrd, groupNum := 0, 0
    visited, low := make([]int, 0, scc.n), make([]int, scc.n)
    ord, ids := make([]int, scc.n), make([]int, scc.n)
    for i := 0; i < scc.n; i++ {
        ord[i] = -1
    }
    var dfs func(v int)
    dfs = func(v int) {
        low[v], ord[v] = nowOrd, nowOrd
        nowOrd++
        visited = append(visited, v)
        for i := g.start[v]; i < g.start[v+1]; i++ {
            to := g.elist[i]
            if ord[to] == -1 {
                dfs(to)
                low[v] = scc.min(low[v], low[to])
            } else {
                low[v] = scc.min(low[v], ord[to])
            }
        }
        if low[v] == ord[v] {
            for {
                u := visited[len(visited)-1]
                visited = visited[:len(visited)-1]
                ord[u] = scc.n
                ids[u] = groupNum
                if u == v {
                    break
                }
            }
            groupNum++
        }
    }
    for i := 0; i < scc.n; i++ {
        if ord[i] == -1 {
            dfs(i)
        }
    }
    for i := 0; i < len(ids); i++ {
        ids[i] = groupNum - 1 - ids[i]
    }
    return groupNum, ids
}
func (scc *SccGraph) min(x, y int) int {
    if x < y {
        return x
    } else {
        return y
    }
}
func (scc *SccGraph) Scc() [][]int {
    groupNum, ids := scc.SccIds()
    counts := make([]int, groupNum)
    for _, x := range ids {
        counts[x]++
    }
    groups := make([][]int, groupNum)
    for i := 0; i < groupNum; i++ {
        groups[i] = make([]int, 0, counts[i])
    }
    for i := 0; i < scc.n; i++ {
        groups[ids[i]] = append(groups[ids[i]], i)
    }
    return groups
}

AtCoder Library Practice Contestでの提出

Two SAT

TwoSAT.go
type SccGraph struct {
    n     int
    edges [][2]int
}
type Csr struct {
    start []int
    elist []int
}
type TwoSat struct {
    n        int
    answer   []bool
    sccGraph *SccGraph
}

func newSccGraph(n int) *SccGraph {
    scc := new(SccGraph)
    scc.n = n
    return scc
}
func (scc *SccGraph) NumVertices() int {
    return scc.n
}
func (scc *SccGraph) AddEdge(from int, to int) {
    scc.edges = append(scc.edges, [2]int{from, to})
}
func (c *Csr) csr(n int, edges [][2]int) {
    c.start = make([]int, n+1)
    c.elist = make([]int, len(edges))
    for _, e := range edges {
        c.start[e[0]+1]++
    }
    for i := 1; i <= n; i++ {
        c.start[i] += c.start[i-1]
    }
    counter := make([]int, n+1)
    copy(counter, c.start)
    for _, e := range edges {
        c.elist[counter[e[0]]] = e[1]
        counter[e[0]]++
    }
}
func (scc *SccGraph) SccIds() (int, []int) {
    g := new(Csr)
    g.csr(scc.n, scc.edges)
    nowOrd, groupNum := 0, 0
    visited, low := make([]int, 0, scc.n), make([]int, scc.n)
    ord, ids := make([]int, scc.n), make([]int, scc.n)
    for i := 0; i < scc.n; i++ {
        ord[i] = -1
    }
    var dfs func(v int)
    dfs = func(v int) {
        low[v], ord[v] = nowOrd, nowOrd
        nowOrd++
        visited = append(visited, v)
        for i := g.start[v]; i < g.start[v+1]; i++ {
            to := g.elist[i]
            if ord[to] == -1 {
                dfs(to)
                low[v] = scc.min(low[v], low[to])
            } else {
                low[v] = scc.min(low[v], ord[to])
            }
        }
        if low[v] == ord[v] {
            for {
                u := visited[len(visited)-1]
                visited = visited[:len(visited)-1]
                ord[u] = scc.n
                ids[u] = groupNum
                if u == v {
                    break
                }
            }
            groupNum++
        }
    }
    for i := 0; i < scc.n; i++ {
        if ord[i] == -1 {
            dfs(i)
        }
    }
    for i := 0; i < len(ids); i++ {
        ids[i] = groupNum - 1 - ids[i]
    }
    return groupNum, ids
}
func (scc *SccGraph) min(x, y int) int {
    if x < y {
        return x
    } else {
        return y
    }
}
func (scc *SccGraph) Scc() [][]int {
    groupNum, ids := scc.SccIds()
    counts := make([]int, groupNum)
    for _, x := range ids {
        counts[x]++
    }
    groups := make([][]int, groupNum)
    for i := 0; i < groupNum; i++ {
        groups[i] = make([]int, 0, counts[i])
    }
    for i := 0; i < scc.n; i++ {
        groups[ids[i]] = append(groups[ids[i]], i)
    }
    return groups
}
func newTwoSat(n int) *TwoSat {
    ts := new(TwoSat)
    ts.n = n
    ts.answer = make([]bool, n)
    ts.sccGraph = newSccGraph(n * 2)
    return ts
}
func (ts *TwoSat) AddClause(i int, f bool, j int, g bool) {
    ts.sccGraph.AddEdge(2*i+ts.judge(f, 0, 1), 2*j+ts.judge(g, 1, 0))
    ts.sccGraph.AddEdge(2*j+ts.judge(g, 0, 1), 2*i+ts.judge(f, 1, 0))
}
func (ts *TwoSat) Satisfiable() bool {
    _, id := ts.sccGraph.SccIds()
    for i := 0; i < ts.n; i++ {
        if id[i*2] == id[2*i+1] {
            return false
        }
        ts.answer[i] = id[2*i] < id[2*i+1]
    }
    return true
}
func (ts *TwoSat) judge(f bool, a int, b int) int {
    if f {
        return a
    } else {
        return b
    }
}
func (ts *TwoSat) Answer() []bool {
    return ts.answer
}

AtCoder Library Practice Contestでの提出

strings

strings.go
import "sort"

func saIs(s []int, upper int) []int {
    n := len(s)
    if n == 0 {
        return []int{}
    }
    if n == 1 {
        return []int{0}
    }
    if n == 2 {
        if s[0] < s[1] {
            return []int{0, 1}
        } else {
            return []int{1, 0}
        }
    }
    sa := make([]int, n)
    ls := make([]bool, n)
    for i := n - 2; i >= 0; i-- {
        if s[i] == s[i+1] {
            ls[i] = ls[i+1]
        } else {
            ls[i] = s[i] < s[i+1]
        }
    }
    sumL, sumS := make([]int, upper+1), make([]int, upper+1)
    for i := 0; i < n; i++ {
        if !ls[i] {
            sumS[s[i]]++
        } else {
            sumL[s[i]+1]++
        }
    }
    for i := 0; i <= upper; i++ {
        sumS[i] += sumL[i]
        if i < upper {
            sumL[i+1] += sumS[i]
        }
    }
    induce := func(lms []int) {
        for i := 0; i < n; i++ {
            sa[i] = -1
        }
        buf := make([]int, upper+1)
        copy(buf, sumS)
        for _, d := range lms {
            if d == n {
                continue
            }
            sa[buf[s[d]]] = d
            buf[s[d]]++
        }
        copy(buf, sumL)
        sa[buf[s[n-1]]] = n - 1
        buf[s[n-1]]++
        for i := 0; i < n; i++ {
            v := sa[i]
            if v >= 1 && !ls[v-1] {
                sa[buf[s[v-1]]] = v - 1
                buf[s[v-1]]++
            }
        }
        copy(buf, sumL)
        for i := n - 1; i >= 0; i-- {
            v := sa[i]
            if v >= 1 && ls[v-1] {
                buf[s[v-1]+1]--
                sa[buf[s[v-1]+1]] = v - 1
            }
        }
    }
    lmsMap := make([]int, n+1)
    for i, _ := range lmsMap {
        lmsMap[i] = -1
    }
    m := 0
    for i := 1; i < n; i++ {
        if !ls[i-1] && ls[i] {
            lmsMap[i] = m
            m++
        }
    }
    lms := make([]int, 0, m)
    for i := 1; i < n; i++ {
        if !ls[i-1] && ls[i] {
            lms = append(lms, i)
        }
    }
    induce(lms)
    if m != 0 {
        sortedLms := make([]int, 0, m)
        for _, v := range sa {
            if lmsMap[v] != -1 {
                sortedLms = append(sortedLms, v)
            }
        }
        recS := make([]int, m)
        recUpper := 0
        recS[lmsMap[sortedLms[0]]] = 0
        for i := 1; i < m; i++ {
            l := sortedLms[i-1]
            r := sortedLms[i]
            endL, endR := n, n
            if lmsMap[l]+1 < m {
                endL = lms[lmsMap[l]+1]
            }
            if lmsMap[r]+1 < m {
                endR = lms[lmsMap[r]+1]
            }
            same := true
            if endL-l != endR-r {
                same = false
            } else {
                for l < endL {
                    if s[l] != s[r] {
                        break
                    }
                    l++
                    r++
                }
                if l == n || s[l] != s[r] {
                    same = false
                }
            }
            if !same {
                recUpper++
            }
            recS[lmsMap[sortedLms[i]]] = recUpper
        }
        recSa := saIs(recS, recUpper)
        for i := 0; i < m; i++ {
            sortedLms[i] = lms[recSa[i]]
        }
        induce(sortedLms)
    }
    return sa
}
func suffixArray(s []int, upper int) []int {
    sa := saIs(s, upper)
    return sa
}
func suffixArrayT(s []int) []int {
    n := len(s)
    idx := make([]int, n)
    for i := 0; i < n; i++ {
        idx[i] = i
    }
    sort.Slice(idx, func(l, r int) bool { return s[l] < s[r] })
    s2 := make([]int, n)
    now := 0
    for i := 0; i < n; i++ {
        if i != 0 && s[idx[i-1]] != s[idx[i]] {
            now++
        }
        s2[idx[i]] = now
    }
    return saIs(s2, now)
}
func suffixArrayS(s string) []int {
    n := len(s)
    s2 := make([]int, n)
    for i := 0; i < n; i++ {
        s2[i] = int(s[i])
    }
    return saIs(s2, 255)
}
func lcpArray(s, sa []int) []int {
    n := len(s)
    rnk := make([]int, n)
    for i := 0; i < n; i++ {
        rnk[sa[i]] = i
    }
    lcp := make([]int, n-1)
    h := 0
    for i := 0; i < n; i++ {
        if h > 0 {
            h--
        }
        if rnk[i] == 0 {
            continue
        }
        j := sa[rnk[i]-1]
        for ; j+h < n && i+h < n; h++ {
            if s[j+h] != s[i+h] {
                break
            }
        }
        lcp[rnk[i]-1] = h
    }
    return lcp
}
func lcpArrayS(s string, sa []int) []int {
    n := len(s)
    s2 := make([]int, n)
    for i := 0; i < n; i++ {
        s2[i] = int(s[i])
    }
    return lcpArray(s2, sa)
}
func zAlgorithm(s []int) []int {
    n := len(s)
    if n == 0 {
        return []int{}
    }
    z := make([]int, n)
    z[0] = 0
    for i, j := 1, 0; i < n; i++ {
        if j+z[j] <= i {
            z[i] = 0
        } else {
            z[i] = zmin(j+z[j]-i, z[i-j])
        }
        for i+z[i] < n && s[z[i]] == s[i+z[i]] {
            z[i]++
        }
        if j+z[j] < i+z[i] {
            j = i
        }
    }
    z[0] = n
    return z
}
func zmin(a, b int) int {
    if a < b {
        return a
    } else {
        return b
    }
}
func zAlgorithmS(s string) []int {
    n := len(s)
    s2 := make([]int, n)
    for i := 0; i < n; i++ {
        s2[i] = int(s[i])
    }
    return zAlgorithm(s2)
}

ACLでは入力のサイズが小さい場合にはナイーブな実装になっていますが、うまく動かなかったため今回は入力サイズによる場合分けはしていません。
Z-Algorithmは動作確認していません。
AtCoder Library Practice Contestでの提出

Segtree

Segtree.go
type E func() int
type Merger func(a, b int) int
type Compare func(v int) bool
type Segtree struct {
    n      int
    size   int
    log    int
    d      []int
    e      E
    merger Merger
}

func newSegtree(v []int, e E, m Merger) *Segtree {
    seg := new(Segtree)
    seg.n = len(v)
    seg.log = seg.ceilPow2(seg.n)
    seg.size = 1 << uint(seg.log)
    seg.d = make([]int, 2*seg.size)
    seg.e = e
    seg.merger = m
    for i, _ := range seg.d {
        seg.d[i] = seg.e()
    }
    for i := 0; i < seg.n; i++ {
        seg.d[seg.size+i] = v[i]
    }
    for i := seg.size - 1; i >= 1; i-- {
        seg.Update(i)
    }
    return seg
}
func (seg *Segtree) Update(k int) {
    seg.d[k] = seg.merger(seg.d[2*k], seg.d[2*k+1])
}
func (seg *Segtree) Set(p, x int) {
    p += seg.size
    seg.d[p] = x
    for i := 1; i <= seg.log; i++ {
        seg.Update(p >> uint(i))
    }
}
func (seg *Segtree) Get(p int) int {
    return seg.d[p+seg.size]
}
func (seg *Segtree) Prod(l, r int) int {
    sml, smr := seg.e(), seg.e()
    l += seg.size
    r += seg.size
    for l < r {
        if (l & 1) == 1 {
            sml = seg.merger(sml, seg.d[l])
            l++
        }
        if (r & 1) == 1 {
            r--
            smr = seg.merger(seg.d[r], smr)
        }
        l >>= 1
        r >>= 1
    }
    return seg.merger(sml, smr)
}
func (seg *Segtree) AllProd() int {
    return seg.d[1]
}
func (seg *Segtree) MaxRight(l int, cmp Compare) int {
    if l == seg.n {
        return seg.n
    }
    l += seg.size
    sm := seg.e()
    for {
        for l%2 == 0 {
            l >>= 1
        }
        if !cmp(seg.merger(sm, seg.d[l])) {
            for l < seg.size {
                l = 2 * l
                if cmp(seg.merger(sm, seg.d[l])) {
                    sm = seg.merger(sm, seg.d[l])
                    l++
                }
            }
            return l - seg.size
        }
        sm = seg.merger(sm, seg.d[l])
        l++
        if l&-l == l {
            break
        }
    }
    return seg.n
}
func (seg *Segtree) MinLeft(r int, cmp Compare) int {
    if r == 0 {
        return 0
    }
    r += seg.size
    sm := seg.e()
    for {
        r--
        for r > 1 && r%2 != 0 {
            r >>= 1
        }
        if !cmp(seg.merger(seg.d[r], sm)) {
            for r < seg.size {
                r = 2*r + 1
                if cmp(seg.merger(seg.d[r], sm)) {
                    sm = seg.merger(seg.d[r], sm)
                    r--
                }
            }
            return r + 1 - seg.size
        }
        sm = seg.merger(seg.d[r], sm)
        if r&-r == r {
            break
        }
    }
    return 0
}
func (seg *Segtree) ceilPow2(n int) int {
    x := 0
    for (1 << uint(x)) < n {
        x++
    }
    return x
}

int型にしか対応していません。
MinLeftは動作確認していません。
AtCoder Library Practice Contestでの提出

LazySegtree

LazySegtree.go
type S struct {
    a    int
    size int
}
type F struct {
    a int
    b int
}
type E func() S
type Merger func(a, b S) S
type Mapper func(f F, x S) S
type Comp func(f, g F) F
type Id func() F
type Compare func(v S) bool
type LazySegtree struct {
    n      int
    size   int
    log    int
    d      []S
    lz     []F
    e      E
    merger Merger
    mapper Mapper
    comp   Comp
    id     Id
}

func newLazySegtree(v []S, e E, merger Merger, mapper Mapper, comp Comp, id Id) *LazySegtree {
    lseg := new(LazySegtree)
    lseg.n = len(v)
    lseg.log = lseg.ceilPow2(lseg.n)
    lseg.size = 1 << uint(lseg.log)
    lseg.d = make([]S, 2*lseg.size)
    lseg.e = e
    lseg.lz = make([]F, lseg.size)
    lseg.merger = merger
    lseg.mapper = mapper
    lseg.comp = comp
    lseg.id = id
    for i, _ := range lseg.d {
        lseg.d[i] = lseg.e()
    }
    for i, _ := range lseg.lz {
        lseg.lz[i] = lseg.id()
    }
    for i := 0; i < lseg.n; i++ {
        lseg.d[lseg.size+i] = v[i]
    }
    for i := lseg.size - 1; i >= 1; i-- {
        lseg.Update(i)
    }
    return lseg
}
func (lseg *LazySegtree) Update(k int) {
    lseg.d[k] = lseg.merger(lseg.d[2*k], lseg.d[2*k+1])
}
func (lseg *LazySegtree) AllApply(k int, f F) {
    lseg.d[k] = lseg.mapper(f, lseg.d[k])
    if k < lseg.size {
        lseg.lz[k] = lseg.comp(f, lseg.lz[k])
    }
}
func (lseg *LazySegtree) Push(k int) {
    lseg.AllApply(2*k, lseg.lz[k])
    lseg.AllApply(2*k+1, lseg.lz[k])
    lseg.lz[k] = lseg.id()
}
func (lseg *LazySegtree) Set(p int, x S) {
    p += lseg.size
    for i := lseg.log; i <= 1; i-- {
        lseg.Push(p >> uint(i))
    }
    lseg.d[p] = x
    for i := 1; i <= lseg.log; i++ {
        lseg.Update(p >> uint(i))
    }
}
func (lseg *LazySegtree) Get(p int) S {
    p += lseg.size
    for i := lseg.log; i >= 1; i-- {
        lseg.Push(p >> uint(i))
    }
    return lseg.d[p]
}
func (lseg *LazySegtree) Prod(l, r int) S {
    if l == r {
        return lseg.e()
    }
    l += lseg.size
    r += lseg.size
    for i := lseg.log; i >= 1; i-- {
        if (l>>uint(i))<<uint(i) != l {
            lseg.Push(l >> uint(i))
        }
        if (r>>uint(i))<<uint(i) != r {
            lseg.Push(r >> uint(i))
        }
    }
    sml, smr := lseg.e(), lseg.e()
    for l < r {
        if (l & 1) == 1 {
            sml = lseg.merger(sml, lseg.d[l])
            l++
        }
        if (r & 1) == 1 {
            r--
            smr = lseg.merger(lseg.d[r], smr)
        }
        l >>= 1
        r >>= 1
    }
    return lseg.merger(sml, smr)
}
func (lseg *LazySegtree) AllProd() S {
    return lseg.d[1]
}
func (lseg *LazySegtree) Apply(p int, f F) {
    p += lseg.size
    for i := lseg.log; i >= 1; i-- {
        lseg.Push(p >> uint(i))
    }
    lseg.d[p] = lseg.mapper(f, lseg.d[p])
    for i := 1; i <= lseg.log; i++ {
        lseg.Update(p >> uint(i))
    }
}
func (lseg *LazySegtree) RangeApply(l int, r int, f F) {
    if l == r {
        return
    }
    l += lseg.size
    r += lseg.size
    for i := lseg.log; i >= 1; i-- {
        if (l>>uint(i))<<uint(i) != l {
            lseg.Push(l >> uint(i))
        }
        if (r>>uint(i))<<uint(i) != r {
            lseg.Push((r - 1) >> uint(i))
        }
    }
    l2, r2 := l, r
    for l < r {
        if l&1 == 1 {
            lseg.AllApply(l, f)
            l++
        }
        if r&1 == 1 {
            r--
            lseg.AllApply(r, f)
        }
        l >>= 1
        r >>= 1
    }
    l, r = l2, r2
    for i := 1; i <= lseg.log; i++ {
        if (l>>uint(i))<<uint(i) != l {
            lseg.Update(l >> uint(i))
        }
        if (r>>uint(i))<<uint(i) != r {
            lseg.Update((r - 1) >> uint(i))
        }
    }
}
func (lseg *LazySegtree) MaxRight(l int, cmp Compare) int {
    if l == lseg.n {
        return lseg.n
    }
    l += lseg.size
    for i := lseg.log; i >= 1; i-- {
        lseg.Push(l >> uint(i))
    }
    sm := lseg.e()
    for {
        for l%2 == 0 {
            l >>= 1
        }
        if !cmp(lseg.merger(sm, lseg.d[l])) {
            for l < lseg.size {
                lseg.Push(l)
                l = 2 * l
                if cmp(lseg.merger(sm, lseg.d[l])) {
                    sm = lseg.merger(sm, lseg.d[l])
                    l++
                }
            }
            return l - lseg.size
        }
        sm = lseg.merger(sm, lseg.d[l])
        l++
        if l&-l == l {
            break
        }
    }
    return lseg.n
}
func (lseg *LazySegtree) MinLeft(r int, cmp Compare) int {
    if r == 0 {
        return 0
    }
    r += lseg.size
    for i := lseg.log; i >= 1; i-- {
        lseg.Push(r - 1>>uint(i))
    }
    sm := lseg.e()
    for {
        r--
        for r > 1 && r%2 != 0 {
            r >>= 1
        }
        if !cmp(lseg.merger(lseg.d[r], sm)) {
            for r < lseg.size {
                lseg.Push(r)
                r = 2*r + 1
                if cmp(lseg.merger(lseg.d[r], sm)) {
                    sm = lseg.merger(lseg.d[r], sm)
                    r--
                }
            }
            return r + 1 - lseg.size
        }
        sm = lseg.merger(lseg.d[r], sm)
        if r&-r == r {
            break
        }
    }
    return 0
}
func (lseg *LazySegtree) ceilPow2(n int) int {
    x := 0
    for (1 << uint(x)) < n {
        x++
    }
    return x
}

MinLeftとMaxRightは動作確認していません。
AtCoder Library Practice Contestでの提出
AtCoder Library Practice Contestでの提出

bits

bits.go
import "math/bits"

func ceilPow2(n int) int {
    x := 0
    for (1 << uint(x)) < n {
        x++
    }
    return x
}
func bsf(n uint) int {
    return bits.TrailingZeros(n)
}

以上になります。
各関数の使い方はACLのドキュメントと照らし合わせるとだいたい分かると思いますが、後日追記するかもしれません。

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

Golang基礎 + MacにGoをインストール

はじめに

Golang又はGo言語 とは

・コンパイル言語の中でも特に処理が高速
・2009年にGoogle開発された
・シンプルな構文
・コードの可読性が高い
・初心者はpythonやphpやRubyからのが良い

勉強中なので間違っていたら教えてください。

開発環境

Visual Studio Code
Mac

Goのインストール

ターミナルで実行してください
1. homebrewを最新にします

$ brew update

2. Goのインストール

$ brew install go

3. Goがインストールされたか確認バージョンが返ってくればOK

$ go version

プログラムの基本構造

順次進行

上から順に処理していく

条件分岐

特定の条件の時にAかBなどに分岐する

繰り返し

決まった回数や条件に当てはまる間は繰り返す

Goの記述方法

例)test.go
package main Goのプログラムは1つはmainに属している必要がある
import ("fmt") ←fmtパッケージをインストールしてる

func main(){  ←関数を記述 mainからスタートする
   fmt.Println("hello") これはfmtパッケージを用いてhelloを表示する
} 波かっこ内はブロックという

Goのプログラムはなんらかのパッケージに属している必要があります

実行するにはターミナルに

$ go run ファイル名.go
上記の例だと
$ go run test.go と記述

これでhelloが表示されます。

変数の宣言仕方

例)test.go
package main 

func main(){  
  var num int (var 変数名 
   num = 1 ←1を代入
   fmt.Println(num) 
}

実行すると1が表示される

宣言の省略すると

例)test.go
package main 

func main(){  
  num := 1 ←宣言の省略書き方
    var num int (var 変数名 
      num = 1 この2行と一緒 
   fmt.Println(num) 
}

実行すると1が表示される

Goのデータ型

int 整数
float64 小数
string 文字列
bool turnかfalseが入る

配列の宣言

変数 := [要素数]データ型{データ1、データ2、...}

例)test.go
package main 

func main(){  
  a := [3]string{"sasa","sumika","takako"} ←配列の宣言
   fmt.Println(a[0])
   fmt.Println(a[1]) 
   fmt.Println(a[2])
}

実行すると
こうなります

sasa
sumika
takako

配列の要素数の省略、変更

例)test.go
package main 

func main(){  
  a := [...]string{"sasa","sumika","takako"} ←配列の宣言[...]で要素数を省略できる
  a[0] = "aki"
   fmt.Println(a[0])
   fmt.Println(a[1]) 
   fmt.Println(a[2])
}

実行するとsasaがakiに変更されています

aki
sumika
takako

条件分岐

例)test.go
package main 

func main(){  

 if a := 0 ; a >= 20{
     fmt.Println("20以上")
 } else if a == 0{
     fmt.Println("0です") 
 } else {
     fmt.Println("条件以外")
}

実行すると以下が表示されます。

0です

繰り返しfor

for 変数 := 初期値; 条件; 増減式{}

例)test.go
package main 

func main(){  

for i := 0; i<=4; i++ {
   fmt.Println(i)
}

実行すると以下が表示されます。

0
1
2
3
4

関数定義

func 関数名(引数の定義なくても良い){
retuen ←戻り値がいるなら
}

例)test.go
package main 

func say(){ 引数なし
  fmt.Println("hello")
}

func say1(say string){ ←引数あり (変数 データ型)
 fmt.Println(say)
}

func say2(x,y int) int { ←引数戻り値1つあり (変数 データ型) 戻り値のデータ型{
 return (x+y)
}

func say3(x,y int) (int, int) { ←引数戻り値2つあり (変数 データ型) 戻り値のデータ型{
 return (x+y), (x-y)
}

func main(){
 say() 引数なし
 say1("hi") 引数あり
 result := say2(5,3) 引数戻り値1つあり
 fmt.Println(result)
 result2, result3 := say3(5,2) 引数戻り値2つあり
 fmt.Println(result2, result3)
}

実行すると以下が表示されます。

hello
hi
8
7 3

構造体を定義

type 構造体名 struct {
フィールド
}

例)test.go
package main 

type Dog struct { 構造体
 name string
 age int
}

func main(){ 
 var s Dog 構造体の初期化 (var 変数名 構造体名 
 s.name = "poko"
  s.age = 5

省略バージョン上記と同じ意味
s := Dog{"poko",5}

一部部分に代入
s := Dog{name: "poko}
又は
s := Dog{age: 5}

 fmt.Println(s)
}

実行すると以下が表示されます。

{ poko 5 }

メソッド

例)test.go
package main 

type Dog struct { 構造体
 name string
 age int
}

func (s Dog) aa() { メソッドと構造体を関連付け (変数 構造体名) 変数() {
   fmt.Println(s.name,s.age)
} こうする事で構造体にアクセスできるようになる
s.nameは構造体Dogのnameにアクセスして情報を取得できる

func (s Dog) aa2(name string, age int) { 引数あり
   fmt.Println(name,age)
}

func (s Dog) aa3(age int)  int { 引数あり戻り値あり
   return age*2

func main(){ 
 a1 := Dog{"poko",5}
 a1.aa() 引数なし
 a1.aa2("domu",6) 引数あり
 result := a1.aa3(2) 引数あり戻り値あり
 fmt.Println(result)
}

実行すると以下が表示されます。

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