Swift
でプログラミング
してみる
2014年12月 iPhone勉強会京都
荻原剛志
京都産業大学コマンドラインで
Swift
『詳解Swift』
対象読者としては、すでにC言語および Objective-C(あるいは少なくともJava など)によるプログラミングの経験があ る人を想定しています。 すでに出版されている入門書や雑誌記 事ではSwiftの全体像が把握できないと感 じている人には特にお勧めです。 ジェネリクスの機能や標準ライブラリ に関する解説なども含んでおり、現時点 では最も「濃い」Swift本になっていると 思います。今日の内容
簡単なアプリケーションを作成してみよう
Swiftのプログラムの書き方について簡単な導入
プログラムの作成を通じて、Swiftの書き方に慣れる
- クラス、構造体などの定義と使い方
- C/Objective-CのAPIの呼び出し方
題材:n-gram法を使ったランダムな文書作成
日本語の文書を入力して、文字の出現頻度に関する統計情
報を得る
統計情報を利用して、ランダムな文章を作成する
主要なデータ型
種類 型名 説明 整数型 Int 整数全般に対する利用が推奨されている 整数型 UInt 符号のない整数型 実数型 Float 浮動小数点数 実数型 Double 浮動小数点数。精度が高い 論理型 Bool リテラルはtrueとfalse。整数型ではない 文字 Character Unicodeの1文字を表す。整数型ではない 文字 UnicodeScalar Unicodeの文字コードを表す 文字列 String 文字列の内容は変更可能 配列 Array<T> [T] と記述できる 辞書 Dictionary<K,V> [K:V] と記述できる オプショナル Optional<T> T? と記述できる変数と定数
var ell : Int = 100
初期値を設定
してもよい
型を指定する。初期値
から型が分かる場合は
省略可能
let psy : Double = 0.25
いったん値を代入したら
変更できない
変数
タプル
2つ以上の値を ( ) 内に列挙したデータ型
要素数は2つ以上(あまり多い個数は勧められない)
個々の要素は別のデータ型でもよい
タプル自体を型として宣言することもできる
同じ型のタプルの間で代入が可能
var t = (10, “Ten”)
var w: (Int, String) = t
let (x, y) = t
let (_, z) = w
// x = 10, y = Ten
// z = Ten
t.0 で 10、t.1 で Ten が参照可能
_ は値を使わないことを指定
オプショナル型
var msg : String?
型の値、またはnilを
保持する
型?
msg = “
致命的エラー
”
msg = “”
msg = nil
var str : String
str = msg!
値を取り出す(開示: アンラップ)
値がnilの時に開示すると
実行時エラーになる
if str == nil { … }
値を開示せずに調べる
こともできる
オプショナル束縛構文
var msg : String?
if let s = msg { … }
msgがnil以外の値を持つなら、if文の条件が真になると同時に
if文の中でだけ有効な定数sにその値が代入される
let str = msg ?? “
エラー
”
msgがnil以外の値を持つならその値が、そうで
なければ ?? の右側の値が式の値となる
nil併合演算子
nil coalescing operator
関数の引数と返り値
func baz(n:Int, k:String) -> String
func gaz(count n:Int, name k:String)
引数はInt型で仮引数名n、String型で仮引数名kの2つ。
返り値はString型。
引数はInt型で仮引数名n、String型で仮引数名kの2つ。
返り値はなし(Void型)。
countとnameは外部引数名(キーワード)で、関数呼
び出しの時に指定しなければならない。
gaz(count:8, name:”Button”)
func kaz(_ n:Int, name k:String)
メソッドの引数
func draw(rect:CGRect, color:UIColor)
メソッド(クラス、構造体、列挙型の持つ手続き)では、第2引数
以降の仮引数名は自動的に外部引数名(キーワード)になる
draw(r, color:bgColor)
呼び出し例
- (void)drawInRect:(CGRect)rect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alphafunc drawInRect(_ rect: CGRect,
blendMode blendMode: CGBlendMode, alpha alpha: CGFloat)
Objective-C Swift これによって、Swiftの メソッド呼び出しと Objective-Cのメソッド 呼び出しの見かけが大 変良く似たものになる
関数のinout引数
func psy(inout n:Int, k:String)
inout修飾子を付けると、関数内での値の変更が呼び出し側の
変数に反映される
psy(&r, “kongloo”)
呼び出し例
引数は変数で、 & を付ける
func swap2Int(inout a:Int, inout b:Int) {
let t = a; a = b; b = t
}
スワップ関数
Cの関数を呼び出す
func fgets(_: UnsafeMutablePointer<Int8>,
_: Int32, _: UnsafeMutablePointer<FILE>)
-> UnsafeMutablePointer<Int8>
char *fgets(char *str, int size, FILE *stream);
CのAPI
Swiftから呼び出す場合のAPI
import Foundation
var buf = [Int8](count: 100, repeatedValue: 0)
while fgets(&buf, 100, stdin) != nil {
let n = strlen(buf)
if n > 0 && buf[n - 1] == 0x0a {
buf[n - 1] = 0
}
puts(buf)
}
配列の場合、値を変更しないなら & は不要
CのAPIを参照するため
0が100個格納された配列
配列の内容を変
更するため &
が必要
// 末尾の改行を削除
例C関数のAPIの例
func fgets(_: UnsafeMutablePointer<Int8>,
_: Int32,
_: UnsafeMutablePointer<FILE>)
-> UnsafeMutablePointer<Int8>
func puts(_: UnsafePointer<Int8>) -> Int32
func strlen(_: UnsafePointer<Int8>) -> UInt
char *fgets(char *str, int size, FILE *stream);
Cの元のAPI
Swiftから呼び出す場合のAPI
int puts(const char *s);
作成するプログラム
統計情報
解析
生成
日本語文書
生成文字列
乱数文字の出現頻度について
文章中の文字の出現頻度には偏りがある
直前までに現れた文字によって、次に現れる文字を
推定することができる
直前のn文字を使って、次の1文字が何かを推定する
- n文字の組み合わせを n-gram と呼ぶ
- 一般に、現在の情報だけを使って未来を推定する方法をマル
コフモデルと呼ぶ。直前のn文字から次の文字を確率的に推定
する方法(n-gram法)もこの一種である
- 文字ではなく、単語や形態素を使う方が一般的かもしれない
n-gramを使って、ランダムな文(?)を作り出す
簡単な原理
昨日も今日も寒いが、明日も寒いらしい。
3文字組では、先行する2文字が「文脈」となる
今
日 も
寒
1/3 2/3昨 日
も
1/1も 寒
い
2/2 ★ ★ ★ ★ ★ ★ ★も 今
日
1/1今 日
も
1/1が
寒 い
ら
1/2 1/2い が
、
1/1データ構造
キー:文字列(String)
値:CharSet
辞書(Dictionary)
キー
値
昨日
日も
も今
今日
も寒
寒い
いが
CharSetクラス
entry(辞書)
キー:文字、値:整数(Int)
total:整数(Int)
キー 値 も 1total = 1
キー 値 今 1 寒 2total = 3
キー 値 が 1 ら 1total = 2
[String:CharSet]
[Character:Int]
クラス CharSet
(1)
class CharSet {
var entry: [Character:Int]
var total = 0
init() {
entry = [:]
total = 0
}
init(_ c: Character) {
entry = [c:1]; total = 1
}
func inc(key:Character) {
if entry[key]?++ == nil {
entry[key] = 1
}
total++
}
// 続く
イニシャライザ。クラスのインスタンス を生成して初期値を与える。 辞書リテラル(空の辞書) 辞書にkeyをキーとする要素があれば カウントを1つ増やす。なければ追加。 辞書[キー] の書法で返される値は オプショナル型である 辞書 辞書リテラル(要素が1つだけ) イニシャライザは何種類か用意できる (関数のオーバーロードのように) オプショナル・チェーンと呼ばれる記法の一種。式の中で ? が付いた部分の値が nil なら、それ以上の評価をせず、 式全体の値が nil になる。クラス CharSet
(2)
// 続き
func randChar() -> Character {
if entry.count == 1 {
for (ch, _) in entry { return ch }
}
let rnd = Int(random() % total)
var sum = 0
for (c, v) in entry {
sum += v
if sum > rnd { return c }
}
return " " // never
}
}
乱数を使い、出現頻度に応じて、 辞書から1文字を選び出す C言語の標準ライブラリ関数 for-in文で辞書から登録内容 を1件ずつタプルとして取り 出せる 要素が1つだけなら それを取り出す for-in文で、配列や辞書、文字列などから要素を取り出 すことができる。forの後には「let」があると考えれば よく、識別子はループ内で有効な定数となる。関数 readfile
func readfile(path:String) -> NSString? { var err: NSError? = nil
var cont = NSString(contentsOfFile:path, encoding:NSUTF8StringEncoding, error: &err) // ポインタの渡し方に注意! if cont == nil { println( err?.localizedDescription ?? "ファイルが読めません") } return cont }
convenience init?(contentsOfFile path: String, encoding enc: UInt,
error error: NSErrorPointer) - (instancetype)initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error
Objective-C
Swift
ファイルの内容を読み込 んで文字列として返すクラス RandomChat
(1)
var maxlen: Int = 3 class RandomChat {
var dic: [String:CharSet] = [:] var startChars : CharSet
init() {
startChars = CharSet() }
// 続く
func makeModel(str: String) { var chseq = [String]()
var pttn = "" for ch in str { if ch <= Character(" ") { continue } if pttn == "" { if ch == Character(" ") { continue } startChars.inc(ch) }else {
if let cset = dic[pttn] { cset.inc(ch) }else { dic[pttn] = CharSet(ch) } } if ch == "。" || ch == "." { pttn = ""; chseq = [] }else { chseq.append(String(ch)) if chseq.count >= maxlen { chseq.removeAtIndex(0) } pttn = join("", chseq) } } } // 続く 全角空白 空のCharSetを作る
dic: 「文脈」文字列をキーと
してCharSetを持つ辞書
startChars: 文の先頭に現れ
る文字を、出現頻度とと
もに持つ辞書
文脈を構成する 文字を含む配列 文脈を構成する文字列 // 文字を接続 決まった 長さを超 えたら先 頭を削除 文末 文脈に文字 を追加 文頭 文脈が辞書にあれば文字のカウン タを増やす。なければ辞書に追加。 クラス変数 の代わりクラス RandomChat
(2)
// 続き
func makeStatment() -> String {
var pttn = String(startChars.randChar()) var chseq = [pttn]
var strbuf = pttn
while let cset = dic[pttn] { let ch = cset.randChar() let s = String(ch) strbuf += s if s == "。" || s == "." { break } chseq.append(s) if chseq.count >= maxlen { chseq.removeAtIndex(0) } pttn = join("", chseq) } return strbuf } } 文脈に対応するCharSet 文脈を構成する文字を含む配列 文脈を構成 する文字列 // 文字を接続 文頭となる 文字を選ぶ 文末 作った文字列を 格納しておく 文脈の後に続く文字を 適当に1つ選ぶ 文脈に文字 を追加 決まった長さを超えたら先頭を削除
コマンドライン引数
var C_ARGC: CInt
var C_ARGV: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>
$ swift comm.swift -help "(^ ^;" 0: "comm.swift"
1: "-help" 2: "(^ ^;"
$ swiftc comm.swift
$ ./comm -magic=10 -flag=NO 100 0: "./comm" 1: "-magic=10" 2: "-flag=NO" 3: "100"
実行例
実行例
for var i = 0; i < Int(C_ARGC); i++ {
if let s = String.fromCString(C_ARGV[i]) {
println("\(i): \"\(s)\"")
}
}
実行
̶1つのファイルにまとめて
class CharSet
class RandomChat
srandom(UInt32(time(nil))) maxlen = 5
var chat = RandomChat()
for var i = 1; i < Int(C_ARGC); i++ {
if let s = String.fromCString(C_ARGV[i]) { if let text = readFile(s) {
chat.makeModel(text) } } } for _ in 0 ..< 10 { println(chat.makeStatment()) }
chats.swift
$ swift chats.swift ファイル… または $ swiftc chats.swift $ ./chats ファイル… Swiftではクラスや関数の宣言順序を 意識する必要がない。 func readFile実行
̶個別のファイルをコンパイル
class CharSet
class RandomChat func readFile
main
$ swiftc -o chats main.swift CharSet.swift RandomChat.swift $ ./chats ファイル… Swiftではそれぞれのクラスファイルを別々にコンパイル しておくのではなく、まとめてコンパイルするのが標準 的な方法