質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.91%

Golang で switch 文による型ごとの処理

解決済

回答 1

投稿

  • 評価
  • クリップ 2
  • VIEW 562

Yhaya

score 365

質問したいこと

interface を引数に持つ関数の内部で、switch 文を使って引数の型ごとに処理を分けるときにどのようにしたら簡潔にコードをかけるかわからない

環境

  • Ubuntu 18.04
  • go 1.14.2

問題の詳細

挿入ソートのコードを go を使って書いています。

package insertsort

func insertSort(x interface{}, size int) {
    switch value := x.(type) {
    case *[]int:
        for i := 0; i < size; i++ {
            tmp := (*value)[i]
            j := i - 1

            for j >= 0 {
                if tmp < (*value)[j] {
                    (*value)[j+1] = (*value)[j]
                    (*value)[j] = tmp
                    j--
                } else {
                    break
                }
            }
        }
    case *[]string:
        for i := 0; i < size; i++ {
            tmp := (*value)[i]
            j := i - 1

            for j >= 0 {
                if tmp < (*value)[j] {
                    (*value)[j+1] = (*value)[j]
                    (*value)[j] = tmp
                    j--
                } else {
                    break
                }
            }
        }
    default:

    }
}

引数には整数の配列や文字列の配列などを許容したくて interface{} にしています。中の処理では、型ごとに switch を使って分岐しているのですが、 *[]int のブランチと *[]string のブランチで中身は全く同じになってしまっています。

これを解決するために分岐の部分を

case *[]int, *[]string:

のように書いてみたのですが、invalid indirect of value (type interface {}) というエラーが出てしまいます。

この書き方だと上のブランチのほかに *[]float64 などどんどん分岐を増やしていくと重複だらけのコードになってしまうので困っているのですが、どのように書くのが正しいのでしょうか?

よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

Goでのオススメの考え方は「必要なものだけを実装する」です。

標準のソート以外が必要になる型は実際には限られるはずです。
必要だとわかってるものだけを実装するのがGoの流儀かなと思います。

実際にやってみたことがあるけれど結局実装を複雑に膨らませただけでいいことは何もありませんでした。
使われるかわからない実装をして、そのためのテストを組むのはもったいないですしね。

それでも複数の型に対応する実装を展開する理由ができた場合、僕の場合はgo generateを活用します。

追記

現時点での僕のオススメの実装例を以下に示します。

以下のツールをインストールします。

go get github.com/cheekybits/genny

以下の2つのコードをメインパッケージにおきます
(必要ならパッケージを分けても構いません)

package main

import "github.com/cheekybits/genny/generic"

//go:generate genny -in=$GOFILE -out=gen-$GOFILE gen "Type=string,int"

// Type ...
type Type generic.Number

// SortType ...
func SortType(s []Type) {
    size := len(s)
    for i := 0; i < size; i++ {
        tmp := s[i]
        j := i - 1

        for j >= 0 {
            if tmp < s[j] {
                s[j+1] = s[j]
                s[j] = tmp
                j--
            } else {
                break
            }
        }
    }
}
package main

import "fmt"

// Sort ...
func Sort(s interface{}) {
    switch v := s.(type) {
    case []int:
        SortInt(v)
    case []string:
        SortString(v)
    }
}

func main() {
    is := []int{3, 2, 5}
    Sort(is)
    fmt.Println(is)
    ss := []string{"oge", "moge", "hoge"}
    Sort(ss)
    fmt.Println(ss)
}

以下のようにgo-generateを行えば、
SortInt、SortStringという関数定義が自動生成されます。

go generate .

以下のコマンドで実行したりできます

go run .

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/23 20:20

    もちろん来年以降に使えるようになるかもしれないジェネリクス 機能を待ってもいいわけですが。

    キャンセル

  • 2020/07/23 23:36

    nobonoboさん、回答していただきありがとうございます。就活のコード面接対策でアルゴリズムを一通りさらい直していたところ、ふと複数の型への拡張は Go だとどうするのだろうと思って質問してみましたが、言われてみれば nobonobo さんの言うとおりですね。 go generate は初めて知りました。ありがとうございます。

    キャンセル

  • 2020/07/24 09:10

    go-generateの例を追記しました。

    質問に書かれたコードでひとつ指摘するとすれば、
    Goのスライスの値の実体は参照型なので僕の書いた実装例のように「スライス」を渡すだけでソートを実装することができます。
    (スライスを渡した引数の内容を変更したとき、渡す前のスライスにも変更が反映されます。)
    わざわざ「スライスへのポインタ」という概念を利用する必要はありません。
    (ただし、要素数に関わる情報は値渡しなので、要素が増えたり減ったりする場合は「スライスへのポインタ」という概念が必要になる場合もあります)

    キャンセル

  • 2020/07/24 14:36

    ありがとうございます。go-generate 試してみました。スライスの関数への渡し方までご指摘いただきありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.91%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る