こんにちは、KOUKIです。
この記事では、デザインパターンの一つであるInterpreterパターンについて紹介します。
<目次>
デザインパターンまとめ
シチュエーション
Interpreter とは、解釈者や説明者を意味する単語です。Interpreterパターンは、なんらかの文法規則で記述された文字を解析し、その結果、得られた手順に基づき処理を実行していくパターンです。
例えば、「(4+3) – (1+2)」という文字列(文法規則)があったとします。
1 2 3 4 5 6 7 8 |
package main import "fmt" func main() { input := "(4+3) - (1+2)" fmt.Println(input) } |
これをいい感じに変換して、計算結果を出力できるプログラムを実装したいと思います。
サンプルコードの実装
サンプルコードを実装しつつ、Interpreterパターンを理解しましょう。
文字列の変換について
最初に「”(4+3) – (1+2)”」の文字列をどうやって変換させるかについて考えてみましょう。
この文字列は、「(」「)」「+」「-」+ 「数値」から成り立っており、プログラムに落とし込むと以下のように表せます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package main import ( "fmt" "strings" ) type CharType int const ( Int CharType = iota // 数値 Plus // + Minus // - Lparen // ( Rparen // ) ) type Char struct { charType CharType text string } func NewChar(charType CharType, text string) Char { return Char{charType: charType, text: text} } |
Convert関数の実装
Convert関数は、Interpreterパターンの「なんらかの文法規則で記述された文字を解析し」を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import ( "fmt" "strings" "unicode" ) // input -> (4+3) - (1+2) func Convert(input string) []Char { var result []Char for i := 0; i < len(input); i++ { // CharType毎に処理を振り分け switch input[i] { case '+': result = append(result, NewChar(Plus, "+")) case '-': result = append(result, NewChar(Minus, "-")) case '(': result = append(result, NewChar(Lparen, "(")) case ')': result = append(result, NewChar(Rparen, ")")) default: // 該当しない場合 -> 数値 or それ以外かを判定 sb := strings.Builder{} d := rune(input[i]) if unicode.IsDigit(d) { sb.WriteRune(d) result = append(result, NewChar(Int, sb.String())) } } } return result } func main() { input := "(4+3) - (1+2)" convertText := Convert(input) fmt.Println(convertText) } |
入力したinputをループで回し、+、-、(、)、数値のいずれかに該当した場合は、resultに格納します。
defaultの「input[i]」には、byte型が入ります。それを数値判定したいので、「unicode.IsDigit」メソッドを使用しました。このメソッドの引数はrune型なので、runeで型変換する必要があります。
プログラムを実行すると以下の結果を得られます。
1 2 |
$ go run main.go [{3 (} {0 4} {1 +} {0 3} {4 )} {2 -} {3 (} {0 1} {1 +} {0 2} {4 )}] |
対象の文字列を仕分けして、Charインスタンス化できましたね。
Parse関数の実装
Parse関数は、Interpreterパターンの「その結果、得られた手順に基づき処理を実行していく」を実現します。
最初に考えるべきことは、仕分けした文字列(Charインスタンス)をどう処理していくか、です。
以下のコードを実装しましょう。
1 2 3 4 5 6 7 |
type Element interface { Value() int } func Parse(chars []Char) Element { } |
引数には、Charインスタンスのスライスを渡しました。そして、戻り値は、Valueメソッドを持つElementインターフェースを指定しています。
これで、何らかの処理をCharインスタンスに加え、最終結果をValueメソッドから取り出せる流れを作りました。
次に考えることは、数値の計算処理です。今回は、可算、減算処理のみをターゲットとしているので、それをプログラムで表してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
type Operation int const ( Add Operation = iota // 可算 Substract // 減算 ) type RuneOperation struct { Type Operation Left, Right Element // (1 + 2) Left = 1. Right = 2 } func (r *RuneOperation) Value() int { switch r.Type { case Add: return r.Left.Value() + r.Right.Value() case Substract: return r.Left.Value() - r.Right.Value() default: panic("Error") } } |
このValueメソッド内で呼び出される「r.Left.Value()/r.Right.Value()」は、RuneOperation構造体のValueメソッドを指しません。
※少し混乱するかもしれません!
Left/Rightプロパティに格納する数値構造体を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 |
// 数値のインスタンス type Integer struct { value int } func NewInteger(value int) *Integer { return &Integer{value: value} } func (i *Integer) Value() int { return i.value } |
RuneOperationをインスタンス化する -> RuneOperationのValueメソッドを呼び出す ->IntegerのValueメソッド を呼び出す、みたいな感じになりますね。
Parse関数の実装を完成させましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
func Parse(chars []Char) Element { result := RuneOperation{} // 数値の左右ポジション判定 leftPosition := false // Convertした(4+3)-(1+2)を前から順にループ for i := 0; i < len(chars); i++ { char := &chars[i] switch char.charType { case Int: d, _ := strconv.Atoi(char.text) integer := NewInteger(d) if !leftPosition { result.Left = integer // 次回は右ポジションに格納 leftPosition = true } else { result.Right = integer } // 可算・減算の判定 case Plus: result.Type = Add case Minus: result.Type = Substract // 「(」のこと case Lparen: // (4+3), (1+2)それぞれを抜き出す j := i for ; j < len(chars); j++ { // ) になったらループを抜ける if chars[j].charType == Rparen { break } } // ループしたので、jは「)」を指している var subexp []Char for k := i + 1; k < j; k++ { subexp = append(subexp, chars[k]) } // (4+3), (1+2)それぞれを計算させる element := Parse(subexp) if !leftPosition { result.Left = element leftPosition = true } else { result.Right = element } i = j } } return &result } |
ここでやりたいことは、「(4+3) – (1+2)」式の計算です。
この計算式は、以下の順で計算されます。
- (4+3) -> 7
- (1+2) -> 3
- 7 – 3 -> 4が得られる
具体的には、「case Lparen」内の処理で、(4+3)の計算結果がLeft、(1+2)の計算結果がRightに入り、Typeには、「-」が入ります。
使ってみよう
実装したプログラムを使ってみましょう。
1 2 3 4 5 6 7 8 |
func main() { input := "(4+3) - (1+2)" convertText := Convert(input) fmt.Println(convertText) parsed := Parse(convertText) fmt.Printf("%s = %d\n", input, parsed.Value()) } |
1 2 3 |
$ go run main.go [{3 (} {0 4} {1 +} {0 3} {4 )} {2 -} {3 (} {0 1} {1 +} {0 2} {4 )}] (4+3) - (1+2) = 4 |
OKですね。
まとめ
Interpreterパターンは、結構難しく感じますね。
今回は、簡単なフォーマット「(4+3) – (1+2)」だったので、なんとか実装できましたが、もっと複雑なものになっていくとめんどくさそうです^^;
しかし、ログのパースツールなどを作る時に、活躍してくれそうな感じもするので、習熟しておきたいですね。
それでは、また!
Go言語まとめ
ソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
package main import ( "fmt" "strconv" "strings" "unicode" ) type CharType int const ( Int CharType = iota // 数値 Plus // + Minus // - Lparen // ( Rparen // ) ) type Char struct { charType CharType text string } func NewChar(charType CharType, text string) Char { return Char{charType: charType, text: text} } // input -> (4+3) - (1+2) func Convert(input string) []Char { var result []Char for i := 0; i < len(input); i++ { // CharType毎に処理を振り分け switch input[i] { case '+': result = append(result, NewChar(Plus, "+")) case '-': result = append(result, NewChar(Minus, "-")) case '(': result = append(result, NewChar(Lparen, "(")) case ')': result = append(result, NewChar(Rparen, ")")) default: // 該当しない場合 -> 数値 or それ以外かを判定 sb := strings.Builder{} d := rune(input[i]) if unicode.IsDigit(d) { sb.WriteRune(d) result = append(result, NewChar(Int, sb.String())) } } } return result } type Operation int const ( Add Operation = iota // 可算 Substract // 減算 ) type RuneOperation struct { Type Operation Left, Right Element // (1 + 2) Left = 1. Right = 2 } func (r *RuneOperation) Value() int { switch r.Type { case Add: return r.Left.Value() + r.Right.Value() case Substract: return r.Left.Value() - r.Right.Value() default: panic("Error") } } type Element interface { Value() int } // 数値のインスタンス type Integer struct { value int } func NewInteger(value int) *Integer { return &Integer{value: value} } func (i *Integer) Value() int { return i.value } func Parse(chars []Char) Element { result := RuneOperation{} // 数値の左右ポジション判定 leftPosition := false // Convertした(4+3)-(1+2)を前から順にループ for i := 0; i < len(chars); i++ { char := &chars[i] switch char.charType { case Int: d, _ := strconv.Atoi(char.text) integer := NewInteger(d) if !leftPosition { result.Left = integer // 次回は右ポジションに格納 leftPosition = true } else { result.Right = integer } // 可算・減算の判定 case Plus: result.Type = Add case Minus: result.Type = Substract // 「(」のこと case Lparen: // (4+3), (1+2)それぞれを抜き出す j := i for ; j < len(chars); j++ { // ) になったらループを抜ける if chars[j].charType == Rparen { break } } // ループしたので、jは「)」を指している var subexp []Char for k := i + 1; k < j; k++ { subexp = append(subexp, chars[k]) } // (4+3), (1+2)それぞれを計算させる element := Parse(subexp) if !leftPosition { result.Left = element leftPosition = true } else { result.Right = element } i = j } } return &result } func main() { input := "(4+3) - (3+2)" convertText := Convert(input) fmt.Println(convertText) parsed := Parse(convertText) fmt.Printf("%s = %d\n", input, parsed.Value()) } |
コメントを残す
コメントを投稿するにはログインしてください。