0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Go入門してみた

0
Posted at

3行

  • 簡易アプリを作りながらGoの勉強をした
  • gotoをみたぼく「おっほ♡」
  • goroutineおもろい

Goってなんかよさげっぽくない?

おはようございます。デブです。
最近応用過去問を解こうとして毎回計算問題の暗算が出来ない面倒なのでとばしながらやっているデブです。
本番って電卓使えましたっけ……?使えたらあんな程度の問題では済まないと思うので使えないとは思いますが。

というわけで応用の勉強をしたくないのでの合間に触ったことのない言語を頭に入れておこう、というのが発端です。

対象はGo。
最近Rustに並んで「プログラマが勉強したい言語」みたいな趣旨でよく見る言語ですね。

なお以下の内容は扱いません。

  • 環境構築
  • メリデメ、向き不向き、使い所
  • 言語仕様

成果物

内容は「4桁の数字を突っ込んだら10になる式を返してくれる謎API」です。
別に何でも良かったのですが、ボリューム軽めで並列処理を自然に扱うならこれかなと。
実際はどう考えてもスレッド化のオーバーヘッドの方が圧倒的にデカいのですが、まあ勉強なので。

ソース
hello.go

package main

import (
	"fmt"
	"math"
	"net/http"
	"strings"
	"sync"

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

const TargetAnswer = 10

const NumbersLength = 4

type Numbers [NumbersLength]float64

type Operator int

const (
	Add Operator = iota
	Subtract
	Multiply
	Divide
)

const OperatorPairLength = NumbersLength - 1

type OperatorPair [OperatorPairLength]Operator

func (o Operator) String() string {
	//fmt対応
	//toString相当
	switch o {
	case Add:
		return "+"
	case Subtract:
		return "-"
	case Multiply:
		return "*"
	case Divide:
		return "/"
	}
	return ""
}

// 配列は定数にできないノーネ
var operators = []Operator{Add, Subtract, Multiply, Divide}

type Formula struct {
	numbers      *Numbers
	operatorPair OperatorPair
	value        float64
}

func newFormula(numbers *Numbers, op OperatorPair) *Formula {
	f := Formula{
		numbers:      numbers,
		operatorPair: op,
		value:        math.NaN(),
	}
	f.calc()
	return &f
}

func (f *Formula) calc() float64 {
	stack := Numbers{0}
	stack[0] = f.numbers[0]
	index := 0

	for i, o := range f.operatorPair {
		n := f.numbers[i+1]
		switch o {
		case Add:
			index++
			stack[index] = n
		case Subtract:
			index++
			stack[index] = -n
		case Multiply:
			stack[index] = stack[index] * n
		case Divide:
			if n == 0 {
				stack[index] = math.NaN()
				break
			}
			stack[index] = stack[index] / n
		}
		if math.IsNaN(stack[index]) {
			return math.NaN()
		}
	}

	value := 0.0
	for _, v := range stack {
		value += v
	}
	f.value = value

	return value
}

func (f *Formula) String() string {
	var sb strings.Builder

	//%vで型ごとにデフォルト表記
	fmt.Fprintf(&sb, "%v ", f.numbers[0])
	for i, o := range f.operatorPair {
		fmt.Fprintf(&sb, "%v %v ", o, f.numbers[i+1])
	}
	fmt.Fprintf(&sb, " = %v", f.value)
	return sb.String()
}

func powInt(x int, y int) int {
	//powがfloat64固定だったので自前で実装
	n := 1
	for i := 0; i < y; i++ {
		n *= x
	}
	return n
}

func str2Numbers(str string) (isValid bool, numbers Numbers) {
	isValid = false
	if len([]rune(str)) != NumbersLength {
		return
	}

	for i, c := range str {
		x := int(c - '0')
		if x < 0 || 9 < x {
			return
		}
		numbers[i] = float64(x)
	}

	isValid = true
	return
}

func operatorPairIterator(yield func(OperatorPair) bool) {
	//ホントはNumbersLength依存にしたいけど再帰使わないロジックが思いつかなかったので妥協
	for _, o1 := range operators {
		for _, o2 := range operators {
			for _, o3 := range operators {
				if (!yield(OperatorPair{o1, o2, o3})) {
					return
				}
			}
		}
	}
}

func calcAllPatterns(numbers *Numbers) []*Formula {
	wg := new(sync.WaitGroup)
	length := powInt(len(operators), NumbersLength-1)
	fmt.Printf("length = %d\n", length)

	ch := make(chan *Formula, length)

	for op := range operatorPairIterator {
		wg.Add(1)
		go func(c chan *Formula) {
			defer wg.Done()
			f := newFormula(numbers, op)
			c <- f
		}(ch)
	}
	fmt.Println("waiting...")

	wg.Wait()
	close(ch)

	fmt.Println("go")

	formulas := make([]*Formula, 0, length)

	for f := range ch {
		formulas = append(formulas, f)
	}

	return formulas
}

func equalFloat64(x float64, y float64) bool {
	//動作確認上では単純比較で問題なかったけど一応
	const epsilon = 1e-9
	abs := math.Abs(x - y)

	return abs < epsilon
}

func main() {

	router := gin.Default()
	router.GET("/ping", func(c *gin.Context) {

		str := c.Query("number")
		isValid, numbers := str2Numbers(str)
		if !isValid {
			c.JSON(http.StatusBadRequest, gin.H{
				"message": "これじゃあダメですね",
			})
			return
		}

		formulas := calcAllPatterns(&numbers)
		answers := make([]string, 0, len(formulas))
		for _, f := range formulas {
			isTargetValue := equalFloat64(f.value, TargetAnswer)
			if isTargetValue {
				answers = append(answers, f.String())
			}
		}

		c.JSON(http.StatusOK, gin.H{
			"input":   str,
			"answers": answers,
		})
	})
	router.Run()
}

色々定数とってますが、再帰なしで桁数に依存しない演算子の総当たりロジックが思いつかなかったので結局4桁固定。
ダサいけど本筋ではないし、LLMに考えてもらうのもあんま勉強にならないので妥協。

感想

そんな感じであんまり詰めきれてはないですが、とりあえず最低限の基礎は触れた気がします。多分おそらくきっと。
ともあれ触ってみる前後の認識の違いは以下の通りです。

  • 型付けコンパイル
  • なんかマルチスレッドに強いらしい
  • 学習コストが低いらしい
  • 一般動詞まんまで名前つけたやつ頭おかしいんじゃねえの?GoumanのGoか?

そして触ってみた後の認識やら感想やら。

  • 思ったよりも低レイヤー
  • なのに配列が値型なのか
    • スライスがあるからいいのか天才か?
  • いうほど学習コスト低いか?(ポインタとかゴリゴリに使うしなあ)
  • gotoあるのか。興奮してきたな
  • defer!お前を待っていたんだ!全言語に配ってくれ!
  • goroutineとチャネル書きやすいんだか書きにくいんだか
  • x := v の:を毎回忘れて怒られる
  • constexprがあったら最高だったな
  • トップレベルの宣言で強制的に空行挟まれるの地味に腹立つな
  • イテレータのbool返却がなんなのか理解するのに5分くらいかかった

大雑把な感想としては「これが必要になるようなレベル高い現場入ってみたいなあ」という感じです。
内部の動きはあまり理解していない(例えばgoroutineのオーバーヘッド等)ので仔細は語れませんが、ちらっと触った程度では書きやすくて理解しやすかった部類。ポインタ最高!
何か機会があればねじ込んでみようかな。多分そんな機会ありませんが。

あとrustの方が面白かったのとC#が恋しくなってきた

余談

win10がサポート終了後、11の入らない化石PCに突っ込んだubuntu上で触ってました。
Goインストールしようとしたんですけど何かバージョンかなんかの問題でちょっと苦戦したので、面倒になってdocker上で動かしてました。
dockerの扱いが簡単なので開発はlinuxの方が好き。
メインPCにdocker desktop入れるの何故か気が進まないんですよね。なんでですかね(他人事)。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?