##はじめに
研究室で言語処理100本ノック2015 を解くことになり、去年はPythonで解いたので違う言語でやることになりました。流行ってるし、「Goやってます」と言いたいがために、初めてのGo言語でやります。
相談相手がいない(みんなJavaとかC++で解いてる)ので、ここで厳しいフィードバックをいただけると幸いです。以下GitHubのページにでき次第あげていきます。
https://github.com/yukitomo/NLP100knock2015_Go
とりあえず、10本です
##第1章: 準備運動
###00. 文字列の逆順
文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.
package main
import "fmt"
func Reverse(s string) string {
runes := []rune(s) //rune配列に変換
for i, j := 0, len(runes)-1; i<j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
func main() {
s := "stressed"
fmt.Printf("%v\n",s)
fmt.Println(Reverse(s))
}
Recverse(s)
:文字列を引数としてrune配列として、順番を逆順として文字列を出力する。
自分の調べた限りだとGoで文字列をインデックス操作しようとするにはrune配列にするのが一般的?ぽいです。
pythonなら s[::-1]
で一発だよなと思いつつ結構長いコードになってしまいました...
####参考URL
http://d.hatena.ne.jp/yuheiomori0718/20131008/1381244919
###01. 「パタトクカシーー」
「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ.
package main
import "fmt"
func main() {
s := "パタトクカシーー"
fmt.Printf("%v\n", s)
runes := []rune(s)
fmt.Println(string(runes[1]) + string(runes[3]) + string(runes[5]) + string(runes[7]))
}
####参考URL
http://qiita.com/ono_matope/items/d5e70d8a9ff2b54d5c37
###02. 「パトカー」+「タクシー」=「パタトクカシーー」
「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ
package main
import "fmt"
func Altjoin(s1, s2 string) string {
runes1 := []rune(s1) //rune配列に変換
runes2 := []rune(s2)
var s3 string
for i := 0; i < len(runes1); i = i+1 {
s3 += string(runes1[i]) + string(runes2[i])
}
return s3
}
func main() {
s1 := "パトカー"
s2 := "タクシー"
fmt.Printf("%v\n",s1)
fmt.Printf("%v\n",s2)
fmt.Println(Altjoin(s1,s2))
}
Altjoin(s1, s2)
:2つの文字列を引数として取り交互に連結して出力
###03. 円周率
"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.
package main
import (
"fmt" // 入出力フォーマットを実装したパッケージ
"strings" //string処理のメソッド
)
func main() {
s := "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
s = strings.Trim(s, ".") // "."の除去、Trimは文字列の前後しか除去できない
s = strings.Replace(s, ",", "", -1) //","の除去
words := strings.Split(s, " ") //slicesに単語が格納
for i := 0; i < len(words); i++ {
fmt.Printf("%d\n", len(words[i])) //各単語の文字数を出力
}
}
func Replace(s, old, new string, n int) string
:文字列sのコピーに対し、最初のn個、oldの部分をnewに置換(重複はなし)したものを返します。n < 0のとき、置換数は無制限になります。
####参考URL
https://github.com/astaxie/build-web-application-with-golang/blob/master/ja/07.6.md
###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文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.
package main
import (
"fmt" // 入出力フォーマットを実装したパッケージ
"strings" //string処理のメソッド
)
func main() {
s := "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
s = strings.Trim(s, ".") // "."の除去
s = strings.Replace(s, ",", "", -1)
words := strings.Split(s, " ") //slicesに単語が格納
words_index := make(map[string]int)
for i := 0; i < len(words); i++ {
runes := []rune(words[i])
switch i {
case 1, 5, 6, 7, 8, 9, 15, 16, 19: //1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語は先頭の1文字
words_index[string(runes[0])] = i
default: //上記以外の単語は先頭の2文字
words_index[string(runes[0])+string(runes[1])] = i
}
}
fmt.Println(words_index)
}
####参考URL
http://qiita.com/high5/items/3fe34d2feeff2c11f5ca
###05. n-gram
与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ.
この関数を用い,"I am an NLPer"という文から単語bi-gram,文字bi-gramを得よ.
package main
import (
"fmt" // 入出力フォーマットを実装したパッケージ
"strings" //string処理のメソッド
)
// n がスライスに含まれているか
func member(n string, xs []string) bool {
for _, x := range xs {
if n == x {
return true
}
}
return false
}
// 重複要素を取り除く
func RemoveDuplicates(xs []string) []string {
ys := make([]string, 0, len(xs))
for _, x := range xs {
if !member(x, ys) {
ys = append(ys, x)
}
}
return ys
}
func WordsSplit(s string) []string {
s = strings.Trim(s, ".") //"."の除去
s = strings.Replace(s, ",", "", -1) //","の除去
words := strings.Split(s, " ") //[]に単語が格納
return words
}
//与えられたslice(単語 or 文字)からngramを返す、cは対象のobjectsの連結文字列
func MakeNgram(objs []string, n int, c string) []string {
Ngrams := []string{}
for i := 0; i < (len(objs) - n + 1); i++ {
Ngrams = append(Ngrams, strings.Join(objs[i:i+n], c))
}
return RemoveDuplicates(Ngrams)
}
//与えられた単語のsliceから文字ngramを返す
func MakeCharactersNgram(words []string, n int) []string {
ChNgrams := []string{}
for i := 0; i < len(words); i++ {
characters := strings.Split(words[i], "") //単語の文字のスライスを作成
ChNgrams = append(ChNgrams, MakeNgram(characters, n, "")...) //'...'をつけることでsliceの連結ができる
}
return RemoveDuplicates(ChNgrams)
}
func PrintSlice(objs []string) {
for i := 0; i < len(objs); i++ {
fmt.Printf("%v\n", objs[i])
}
}
func main() {
s := "I am an NLPer"
fmt.Printf("%v\n", s)
words := WordsSplit(s) //単語に分割し、スライスに格納
//単語bigram
fmt.Printf("---words bigram---\n")
Wordsbigram := MakeNgram(words, 2, " ")
PrintSlice(Wordsbigram)
//文字bigram
fmt.Printf("---characters bigram---\n")
Charactersbigram := MakeCharactersNgram(words, 2)
PrintSlice(Charactersbigram)
}
WordsSplit(s)
において、句読点の除去を行ってしまったのですが題意的にはそのままでも良かったんでしょうか。
####参考URL
http://qiita.com/ono_matope/items/d5e70d8a9ff2b54d5c37
http://qiita.com/kazuph/items/4bc33162da7e7d00d80c
###06. 集合
"paraparaparadise"と"paragraph"に含まれる文字bi-gramの集合を,それぞれ,XとYとして求め,XとYの和集合,積集合,差集合を求めよ.さらに,'se'というbi-gramがXおよびYに含まれるかどうかを調べよ.
package main
import (
"fmt" // 入出力フォーマットを実装したパッケージ
"strings" //string処理のメソッド
)
// n がスライスに含まれているか
func member(n string, xs []string) bool {
for _, x := range xs {
if n == x {
return true
}
}
return false
}
// 重複要素を取り除く
func RemoveDuplicates(xs []string) []string {
ys := make([]string, 0, len(xs))
for _, x := range xs {
if !member(x, ys) {
ys = append(ys, x)
}
}
return ys
}
//与えられたslice(単語 or 文字)からngramを返す
func MakeNgram(objs []string, n int, c string) []string {
Ngrams := []string{}
for i := 0; i < (len(objs) - n + 1); i++ {
//fmt.Println((objs[i : i+n]))
Ngrams = append(Ngrams, strings.Join(objs[i:i+n], c))
}
return RemoveDuplicates(Ngrams)
}
//和集合の導出
func MakeUnion(set1, set2 []string) []string {
set := append(set1, set2...) //sliceの結合
return RemoveDuplicates(set) //重複要素の削除
}
//積集合の導出
func MakeIntersection(set1, set2 []string) []string {
set := []string{}
//set1の各要素がset2に含まれていた場合setに格納
for _, c := range set1 {
if member(c, set2) {
set = append(set, c)
}
}
return set
}
//差集合の導出 (set1 - set2)
func MakeDifference(set1, set2 []string) []string {
set := []string{}
//set1の各要素がset2に含まれていない場合setに格納
for _, c := range set1 {
if !member(c, set2) {
set = append(set, c)
}
}
return set
}
func main() {
sent1 := "paraparaparadise"
sent2 := "paragraph"
fmt.Printf("%v\n", sent1)
fmt.Printf("%v\n", sent2)
X := MakeNgram(strings.Split(sent1, ""), 2, "") //文字bigramの作成
Y := MakeNgram(strings.Split(sent2, ""), 2, "")
fmt.Printf("X : %v\n", X)
fmt.Printf("Y : %v\n", Y)
Union := MakeUnion(X, Y) //和集合
fmt.Printf("Union(X,Y) : %v\n", Union)
Intersection := MakeIntersection(X, Y) //積集合
fmt.Printf("Intersection(X,Y) : %v\n", Intersection)
Difference := MakeDifference(X, Y) //差集合
fmt.Printf("Difference(X,Y) : %v\n", Difference)
//'se'がXに含まれるか
if member("se", X) {
fmt.Printf("'se' is member of X\n")
} else {
fmt.Printf("'se' isn't member of X\n")
}
//'se'がYに含まれるか
if member("se", Y) {
fmt.Printf("'se' is member of Y\n")
} else {
fmt.Printf("'se' isn't member of Y\n")
}
}
####参考URL
http://www.geocities.jp/m_hiroi/golang/yagp02.html
http://golang.jp/effective_go
###07. テンプレートによる文生成
引数 x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ.
さらに,x=12, y="気温", z=22.4として,実行結果を確認せよ.
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func scan(sent string) string {
fmt.Printf("%v : ", sent)
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
return input
}
func main() {
//各x,y,zをターミナルから入力
x := strings.Trim(scan("x"), "\n")
y := strings.Trim(scan("y"), "\n")
z := strings.Trim(scan("z"), "\n")
fmt.Printf("「%v時の%vは%v」\n", x, y, z)
}
scan(string)
というようなstringをプリントし、ターミナルから入力されたものを返す関数を定義しました。Goの入出力関連いまいちまだよく分かってません...
tetsuokさんからコメントをいただいた上で以下のように書きかえました。コマンドラインから引数を取得する際には flag
パッケージを利用するのが良いみたいです。
//実行 go run src/test07_2.go x y z
package main
import (
"flag"
"fmt"
"os"
)
func format(a []string) string {
if len(a) < 3 {
return ""
}
return fmt.Sprintf("%s時の%sは%s", a[0], a[1], a[2])
}
func main() {
//引数に関してのUsage
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s x y z\n", os.Args[0])
flag.PrintDefaults()
}
//引数をパース
flag.Parse()
//引数の数を調べ、3つでない場合Usageを表示し、終了コードで終了
if flag.NArg() != 3 {
//Usageの表示
flag.Usage()
//終了コード1で終了
os.Exit(1)
}
//flag.Args():標準入力の引数がstringの配列で渡される
fmt.Println(format(flag.Args()))
}
####参考URL
http://d.hatena.ne.jp/umisama/20100325/1269508269
https://play.golang.org/p/5OEVRC1Ncq
###08. 暗号文
与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ
英小文字ならば(219 - 文字コード)の文字に置換
その他の文字はそのまま出力
この関数を用い,英語のメッセージを暗号化・復号化せよ.
package main
import (
"bufio"
"fmt"
"os"
"regexp"
"strings"
)
func scan(sent string) string {
fmt.Printf("%v : ", sent)
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
return input
}
//英小文字かどうか正規表現で判定
func IsLowAlphabet(character string) bool {
if m, _ := regexp.MatchString("[a-z]", character); !m {
return false
}
return true
}
//英小文字ならば(219 - 文字コード)の文字に置換、その他の文字はそのまま出力
func Cipher(s string) string {
runes := []rune(s)
for i := 0; i < len(runes); i++ {
//正規表現で判断(runeをいちいちstringに戻しているので冗長)
/*if IsLowAlphabet(string(runes[i])) {
runes[i] = 219 - runes[i]
}*/
//英小文字 [a-z] はutf-8の10進数で 97 ~ 123 で表現されるのでそれを用いて判断
if runes[i] > 96 && runes[i] < 123 {
runes[i] = 219 - runes[i]
}
}
return string(runes)
}
func main() {
sent := strings.Trim(scan("input sentence"), "\n")
cipherSent := Cipher(sent)
fmt.Printf("cipherted sentence : %v\n", cipherSent)
decodeSent := Cipher(cipherSent)
fmt.Printf("decoded sentence : %v\n", decodeSent)
}
最初はコメントアウトしている部分で IsLowAlphabet()
という関数中で英小文字かどうかを正規表現を使って判断していたのですが、このエントリを書いている途中で文字コードで判断 if runes[i] > 96 && runes[i] < 123
した方がシンプルだと気づいたので書き換えました。
####参考URL
http://astaxie.gitbooks.io/build-web-application-with-golang/content/ja/07.3.html
http://www.utf8-chartable.de/unicode-utf8-table.pl?number=128&utf8=dec
###09. Typoglycemia
スペースで区切られた単語列に対して,各単語の先頭と末尾の文字は残し,それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ.ただし,長さが4以下の単語は並び替えないこととする.
適当な英語の文(例えば"I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind .")を与え,その実行結果を確認せよ.
package main
import (
"fmt"
"math/rand"
"strings"
)
//単語の分割
func WordsSplit(s string) []string {
words := strings.Split(s, " ")
return words
}
//与えられた単語の長さが4より大きい場合、先頭と末尾の文字は残し,
//それ以外の文字の順序をランダムに並び替える
func RandomizeWord(word string) string {
var new_word string
runes := []rune(word)
length := len(runes)
//文字列長が4以上の場合
/*
string = "ABCDE"
length = 5
index = [0,1,2,3,4]
rand_idx := rand.Perm(length - 2) = [1,0,2] → 1を足せば [2,1,3] が取れる
*/
if length > 4 {
new_runes := make([]rune, length)
rand_idx := rand.Perm(length - 2) // 0 ~ (length-2-1) をランダムに並べたもの
new_runes[0] = runes[0] //最初と最後は同じ
new_runes[length-1] = runes[length-1]
for i := range rand_idx {
//rand_idxの各要素に1を足せば最初と最後以外のインデックスがとれる
new_runes[i+1] = runes[rand_idx[i]+1]
}
new_word = string(new_runes)
} else {
new_word = word
}
return new_word
}
func RandomizeSent(sent string) string {
words := WordsSplit(sent)
for i := 0; i < len(words); i++ {
words[i] = RandomizeWord(words[i])
}
return strings.Join(words, " ") //最後に一つのstringに結合
}
func main() {
//入力文
sent := "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
random_sent := RandomizeSent(sent)
fmt.Printf("src sent : %v\n", sent) //元の文
fmt.Printf("rand sent : %v\n", random_sent) //ランダム化された文
}
単語長が4以上の単語については、rand_idx := rand.Perm(length - 2)
で 0 ~ (length-2-1) を並び替えたものを取得し、各要素に1を足すことで最初と最後のインデックス以外がランダムなインデックスが取れる。
参考URL
https://groups.google.com/forum/#!topic/golang-nuts/JDVCR__dHbI
http://golang.org/pkg/math/rand/