golangで言語処理100本ノック(第1章)

Tour of GoのBasicを終わらせたのでひとまずgolangで言語処理100本ノックの第1章を解いてみました。

せっかくなのでインフラ勉強会でコードレビューをやってコメントをもらいました。
https://wp.infra-workshop.tech/event/俺のコードを見てくれ～言語処理100本ノック第1章/

コードはこちらに置いてます。
https://github.com/sato-ken/100KnockGolang

では問題を解いていきましょう。

###00. 文字列の逆順
文字列"stressed"の文字を逆に（末尾から先頭に向かって）並べた文字列を得よ。

→文字列の長さを取って逆からデクリメントして文字取り出してけばいいんじゃね？

でこうなりました。

```00.go
package main

import (
	"fmt"
)

func main() {
	word := "stressed"

	for i := len(word) - 1; i >= 0; i-- {
		fmt.Print(string(word[i]))
	}
}
```

###01. 「パタトクカシーー」
「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ。

→文字列をfor文で取り出して、indexを計算して0で割り切れたときにPrintすればいいんじゃね？

でこうなりました。

```01.go
package main

import (
	"fmt"
)

func main() {
	word := "パタトクカシーー"
	runeWord := []rune(word)

	for i := 0; i < len(runeWord); i++ {
		if i%2 == 0 {
			fmt.Print(string(runeWord[i]))
		}
	}
}
```

解説：runeって何ぞ？

https://hake.hatenablog.com/entry/20150826/p1


###02. 「パトカー」＋「タクシー」＝「パタトクカシーー」
「パトカー」＋「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ。

→両方の長さ同じだからfor文で両方取り出せばいいんじゃね？

でこうなりました。

```02.go
package main

import (
	"fmt"
)

func main() {
	wordA := "パトカー"
	wordB := "タクシー"
	runeWordA := []rune(wordA)
	runeWordB := []rune(wordB)

	for i := 0; i < len(runeWordA); i++ {
		fmt.Print(string(runeWordA[i]), string(runeWordB[i]))
	}
}
```

###03. 円周率
"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."という文を単語に分解し，各単語の（アルファベットの）文字数を先頭から出現順に並べたリストを作成せよ。

→とりあえず邪魔な,と.を置換して取り除いてスペースで区切って単語の配列へ。
for文で取り出して単語の長さをスライスに追加してけばばいいんじゃね？

でこうなりました。

```03.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	word := "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
	word = strings.Replace(word, ",", "", -1)
	word = strings.Replace(word, ".", "", -1)

	arr := strings.Fields(word)
	list := []int{}

	for _, v := range arr {
		list = append(list, len(v))
	}
	fmt.Println(list)
}
```

###04. 元素記号
"Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."という文を単語に分解し，1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語は先頭の1文字，それ以外の単語は先頭に2文字を取り出し，取り出した文字列から単語の位置（先頭から何番目の単語か）への連想配列（辞書型もしくはマップ型）を作成せよ。

→単語の配列にするまでは03といっしょ。for文で取り出すときにindex＋1が1, 5, 6, 7, 8, 9, 15, 16, 19だったら、1文字をmapのkeyに、indexをvalueにセット、それ以外は2文字をmapに入れてけばいいんじゃね？

でこうなりました。

```04.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	word := "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
	num := []int{1, 5, 6, 7, 8, 9, 15, 16, 19}
	word = strings.Replace(word, ".", "", -1)
	arr := strings.Fields(word)
	wordMap := make(map[string]int)

	for i, v := range arr {
		for _, vv := range num {
			if i+1 == vv {
				wordMap[string(v[0])] = i + 1
			} else {
				wordMap[string(v[0:2])] = i + 1
			}
		}
	}

	fmt.Println(wordMap)

}
```


###05. n-gram
与えられたシーケンス（文字列やリストなど）からn-gramを作る関数を作成せよ．この関数を用い，"I am an NLPer"という文から単語bi-gram，文字bi-gramを得よ．

→単語の配列にするまではry、for文で取り出すときにindex+1して次の値もいっしょに取り出せばいいんじゃね？

でこうなりました。

```05.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	word := "I am an NLPer"
	arr := strings.Fields(word)

	fmt.Print("単語:bi-gram ")
	for i := 0; i < len(arr)-1; i++ {
		fmt.Print(arr[i], "-", arr[i+1], " ")
	}

	fmt.Println("")
	fmt.Print("文字:bi-gram ")
	for _, v := range arr {
		if 2 <= len(v) {
			for i := 0; i < len(v)-1; i++ {
				fmt.Print(v[i:i+2], " ")
			}
		}
	}

}
```


###06. 集合
"paraparaparadise"と"paragraph"に含まれる文字bi-gramの集合を，それぞれ, XとYとして求め，XとYの和集合，積集合，差集合を求めよ。さらに，'se'というbi-gramがXおよびYに含まれるかどうかを調べよ。

→とりあえずXとYの文字bi-gramをそれぞれ作るべ。bi-gramにしたらそれぞれで重複しているのを削除しないといけないなよね。
どうする？mapのkeyに入れて存在チェックして重複を削除したXとYのmapを関数に渡してクロスチェックさせればいいんじゃね？

でこうなりました。

```06.go
package main

import (
	"fmt"
)

func syugou(map1, map2 map[string]bool, check string) {
	wa, seki, sa := []string{}, []string{}, []string{}

	for k1, _ := range map1 {
		wa = append(wa, k1)
		_, ok := map2[k1]
		if ok == false {
			sa = append(sa, k1)
		} else {
			seki = append(seki, k1)
		}
	}
	for k2, _ := range map2 {
		_, ok := map1[k2]
		if ok == false {
			wa = append(wa, k2)
		}
	}

	fmt.Print(wa, "\n", seki, "\n", sa, "\n")
	fmt.Printf("X in 'se' is %v\n", map1[check])
	fmt.Printf("Y in 'se' is %v\n", map2[check])

}

func main() {
	word1 := "paraparaparadise"
	word2 := "paragraph"

	bigram := func(str string) []string {
		str_bigram := []string{}
		for i := 0; i < len(str)-1; i++ {
			str_bigram = append(str_bigram, str[i:i+2])
		}
		return str_bigram
	}
	word1_bi, word2_bi := bigram(word1), bigram(word2)
	fmt.Println("X:", word1_bi)
	fmt.Println("Y:", word2_bi)

	makemap := func(arr []string) map[string]bool {
		tmp_map := make(map[string]bool)
		for _, v := range arr {
			_, ok := tmp_map[v]
			if ok == false {
				tmp_map[v] = true
			}
		}
		return tmp_map
	}

	syugou(makemap(word1_bi), makemap(word2_bi), "se")

}
```


###07. テンプレートによる文生成
引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ。さらに，x=12, y="気温", z=22.4として，実行結果を確認せよ。

→fmt.Printfで返せばいいやw

でこうなりました。

```07.go
package main

import (
	"fmt"
)

func template(x int, y string, z float64) {
	fmt.Printf("%v時の%vは%v", x, y, z)
}

func main() {
	template(12, "気温", 22.4)
}

```

###08. 暗号文
与えられた文字列の各文字を，以下の仕様で変換する関数cipherを実装せよ。
英小文字ならば(219 - 文字コード)の文字に置換
その他の文字はそのまま出力
この関数を用い，英語のメッセージを暗号化・復号化せよ。

→小文字のa~zのコード範囲だったら(219 - 文字コード)して返せばいいんじゃね？

でこうなりました。

```08.go
package main

import (
	"fmt"
)

func cipher(str string) []rune {
	result := []rune{}
	for _, y := range str {
		if y >= 96 && y < 123 {
			result = append(result, 219-y)
		}
	}
	return result
}

func main() {
	a := "abc"
	b := cipher(a)
	fmt.Println(string(b[:]))
}
```

###09. Typoglycemia

スペースで区切られた単語列に対して，各単語の先頭と末尾の文字は残し，それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ．ただし，長さが４以下の単語は並び替えないこととする．適当な英語の文（例えば"I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."）を与え，その実行結果を確認せよ．

→単語の配列にするまではry、for文で取り出して4文字以上だったら、先頭と末尾の文字を控えておいてから真ん中の文字で全部の組み合わせパターンを出す。全パターンのうちからランダムで選んだのを控えておいた先頭と末尾の文字の間にくっつければいいんじゃね？４文字いかはそのまま出すだけ。

でこうなりました。

```09.go
package main

import (
	"fmt"
	"math/rand"
	"strings"
	"time"
)

func join(ins []rune, c rune) (result []string) {
	for i := 0; i <= len(ins); i++ {
		result = append(result, string(ins[:i])+string(c)+string(ins[i:]))
	}
	return
}

func permutations(testStr string) []string {
	var n func(testStr []rune, p []string) []string
	n = func(testStr []rune, p []string) []string {
		if len(testStr) == 0 {
			return p
		} else {
			result := []string{}
			for _, e := range p {
				result = append(result, join([]rune(e), testStr[0])...)
			}
			return n(testStr[1:], result)
		}
	}

	output := []rune(testStr)
	return n(output[1:], []string{string(output[0])})
}

func main() {
	str := "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
	strArr := strings.Fields(str)
	for _, word := range strArr {
		if 4 < len(word) {
			strFirst := string(word[0])
			strLast := string(word[len(word)-1])
			d := permutations(string(word[1 : len(word)-1]))

			rand.Seed(time.Now().UnixNano())
			strShufle := d[rand.Intn(len(word))]
			fmt.Print(strFirst + strShufle + strLast + " ")
		} else {
			fmt.Print(word + " ")
		}
	}
}
```

ってな感じで第１章を解いてみました。

聞いてもらった人のコメントでは

- ループのiは分かりにくいので"変数index"みたいな一目で分かりそうな変数にしたほうが良い
- できればGoらしくメソッドで細かくわけたほうが美しい
- 変数名がスネークケースなのは何か意味があるのか

→For文はついiで書いちゃうなーw
 Goらしいキレイなコードは勉強中ですね...
 変数の付け方は統一すべきですね、Goだと規則あるんだっけ？調べてみましょう。

というわけで引き続き２章も解いていきたいと思いました。