🎯 最終演習

Swiftで1か月の家計を費目別にまとめてみよう(総合演習)

ここまでの Swift レッスンで学んだことを全部つないで、動くものを1つ完成させます。新しい構文は出てきません。struct・enum・辞書・オプショナル・guard let・sorted——すでに練習したものだけで組み立てます。

作るのは1か月の家計の集計です。費目ごとに合計し、予算と比べ、金額の大きい支出を並べて、レポートにまとめます。

この演習には仕掛けが2つあります。——予算を決めていない費目と、メモの無い支出です。どちらも「値が無い」状況で、Swiftではこれをオプショナルで表します。

ここがSwiftを学ぶ意味のいちばん出るところです。「無いかもしれない」を型に書かせ、使う前に必ず確かめさせる——だからguard let??が、覚えるべき記法ではなく必要だから使う道具として出てきます。

このページではコードを実行していません。かわりにお手本と同じコードを書けたかを、その場で判定します(判定はあなたのブラウザの中だけで行われ、どこにも送信されません)。

表示している実行結果は本物です。このページを作るときに、実際のSwiftで走らせて得た出力をそのまま載せています。

完成イメージ

最後まで進むと、このコードが完成します。下の実行結果は、このページを作るときに開発機で実際にコードを走らせて得たものです(このページ上では実行していません)。

===== 今月の家計 =====
--- 金額の大きい順 ---
19日 食費 6,120円  (来客の買い出し)
 3日 食費 4,280円
12日 趣味 3,800円  (写真展)
--- 費目別 ---
食費 使用 13,050円 / 予算 12,000円  超過
日用品 使用 2,800円 / 予算 3,000円  注意
交通費 使用 3,600円 / 予算 6,000円  余裕あり
趣味 使用 3,800円 / 予算 ——  予算未設定
--------------------------------------------
支出合計: 23,250円
1費目が予算を超えています。

STEP 1費目をenumで、支出をstructで表す

🎯 このステップのゴール: 取りうる費目を限定し、支出1件を値型でまとめる

まずデータの形を決めます。費目から始めましょう。

費目をStringにすると、"食費""食品費"が混ざります。集計したときに別の費目として数えられて、合わない——ありがちな失敗です。

enum Category: Stringと書くと、この4つ以外は存在できません: Stringを付けているのはrawValue(生の値)を持たせるためで、Category.food.rawValue"食費"になります。

この書き方の効き目は、表示を変えたくなったときに出ます。「食費」を「食料品費」にしたい——直すのはenumの1行だけです。

支出1件はstruct Expenseです。Swiftではclassよりstructを先に考えます

違いは「渡したときに何が起きるか」です。struct値型——他の変数に入れると複製されます。片方を書き換えても、もう片方は変わりません。class参照型で、同じ実物を指すので、片方を変えるともう片方も変わります。

家計簿の1件のような「ただのデータ」は、複製されるほうが安全です。知らないうちに別の場所の値が変わっていた、という事故が起きません。

let memo: String??オプショナルです。メモの無い支出のほうが多いので、nilを許す形にしています。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
支出件数: 8件
1件目: 3日 食費 4280円
予算を決めた費目: 3個

STEP 2辞書で費目ごとに合計する

🎯 このステップのゴール: default付きの添字で、初回の場合分けを消す

費目ごとに合計します。使うのは辞書——キーと値の対応表です。

var totals: [Category: Int] = [:]で空の辞書を作ります。[:]が空の辞書[]が空の配列です。見た目が似ているので注意してください。

集計の中身がこの演習でいちばん覚えて帰ってほしい1行です。

totals[e.category, default: 0] += e.amount

default: 0は「そのキーがまだ無ければ0として扱う」という意味です。だから初回でも0 + 4280になって、正しく積み上がります

これが無いとどうなるか。辞書から取り出した値はInt?——キーが無ければnilなので、そのままでは足せません。if letで場合分けして、無ければ代入、あれば加算……と4行くらい書くことになります

default:はその4行を1行に畳んでくれます。Swiftらしい、実用的な短縮です。

戻り値の型を[Category: Int]と書いているのにも意味があります。キーがCategoryに限られるので、totals["食費"]のような書き方は通りません。ステップ1でenumにしておいた効果が、ここまで効いています。

引数の_ expenses:_は「呼ぶときにラベルを書かせない」という指定です。totalByCategory(expenses)と素直に書けます。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
食費: 13050円
日用品: 2800円
交通費: 3600円
趣味: 3800円

STEP 3guard letで「予算が無い」を先に片付ける

🎯 このステップのゴール: 早めに抜けて、その先はオプショナルを気にせず書く

予算と比べて、状態を一言で表します。ただし趣味には予算を決めていません——budgets[.hobby]nilです。

ここでguard letを使います。

guard let budget = budget else { return "予算未設定" }——読み方はbudgetが取り出せなければ、ここで抜ける」です。

if letとの違いは「その後の書きやすさ」です。if letで書くと、取り出した値が使えるのは波括弧の中だけ。判定が続くと、どんどん入れ子が深くなります。

guard letは逆で、この行より下ではbudgetIntとして使えます?が外れた状態で、最後まで素直に書けます。

「うまくいかない場合を先に全部片付けて、残りを本筋として書く」——これがguardの考え方です。Swiftのコードが浅く読みやすいのは、この書き方が根付いているからです。

guardelseでは必ず抜けなければなりませんreturnthrowを書かないとコンパイルが通りません。「抜けたつもりで抜けていない」が起きないようになっています。

注意の判定はDouble(spent) >= Double(budget) * warnRatioです。整数どうしのままでは0.9を掛けられません——Swiftは型の混在に厳しいので、明示的にDouble()で変換します。

面倒に見えますが、気づかないうちに小数が切り捨てられていた、という事故が起きません

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
食費 13050 / 12000 → 超過
日用品 2800 / 3000 → 注意
交通費 3600 / 6000 → 余裕あり
趣味 3800 / 未設定 → 予算未設定

STEP 4決まった順番で費目を並べる

🎯 このステップのゴール: 辞書の順序に頼らず、自分で決めた並びで回す

ステップ2で作った辞書を、そのままforで回して表示すると——実行するたびに順番が変わります

これはバグではありません。辞書はキーから値を素早く引くための入れ物で、順番を保つようには作られていないのです。

SwiftでもKotlinでもJavaでもPythonでも、多かれ少なかれ同じ問題があります。「手元では正しく見えたのに、本番で並びが変わった」——言語を問わず、必ず一度は踏みます

直し方は決まっています。表示する順番を、自分で持つ

static let all: [Category] = [.food, .daily, .transport, .hobby]——enumの中に並び順を書いておきます。あとはこの配列を回せば、いつでも同じ順で出ます。

enumの中に置くのが要点です。費目を1つ足したとき、同じファイルの数行下に並び順があるので、直し忘れにくくなります。別の場所に離して置くと、まず忘れます。

totals[category] ?? 0??は、nilなら右の値を使う」という意味です。1件も支出の無い費目は辞書にキーが無いので、0として表示します。

budget.map { yen($0) } ?? "——"は少し込み入っています。mapは「中身があるときだけ、その中身を加工する」——nilなら加工せずnilのままです。そこに?? "——"で受け止めています。

「あるときだけ整形し、無ければ別のものを出す」が1行で書けます。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
食費: 使用 13050円 / 予算 12000円
日用品: 使用 2800円 / 予算 3000円
交通費: 使用 3600円 / 予算 6000円
趣味: 使用 3800円 / 予算 ——

STEP 5金額の大きい順に並べて上位を出す

🎯 このステップのゴール: sortedに比較の条件を渡し、prefixで先頭だけ取る

「何にいちばん使ったか」を出します。金額の大きい順に並べて、上位3件だけ表示します。

expenses.sorted { $0.amount > $1.amount }——並べ替えの条件を渡すだけです。

$0$1は「比べる2つ」を表す省略記法です。$0.amount > $1.amountは「1つ目の金額が2つ目より大きいなら、1つ目を先に」——つまり降順という意味になります。

<にすれば昇順です。不等号の向きが、そのまま並び順になります。

sortedは元の配列を変えません。並べ替えた新しい配列を返します。元を直接並べ替えたいときはsort()——末尾にedが付くほうが「新しく作って返す」という命名の決まりがSwiftにはあります。

.prefix(3)先頭3件を取ります。件数が3件未満でも落ちません——あるだけ返ってきます。sorted[0..<3]と書くと、3件未満のときに落ちます。

金額の整形はNumberFormatterです。numberStyle = .decimalとすると3桁ごとにカンマが入ります。

?? String(amount)で受けているのは、formatter.string(from:)が失敗しうるためです。まず失敗しませんが、Swiftは「失敗しうる」と宣言されたものを、必ず扱わせます

メモの表示がe.memo.map { " (\($0))" } ?? ""です。ステップ4と同じ形——あるときだけ括弧を付け、無ければ何も出しません。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
--- 金額の大きい順 ---
19日 食費 6,120円  (来客の買い出し)
3日 食費 4,280円
12日 趣味 3,800円  (写真展)

STEP 6ひとつの関数にまとめて仕上げる

🎯 このステップのゴール: reportにまとめ、桁を揃えて締めの一言まで出す

最後の仕上げです。ここまでの部品をreportという1つの関数にまとめます

並べる順番に意味を持たせます。まず金額の大きい支出、次に費目別、最後に合計と結論——目を引くものを先に、まとめを最後にという流れです。

桁揃えにString(format:)を使っています。ここに Swift ならではの注意点があります

%@という指定子と、as NSStringという変換が出てきます。String(format:)はもともと Objective-C 時代からある仕組みで、文字列を渡すときは%sではなく%@を使い、NSString に変換して渡すのが確実です。

見慣れない書き方ですが、Swiftが iOS/macOS の土台と地続きであることの現れです。文字列補間\(...)だけで済むなら、そちらのほうが Swift らしい書き方になります。

String(repeating: "-", count: 44)で区切り線を引きます。引数に名前が付いているのが Swift らしいところで、String("-", 44)のような曖昧な呼び方はできません。

締めはif over == 0で分けます。「予算を超えた費目はありません」か「〇費目が超えています」か——数字を並べて終わりにせず、「それで、どうなの?」に答えるのが道具としての親切さです。

完成です。budgets.hobby: 5000を足してみてください。「予算未設定」が「余裕あり」に変わります——ステップ3でguard letを書いておいた分岐が、実際に切り替わるところが見られます。

warnRatio0.5にすると、「注意」の付く費目が増えます。1つの数字を変えるだけで判定が変わる——定数に切り出しておいた効果です。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
===== 今月の家計 =====
--- 金額の大きい順 ---
19日 食費 6,120円  (来客の買い出し)
 3日 食費 4,280円
12日 趣味 3,800円  (写真展)
--- 費目別 ---
食費 使用 13,050円 / 予算 12,000円  超過
日用品 使用 2,800円 / 予算 3,000円  注意
交通費 使用 3,600円 / 予算 6,000円  余裕あり
趣味 使用 3,800円 / 予算 ——  予算未設定
--------------------------------------------
支出合計: 23,250円
1費目が予算を超えています。

よくある質問

このページではコードを実行しないのですか?

していません。Swiftはコンパイルが必要な言語で、ブラウザの中では走らせられないためです。このページではお手本と同じコードを書けたかどうかを判定する形にしています(判定はあなたのブラウザの中だけで行われ、コードはどこにも送信されません)。

ただし表示している実行結果は本物です。このページを作るときに実際のSwiftで走らせた出力を、そのまま載せています。

guard let と if let は、どちらを使えばいいですか

取り出した値をその後ずっと使うならguard letです。

if letで取り出した値は波括弧の中でしか使えません。判定が2つ3つと続くと、その分だけ入れ子が深くなり、本筋がどんどん右へ寄っていきます

guard letならその行より下でずっと使えます。「うまくいかない場合を先に片付けて、残りを素直に書く」という形になり、コードが浅く保てます。

if letが向くのは、その場だけで使って終わりのときです。

struct と class は、どう使い分けますか

迷ったらstructから始めてください。Swiftの標準ライブラリも、配列も文字列も辞書もすべてstructです。

struct値型で、他の変数に入れると複製されます。知らないうちに別の場所の値が変わっていた、という事故が起きません

classが要るのは、同じ実物を複数の場所から指したいときや、継承したいときです。画面の状態や、アプリ全体で1つだけ持つ設定などが当てはまります。

辞書の順番が実行するたびに変わります

辞書は順番を持たない入れ物だからです。キーから値を素早く引くことに特化していて、並び順は保証されません。

決まった順で表示したいなら、ステップ4のように並び順を自分で配列として持ちますCategory.allを回せば、何度実行しても同じ順です。

キーを並べ替える方法もありますが、「食費・日用品・交通費・趣味」のように意味のある順に並べたいときは、自分で書いたほうが素直です。

🚩 次はこのカテゴリへ

Swiftのオプショナルと、Kotlinのnull安全はほとんど同じ考え方です。guard letにあたるものが?:と早期return??はそのまま?:——片方を知っていると、もう片方はすぐ読めます。Kotlinカテゴリの最終演習では、記録し忘れのある歩数データを集計します。

Kotlinのレッスンへ進む
広告スペース(バナー)
広告スペース(記事内)