見出し画像

Swiftでプログラミング。 - Enumerations 2

Associated Values 関連する値

前のセクションの例は、列挙型のcaseがそれ自体で定義された(および型指定された)値である方法を示しています。定数または変数をPlanet.earthに設定し、後でこの値を確認できます。ただし、これらのcase値と一緒に他の型の値を保存できると便利な場合があります。この追加情報は関連値Associated Valueと呼ばれ、コードでそのcaseを値として使用するたびに異なります。

Swift列挙型を定義して、任意のタイプの関連する値を格納できます。また、必要に応じて、列挙型のケースごとに値タイプを変えることができます。これらに類似した列挙は、他のプログラミング言語では、識別された共用体、タグ付き共用体、またはバリアントとして知られています。

たとえば、在庫追跡システムが2つの異なるタイプのバーコードで製品を追跡する必要があるとします。一部の製品には、0〜9の数字を使用するUPC形式の1Dバーコードでラベルが付けられています。各バーコードには、数字のシステム数字があり、その後に5つのメーカーコード数字と5つの製品コード数字が続きます。これらの後にチェックディジットが続き、コードが正しくスキャンされたことを確認します。

その他の製品は、QRコード形式の2Dバーコードでラベル付けされています。これは、ISO 8859-1文字を使用でき、最大2,953文字の文字列をエンコードできます。

在庫追跡システムでは、UPCバーコードを4つの整数のタプルとして保存し、QRコードバーコードを任意の長さの文字列として保存すると便利です。

Swiftでは、いずれかのタイプの製品バーコードを定義するための列挙は次のようになります。

    enum Barcode {
       case upc(Int, Int, Int, Int)
       case qrCode(String)
   }

これは次のように読むことができます:

「バーコードと呼ばれる列挙型を定義します。これは、upcの値とそれに関連付けられた型(Int、Int、Int、Int)の値、またはqrCodeの値と関連付けられた型の値のいずれかを取ることができます。」

この定義は、実際のInt値またはString値を提供するものではなく、Barcode.upcまたはBarcode.qrCodeと等しい場合にバーコード定数および変数が格納できる関連値のタイプを定義するだけです。

次に、次のいずれかのタイプを使用して新しいバーコードを作成できます。

var productBarcode = Barcode.upc(8, 85909, 51226, 3)

この例では、productBarcodeという新しい変数を作成し、それに関連付けられたタプル値(8、85909、51226、3)を持つBarcode.upcの値を割り当てます。

同じ製品に異なるタイプのバーコードを割り当てることができます。

productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

この時点で、元のBarcode.upcとその整数値は、新しいBarcode.qrCodeとその文字列値に置き換えられます。 バーコード型の定数と変数は、.upcまたは.qrCodeのいずれかを(関連する値とともに)格納できますが、一度に格納できるのはそのうちの1つだけです。

列挙値とSwitchステートメントの照合の例と同様に、switchステートメントを使用してさまざまなバーコードタイプを確認できます。 ただし、今回は、関連する値がswitchステートメントの一部として抽出されます。 スイッチケースの本体内で使用するために、関連付けられた各値を定数(letプレフィックス付き)または変数(varプレフィックス付き)として抽出します。

    switch productBarcode {
   case .upc(let numberSystem, let manufacturer, let product, let check):
       print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
   case .qrCode(let productCode):
       print("QR code: \(productCode).")
   }
   // Prints "QR code: ABCDEFGHIJKLMNOP."

列挙型のケースに関連付けられているすべての値が定数として抽出される場合、またはすべてが変数として抽出される場合は、簡潔にするために、case名の前に単一の変数または注釈を付けることができます。

    switch productBarcode {
   case let .upc(numberSystem, manufacturer, product, check):
       print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
   case let .qrCode(productCode):
       print("QR code: \(productCode).")
   }
   // Prints "QR code: ABCDEFGHIJKLMNOP."

Raw Values

関連付けられた値」のバーコードの例では、列挙型のケースが、異なる型の関連付けられた値を格納することを宣言する方法を示しています。関連付けられた値の代わりに、列挙型のケースには、すべて同じ型のデフォルト値(raw valuesと呼ばれる)があらかじめ入力されています。

以下は、生のASCII値を名前付きの列挙caseと一緒に格納する例です。

    enum ASCIIControlCharacter: Character {
       case tab = "\t"
       case lineFeed = "\n"
       case carriageReturn = "\r"
   }

ここでは、ASCIIControlCharacterという列挙のraw valueはCharacter型と定義されており、一般的なASCII制御文字のいくつかが設定されています。文字値については、「文字列と文字」で説明しています。

raw valueは、文字列、キャラクタ、または整数型や浮動小数点型のいずれかです。各raw valueは、その列挙宣言内で一意でなければなりません。

raw valueは、関連付けられた値とは異なります。生の値は、上記の3つのASCIIコードのように、コードで最初に列挙を定義したときに、あらかじめ入力された値に設定されます。特定の列挙ケースの生の値は常に同じです。関連付けられた値は、列挙のケースの1つに基づいて新しい定数または変数を作成したときに設定され、そのたびに異なることがあります。

Implicitly Assigned Raw Values 暗黙のうちに割り当てられた生の値

整数または文字列の生の値を格納する列挙で作業しているときは、各caseに Raw Valueを明示的に割り当てる必要はありません。そうしない場合、Swift は自動的に値を割り当てます。

たとえば、 Raw Valueに整数が使用されている場合、各caseの暗黙の値は、前のcaseよりも1つ多くなります。最初のケースに値が設定されていない場合、その値は 0 です。

以下の列挙は、以前のPlanet列挙を改良したもので、各惑星の太陽からの順番を表すために整数の Raw Valueを使用しています。

    enum Planet: Int {
       case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
   }

上の例では、Planet.mercuryは1の明示的な Raw Valueを持ち、Planet.venusは2の暗黙的な Raw Valueを持ちます。以下の値も同じようにRaw Valueとして数字を持ちます。

文字列がRaw Valueに使われている場合、各caseの暗黙の値は、そのケースの名前のテキストです。

以下の列挙は、前出の CompassPoint 列挙を改良したもので、各方位の名前を表す文字列のRaw Valueを持っています。

    enum CompassPoint: String {
       case north, south, east, west
   }

上記の例では、CompassPoint.southには暗黙のRaw Valueとして「south」が設定されており、以下のようになります。

列挙ケースの生の値にアクセスするには、その rawValue プロパティを使用します。

let earthsOrder = Planet.earth.rawValue

   // earthsOrder is 3
   let sunsetDirection = CompassPoint.west.rawValue
   // sunsetDirection is "west"

Initializing from a Raw Value     Raw Valueの初期化

rawValue の型で列挙を定義すると、列挙は自動的にイニシャライザを受け取ります。このイニシャライザは、rawValueの型の値を(rawValue というパラメータとして)受け取り、列挙のcaseまたは nil を返します。このイニシャライザを使用して、列挙体の新しいインスタンスを作成することができます。

この例では、天王星をその生の値である7から特定しています。

    let possiblePlanet = Planet(rawValue: 7)
   // possiblePlanet is of type Planet? and equals Planet.uranus

しかし、すべての可能なInt値が一致する惑星を見つけるわけではありません。このため、rawValueのイニシャライザは、常にオプションの列挙caseを返します。上の例では、possiblePlanetはPlanet?タイプ、つまり "オプションのPlanet "です。

すべての生の値が列挙ケースを返すわけではないので、生の値のイニシャライザは、失敗するイニシャライザです。詳細については、「失敗する初期化子」を参照してください。

位置が11の惑星を見つけようとすると、rawValueのイニシャライザが返すオプションのPlanetの値はnilになります。

    let positionToFind = 11
   if let somePlanet = Planet(rawValue: positionToFind) {
       switch somePlanet {
       case .earth:
           print("Mostly harmless")
       default:
           print("Not a safe place for humans")
       }
   } else {
       print("There isn't a planet at position \(positionToFind)")
   }
   // Prints "There isn't a planet at position 11"

この例では、オプショナルバインディングを使用して、rawValueが11の惑星にアクセスしようとしています。 if let somePlanet = Planet(rawValue: 11)というステートメントは、オプショナルなPlanetを作成し、もしそれが取得できるのであれば、somePlanetをそのオプショナルなPlanetの値に設定します。この場合、位置が11の惑星を取得することはできませんので、代わりにelseブランチが実行されます。

Recursive Enumerations  再帰的な列挙

再帰的な列挙とは、1つ以上の列挙ケースの関連する値として、その列挙の別のインスタンスを持つ列挙のことです。列挙のケースが再帰的であることを示すには、その前に indirect と書くことで、コンパイラに必要なインダイレクトの層を挿入するように指示します。

例えば、単純な算術式を格納する列挙を以下に示します。

    enum ArithmeticExpression {
       case number(Int)
       indirect case addition(ArithmeticExpression, ArithmeticExpression)
       indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
   }

また、列挙の先頭に indirect と書くことで、関連する値を持つ列挙のすべてのケースに対してインダイレクトを有効にすることができます。

    indirect enum ArithmeticExpression {
       case number(Int)
       case addition(ArithmeticExpression, ArithmeticExpression)
       case multiplication(ArithmeticExpression, ArithmeticExpression)
   }

この列挙体は、3種類の算術式を格納することができます。すなわち、普通の数字、2つの式の加算、2つの式の乗算です。加算と乗算のケースには、算術式に関連した値があり、これらの関連した値によって式を入れ子にすることができます。例えば、「(5 + 4) * 2」という式は、掛け算の右辺に数値があり、掛け算の左辺に別の式があります。データが入れ子になっているので、データを保存するための列挙も入れ子をサポートする必要があります。つまり、列挙は再帰的である必要があります。以下のコードでは、(5 + 4) * 2 用に ArithmeticExpression 再帰的列挙を作成しています。

   let five = ArithmeticExpression.number(5)
   let four = ArithmeticExpression.number(4)
   let sum = ArithmeticExpression.addition(five, four)
   let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

再帰関数とは、再帰的な構造を持つデータを扱うための簡単な方法です。たとえば、算術式を作る関数は次のとおり。

    func evaluate(_ expression: ArithmeticExpression) -> Int {
       switch expression {
       case let .number(value):
           return value
       case let .addition(left, right):
           return evaluate(left) + evaluate(right)
       case let .multiplication(left, right):
           return evaluate(left) * evaluate(right)
       }
   }
   print(evaluate(product))
   // Prints "18"

この関数は、単純に関連する値を返すことで数値計算します。加算や乗算は、左辺と、右辺を加算または乗算します。この場合は"product"を計算するので左の値が"sum"で足し算した結果(5+4)、右の値が"2"。これを掛け算して"18"という結果となっています。



この記事が気に入ったらサポートをしてみませんか?