文字列を分解して文字毎の個数を得る構造体をつくる【競技プログラミング】

# はじめに
- [とある問題](https://atcoder.jp/contests/abc009/tasks/abc009_3)を解く際、同じ長さの2つの文字列を比較して、同じ文字の個数の差を利用する場面があったため、専用の構造体を作ろうと思ったのがきっかけです。
- ちょっと分かりづらいと思うから、例を言うと、文字列①「aaabbc」と文字列②「abbbcc」があったとき、①と②を比べるとaの差は2文字、bの差は1文字、cの差は1文字なので、合計4文字の差となるので、答えは4となります。
- ついでに、①の方が大きい時の差（上記なら2文字）、②の方が大きいときの差(上記なら2文字)も出せるようにしておこうか。いや、でも、これって常に同値（4文字の半分）になるから意味ないね。

# つくってみた
- とりあえず、文字列は英小文字(a〜z)のみとし、Dictionary型をデータ格納に用いることとした。
- 構造体の名前はDaz("D"ictionary型を英小文字"a-z"の格納に使うから)とした。
```swift
// 文字列を文字（abc）毎の数に置き換える。文字順の情報は消える
struct Daz {

    let (lb,ub) = (97,122) // 97はaのasciiコード、122はzのasciiコード
    var dic:[Character:Int] = [:] // 辞書型データとして格納
    
    init?(_ abc:String) {

        let ary = Array(abc)
    
        (lb...ub).forEach{ dic[Character(UnicodeScalar($0)!)] = 0 }
        
        for a in ary {
            dic[a,default:0] += 1
            if a.asciiValue! < lb || a.asciiValue! > ub {return nil} // 失敗可能イニシャライザ
        }
         
    }
    
    // 文字数を返す
    func len()->Int{
        var sum = 0
        for d in dic {
            sum += d.1
        }
        return sum
    }
    
     // 長さが同じ文字列の差異を返す。文字数が違う場合は「-1」、文字数が一致するときは「不一致文字数」を返す
    func diff(_ adic:Self)->Int{
        if self.len() != adic.len() {return -1}
        var sum = 0
        var cnt = 0
        for (k,v) in dic {
            sum += abs(v - adic.dic[k]!)
        }
        return sum
    }
}

var d1 = Daz("aaabbc")!
var d2 = Daz("abbbcc")!

print(d1.diff(d2)) // 4

```
- 格納した文字数を返す関数をlen()とした。そして、目的の同じ文字の文字数の差異を返す関数をdiffとした。
- なお、失敗可能イニシャライザとしている。つまり、文字列にa〜z以外の文字を含むときは、nilを返すということ。よって、イニシャライズするとき、お尻に！を付けている。

# 脱線（辞書型の順番）
- ついでに、辞書型のストアドプロパティに格納されたデータを見やすくするための計算型プロパティsortedも追加した。辞書型って、要素を表示すると並び順が毎回バラバラで醜いから、直接表示はしたくないんだよね。
```swift    
    var sorted:[(Character,Int)] { // abc順に表示したいとき用
        dic.sorted{$0.0 < $1.0}.map{$0}.filter{$0.1 > 0}
    }
```
- 細かいことを言えば、辞書型は[Collectionプロトコル](https://developer.apple.com/documentation/swift/collection/)に準拠していて、Collectionプロトコルは[Sequenceプロトコル](https://developer.apple.com/documentation/swift/sequence)に準拠している。そして、Sequenceプロトコルは、先頭から順番に全ての要素にアクセスできるから、辞書型も順番自体は内部に持っているけど、それがインスタンスを作成する度に、毎回ズレちゃうということ。
- 具体的に言うと、下記コードを走らせるたび、①の表示は、`["b": 20, "a": 10, "c": 30]`となったり、`["a": 10, "c": 30, "b": 20]`となったり、毎回バラバラだけど、毎回①②③は一致している。インスタンス生成時に、順番を内部に持つので、①②③の間で、順番は変わらないと言うこと。
```swift
let dic = ["a":10,"b":20,"c":30]

print(dic) // ①
print(dic) // ②
print(dic) // ③
```


# おまけ
- [データ構造体](https://qiita.com/kei_tnk/items/47f952f3053301faa265)を作るなら、popFirst()ぐらいは欲しいよな、と思って関数を作った。その過程で、先頭文字を表示するだけの計算型プロパティfirstも作っている。
```swift
    // 最小値をO(1)で取り出せるようにfirstも容易
    var first:(Character,Int)?

    mutating func popFirst()->Character?{ // 
        if len() == 0 {return nil}
        let ans = first!.0
        dic[ans,default:0] -= 1
        if len() > 0 {first = sorted[0]} else {first = nil}
        return ans
    }
```
- firstを追加するに当たって、initも改造
```swift
    init?(_ abc:String) {

        //中略//

        //first用に追加
        for i in lb...ub {
            let a = Character(UnicodeScalar(i)!)
            if dic[a]! > 0 {first = (a,dic[a]!) ; break}
        }        
        
    }
```
- それと、任意の文字の文字数を表示するcnt(文字)や任意の文字を削除するremove(文字)も作った。
```swift    
    func cnt(_ a:Character)->Int{
        return dic[a]!
    }
    
    mutating func remove(_ a:Character)->Bool{
        if len() == 0 {return false}
        if a == first!.0 {popFirst();return true}
        
        let code = Int(a.asciiValue!)
        if code < lb || code > ub {return false}
        
        if dic[a]! > 0 {
            dic[a,default:0] -= 1
            return true
        } else {return false}
    }
```

# おわりに
- 今回初めて、失敗可能イニシャライザinit?()を使ってみた。
- これは、範囲外(a-z外)の文字列を受け付けたときにインスタンスを作成させないための処置。
- 本当は、エラーを吐くようにinit()throws{}の形にしようと思ったけど、それだと、イニシャライズする際、`try? Daz("aaabc")`のようにtryが必要になってウザったいので、`Daz("aaabc")!`のようにビックリマーク1つで済む「失敗可能イニシャライザ」を採用したよ。
- それにしても、これまで一度も失敗可能イニシャライザもthrowsをつかったエラー有りイニシャライザも書いたことないとか、個人の趣味でコード書いてるだけだと成長しないよなぁ、と我ながら思うね。

# コード全体
- 一発でコピペできるように、コード全体も載せときます。
```
// 文字列を文字（abc）毎の数に置き換える。文字順の情報は消える
struct Daz {

    let (lb,ub) = (97,122) // 97はaのasciiコード、122はzのasciiコード
    var dic:[Character:Int] = [:] // 辞書型データとして格納
    
    // 最小値をO(1)で取り出せるようにfirstも容易
    var first:(Character,Int)?
    
    var sorted:[(Character,Int)] { // abc順に表示したいとき用
        dic.sorted{$0.0 < $1.0}.map{$0}.filter{$0.1 > 0}
    }

    init?(_ abc:String) {

        let ary = Array(abc)
    
        (lb...ub).forEach{ dic[Character(UnicodeScalar($0)!)] = 0 }
        
        for a in ary {
            dic[a,default:0] += 1
            if a.asciiValue! < lb || a.asciiValue! > ub {return nil} // 失敗可能イニシャライザ
        }
        
        for i in lb...ub {
            let a = Character(UnicodeScalar(i)!)
            if dic[a]! > 0 {first = (a,dic[a]!) ; break}
        }        
        
    }
    
    // 文字数を返す
    func len()->Int{
        var sum = 0
        for d in dic {
            sum += d.1
        }
        return sum
    }
    
     // 長さが同じ文字列の差異を返す。文字数が違う場合は「-1」、文字数が一致するときは「不一致文字数」を返す
    func diff(_ adic:Self)->Int{
        if self.len() != adic.len() {return -1}
        var sum = 0
        var cnt = 0
        for (k,v) in dic {
            sum += abs(v - adic.dic[k]!)
        }
        return sum
    }
    
    func cnt(_ a:Character)->Int{
        return dic[a]!
    }
    
    mutating func popFirst()->Character?{ // 
        if len() == 0 {return nil}
        let ans = first!.0
        dic[ans,default:0] -= 1
        if len() > 0 {first = sorted[0]} else {first = nil}
        return ans
    }
    
    mutating func remove(_ a:Character)->Bool{
        if len() == 0 {return false}
        if a == first!.0 {popFirst();return true}
        
        let code = Int(a.asciiValue!)
        if code < lb || code > ub {return false}
        
        if dic[a]! > 0 {
            dic[a,default:0] -= 1
            return true
        } else {return false}
    }
    
}
```