01. スネークケースとキャメルケースを変換する
user_id と userId を行き来させます。データベースの列名と JSON のキー、定数と変数のように、名前の書き方をそろえるときに使います。HTTPServer のように大文字が続く名前で、よく見かける書き方はほとんどの言語で崩れます。 ほかの言語・正解の例・解説 →
case_convert.swift6.3.3 で 14 / 14 件の出力が正解と一致
// スネークケース(user_id)とキャメルケース(userId)を、指定した向きに変換する
let mode = CommandLine.arguments[1]
let s = CommandLine.arguments[2]
// Character.isLetter は「な」にも true を返す。isASCII と合わせて、半角の英数字だけを通す
func isUpper(_ c: Character) -> Bool { c.isASCII && c.isUppercase }
func isLower(_ c: Character) -> Bool { c.isASCII && c.isLowercase }
func isDigit(_ c: Character) -> Bool { c.isASCII && c.isNumber }
if !s.allSatisfy({ isUpper($0) || isLower($0) || isDigit($0) || $0 == "_" }) {
print("NG: 半角の英数字と _ だけにしてください")
} else if mode == "snake" {
// Swift の文字列は整数の添字で引けないので、配列にしてから前後の文字を見る
let cs = Array(s)
var out = ""
for (i, c) in cs.enumerated() {
if i > 0 && isUpper(c) {
let prev = cs[i - 1]
// 小文字か数字の次の大文字(userId)と、大文字の続きの最後で次が小文字のもの(HTTP|Server)
let nextLower = i + 1 < cs.count && isLower(cs[i + 1])
if isLower(prev) || isDigit(prev) || (isUpper(prev) && nextLower) {
out += "_"
}
}
out += c.lowercased()
}
print(out)
} else {
// split は既定で空の部分を捨てる(omittingEmptySubsequences: true)。「__」でも空の部分はできない
let parts = s.split(separator: "_")
var out = ""
for (i, p) in parts.enumerated() {
// Foundation の capitalized は単語の区切りを自分で決めるので、先頭1文字と残りを自分で分ける
out += i == 0 ? p.lowercased() : p.prefix(1).uppercased() + p.dropFirst().lowercased()
}
print(out)
}
02. HTML の特殊文字をエスケープする
利用者が入力した文字を HTML に埋め込む前に、< > & " ' を < などに置き換えます。標準の関数は言語ごとに ' の書き方が違い、この決まりの形とそのまま一致したのは Ruby だけでした。 ほかの言語・正解の例・解説 →
escape.swift6.3.3 で 8 / 8 件の出力が正解と一致
// 受け取った文字列の & < > " ' を、HTML の書き方(& < > " ')に変える
// Swift の標準ライブラリにはエスケープの関数がないので、1文字ずつ見て置き換える
let s = CommandLine.arguments[1]
var out = ""
// Swift の Character は「見た目の1文字」で、< のあとに結合文字が続くと2つで1文字になり '<' と一致しない。
// unicodeScalars で、コードポイント1つずつ見る
for u in s.unicodeScalars {
switch u {
case "&": out += "&"
case "<": out += "<"
case ">": out += ">"
case "\"": out += """
case "'": out += "'"
default: out.unicodeScalars.append(u)
}
}
print(out)
03. 順番を保って重複を消す
同じ値の2回目以降を消し、初めて出た順に残します。タグの一覧や、選んだ項目の一覧でよく使います。集合(Set)に入れて戻す書き方では、言語によって順番が変わったり、実行するたびに変わったりします。 ほかの言語・正解の例・解説 →
unique.swift6.3.3 で 9 / 9 件の出力が正解と一致
// 受け取った文字列から、2回目以降に出たものを消し、初めて出た順に出す
let items = Array(CommandLine.arguments.dropFirst())
// Swift の String の == と Set<String> は、見た目が同じ(正規化して同じ)なら同じとみなす。
// 「が」1文字と「か」+濁点を別にするため、UTF-8 のバイトの並びで覚える
var seen = Set<[UInt8]>()
let out = items.filter { seen.insert(Array($0.utf8)).inserted }
for s in out {
print(s.isEmpty ? "(空)" : s)
}
print("\(out.count)件(\(items.count - out.count)件を消した)")
04. 日付と時刻を ISO 8601 で読み書きする
API やログでよく使う 2026-10-30T09:00:00+09:00 の形を読み、UTC と日本時間で書き直します。標準のパーサーにそのまま渡すと、2月30日を3月2日として読んだり、タイムゾーンの無い日時を動かす場所の時間帯で読んだりします。 ほかの言語・正解の例・解説 →
iso8601.swift6.3.3 で 15 / 15 件の出力が正解と一致
// ISO 8601 の日時を受け取って、UTC と日本時間で書き出す
import Foundation
// ISO8601DateFormatter は秒の小数を「有り」か「無し」のどちらかに決めないと読めず、桁数の扱いも Mac と Linux で揃わない。
// 形は正規表現で決めて、数字を取り出して Calendar で組み立てる。
// /…/ と書く正規表現はコンパイラの設定(Swift 5 モード)で使えないことがあるので、#/…/# で書く
let iso = #/([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,9})?(Z|[+-][0-9]{2}:[0-9]{2})?/#
func convert(_ s: String) -> String {
// wholeMatch は文字列全体が合うときだけ当たる
guard let m = s.wholeMatch(of: iso) else { return "NG: 日時の形ではありません" }
let (y, mo, d, h, mi, se) = (Int(m.1)!, Int(m.2)!, Int(m.3)!, Int(m.4)!, Int(m.5)!, Int(m.6)!)
// Calendar は動かす場所の設定(Calendar.current)を使わず、西暦の暦と UTC に固定する。
// TimeZone(secondsFromGMT:) は時差だけのタイムゾーンで、tzdata を見ない
var cal = Calendar(identifier: .gregorian)
cal.timeZone = TimeZone(secondsFromGMT: 0)!
// date(from:) は 2月30日を3月2日に繰り上げるので、作った日時を年月日に戻して入力どおりかを確かめる
let local = DateComponents(year: y, month: mo, day: d, hour: h, minute: mi, second: se)
guard let lt = cal.date(from: local),
cal.dateComponents([.year, .month, .day, .hour, .minute, .second], from: lt) == local
else { return "NG: 日時の形ではありません" }
guard let tz = m.8 else { return "NG: タイムゾーン(Z か +09:00 など)を付けてください" }
var offset = 0
if tz != "Z" {
let oh = Int(tz.dropFirst().prefix(2))!, om = Int(tz.suffix(2))!
if oh > 23 || om > 59 { return "NG: 日時の形ではありません" }
offset = (tz.first == "+" ? 1 : -1) * (oh * 3600 + om * 60)
}
// 書いてある時刻を UTC として作ったので、時差を引くと本当の UTC になる
let t = lt.addingTimeInterval(TimeInterval(-offset))
func show(_ format: String, _ secondsFromGMT: Int) -> String {
let f = DateFormatter()
f.calendar = cal
f.locale = Locale(identifier: "en_US_POSIX")
f.timeZone = TimeZone(secondsFromGMT: secondsFromGMT)!
f.dateFormat = format
return f.string(from: t)
}
return "UTC " + show("yyyy-MM-dd'T'HH:mm:ss'Z'", 0) + "\n" + "日本時間 " + show("yyyy年MM月dd日 HH:mm:ss", 9 * 3600)
}
print(convert(CommandLine.arguments[1]))
05. アクセスログを集計する
Web サーバーのアクセスログを標準入力から読み、ステータスごとの件数と、応答の遅いリクエストの上位3件を出します。空白で区切って何番目かを取る書き方は手軽ですが、壊れた行を数えたり、応答秒を文字のまま並べたりします。 ほかの言語・正解の例・解説 →
access_log.swift6.3.3 で 8 / 8 件の出力が正解と一致
// 標準入力のアクセスログを読んで、件数・ステータスごとの件数・遅い順の上位3件を出す
// 日時やHTTPの版には空白が入るので、空白で区切って何番目と数えず、行の形そのものを正規表現で確かめる。
// Swift の正規表現の \d は全角などの数字にも合うので [0-9] と書く。#/…/# は「/」を書きやすくする囲み方
let log = #/\S+ \S+ \S+ \[[^\]]+\] "(\S+) (\S+) [^"]+" ([0-9]{3}) ([0-9]+|-) ([0-9]+\.[0-9]+)/#
var rows: [(sec: Substring, secNum: Double, method: Substring, path: Substring, status: Substring)] = []
var bad = 0
// readLine は \n でも \r\n でも行を区切り、行の終わりの記号を取って返す
while let line = readLine() {
if line.isEmpty {
continue
}
// firstMatch は行のどこかに合えば通る。行全体が形どおりか確かめるのは wholeMatch
guard let m = try? log.wholeMatch(in: line) else {
bad += 1
continue
}
// 正規表現で「数字.数字」と確かめてあるので、Double(…) は nil にならない
rows.append((m.5, Double(m.5)!, m.1, m.2, m.3))
}
print("件数 \(rows.count)")
if bad > 0 {
print("読めない行 \(bad)")
}
var counts: [Substring: Int] = [:]
for r in rows {
counts[r.status, default: 0] += 1
}
print("ステータス")
// Dictionary を回す順番は決まっておらず、動かすたびに変わる。キーを並べてから出す
for status in counts.keys.sorted() {
print("\(status) \(counts[status]!)")
}
print("遅い順")
// Swift の sort は安定と約束されていない(いまの実装は安定だが、文書では保証しない)。
// 何行目かを2つ目のキーにして、同じ秒なら先に出た行を先にする
let slow = rows.enumerated().sorted { a, b in
a.element.secNum != b.element.secNum ? a.element.secNum > b.element.secNum : a.offset < b.offset
}.prefix(3)
for (_, r) in slow {
print("\(r.sec) \(r.method) \(r.path)")
}
06. URL 用の文字列(slug)を作る
記事のタイトルから「hello-world」のような URL の一部を作ります。ブログや商品ページのアドレスを、見て意味が分かる形にするときに使います。よく見かける書き方は、日本語のタイトルをそのまま URL に入れたり、空の slug を黙って返したりします。 ほかの言語・正解の例・解説 →
slug.swift6.3.3 で 14 / 14 件の出力が正解と一致
// 文字列を URL に使える形(slug)に変える
let maxLen = 32
// lowercased() は Unicode の決まりで変えるので、İ が2文字になる。1文字ずつ見て ASCII だけを変える
var s = ""
for ch in CommandLine.arguments[1] {
// isLetter は「あ」にも true を返す。isASCII と合わせて半角の英数字だけを残す
let c = ch.isASCII && ch.isUppercase ? Character(ch.lowercased()) : ch
if c.isASCII && (c.isLowercase || c.isNumber) {
s.append(c)
} else if !s.hasSuffix("-") {
// 続いている分はまとめて「-」1つにする(絵文字のように複数の値でできた文字も1つ)
s += "-"
}
}
if s.hasPrefix("-") { s.removeFirst() }
if s.hasSuffix("-") { s.removeLast() }
if s.count > maxLen {
s = String(s.prefix(maxLen))
if s.hasSuffix("-") { s.removeLast() }
}
print(s.isEmpty ? "NG: URL に使える文字がありません" : s)
07. URL エンコードする
検索語や日本語の名前を、URL のクエリの値(`?q=...`)に入れられる形に変えます。よく見かける書き方は、空白を「+」にしたり、`!` や `~` を変えずに残したりして、受け取る側で別の文字列になります。 ほかの言語・正解の例・解説 →
url_encode.swift6.3.3 で 15 / 15 件の出力が正解と一致
// 文字列を URL のクエリの値に入れられる形(パーセントエンコード)に変える
// addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) は & や = を残す(Foundation も要る)
// utf8 はそのまま UTF-8 のバイトの並び
let keep = Set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~".utf8)
let hex = Array("0123456789ABCDEF")
var out = ""
for b in CommandLine.arguments[1].utf8 {
if keep.contains(b) {
out.append(Character(UnicodeScalar(b)))
} else {
out.append("%")
out.append(hex[Int(b >> 4)])
out.append(hex[Int(b & 0xF)])
}
}
print(out)