😎

paiza の基礎問題を Go で解いて気づいた Go らしさ

2021/05/22に公開2

たまたま見かけた以下の問題を解きました。
https://paiza.jp/works/mondai/skillcheck_archive/word-count?language_uid=go

問題としてはかなり簡単なものですが、 「Go らしさ」が伝わって面白かったので書きます。
Ruby との比較もあるよ💎

問題

スペースで区切られた英単語列が与えられます。
英単語列に含まれる英単語の出現回数を出現した順番に出力してください。
red green blue blue green blue
 
=> 結果
red 1
green 2
blue 3
Apple Apricot Orange Cherry Apple Orange Cherry Orange

=> 結果
Apple 2
Apricot 1
Orange 3
Cherry 2

解答

package main

import (
	"fmt"
	"strings"
)

func main() {
	sample1 := "red green blue blue green blue"
	sample2 := "Apple Apricot Orange Cherry Apple Orange Cherry Orange"

	execute(sample1)
	execute(sample2)
}

func execute(s string) {
	fmt.Println("==================================")

	strs := strings.Split(s, " ")
	uniqStrs := uniq(strs)

	result := make(map[string]int)

	for _, v := range strs {
		count := strings.Count(s, v)
		result[v] = count
	}
	display(uniqStrs, result)
}

func display(uniqSlice []string, result map[string]int) {
	for _, key := range uniqSlice {
		fmt.Printf("%s %d\n", key, result[key])
	}
}

func uniq(slice []string) []string {
	m := make(map[string]bool)
	uniqSlice := make([]string, 0)

	for _, v := range slice {
		if _, ok := m[v]; !ok {
			m[v] = true
			uniqSlice = append(uniqSlice, v)
		}
	}
	return uniqSlice
}

解説

シンプルな問題ですが、コードの量が多いのが印象的です🧐

スライス要素の重複を削除したいだけですが、 uniq のような関数が必要になります。
この書き方は Go の中では一般的なようで、適当な map と最終的な結果を入れるスライスを作成して range ループを回すということらしいです。

Go はやはり痒いところに手が届くような関数は用意されておらず、ある程度自分で実装する必要があります。
そういう時には、イディオム的な書き方を知っておくと便利だと思います。

参考までにこの問題を ruby で解くと以下のようになりました。

def main(str)
  p "========================================================"
  arr = str.split(" ")
  uniq = arr.uniq

  result = uniq.each_with_object({}) do |s, h|
    h[s] = str.scan(s).length
  end

  result.each do |k, v|
    p "#{k} #{v}"
  end
end

sample1 = "red green blue blue green blue"
sample2 = "Apple Apricot Orange Cherry Apple Orange Cherry Orange"

main(sample1)
main(sample2)

単純な比較はできませんが、コード量でいくと ruby の方が随分少ないことが分かります。
配列の重複排除は uniq で1発ですし、 each_with_object も便利です。


手を動かして、目指せ Gopher 👨‍💻

Discussion

bluebladeblueblade

Go のマップ や Ruby のハッシュを使えば、自然に重複排除ができるので、わざわざ uniq する必要はありません。

Ruby 版は以下のようになります。

def count_words(str)
  print("====================\n")

  word_counts = Hash.new(0)

  str.split(" ").each do |word|
    word_counts[word] += 1
  end

  word_counts.each do |word, count|
    printf("%10s %s\n", word, count)
  end
end

sample1 = "red green blue blue green blue"
sample2 = "Apple Apricot Orange Cherry Apple Orange Cherry Orange"

count_words(sample1)
count_words(sample2)

Go 版は以下の通りです。
Go の map はキーの順序を保持しないので、uniq_words が順序保持のために必要になりますが、
それ以外は Ruby と比べても、多少長い程度で、十分シンプルだと思います。

package main
    
import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    sample1 := "red green blue blue green blue"
    sample2 := "Apple Apricot Orange Cherry Apple Orange Cherry Orange"

    count_words(sample1)
    count_words(sample2)
}

func count_words(str string) {
    fmt.Println("==================================")

    words := strings.Split(str, " ")
    uniq_words := []string{}
    word_counts := map[string]int{}

    for _, word := range words {
        if _, ok := word_counts[word]; !ok {
            uniq_words = append(uniq_words, word)
        }
        word_counts[word]++
    }

    for _, word := range uniq_words {
        fmt.Printf("%10s %d\n", word, word_counts[word])
    }
}
tatsuromtatsurom

コメントありがとうございます!
手元で試せていませんが、そういう書き方もあるのですね。

勉強になりました、ありがとうございます😊