はじめに
初めまして、26卒の就活生です。
今回は、前回のアルゴリズム問題と同じ形式で、Swiftにおけるコーディングテスト対策を残します。
準備
応用問題は、落ち着いて綺麗なコードを書けば後々自分が楽になると思います。
手順に沿って準備していきましょう。
ディレクトリ構成
応用問題は、一つのファイルで作成するのでなく、複数のフォルダを作成して解くのがおすすめです。
ディレクトリ構成は、完全に個人の好みではありますが、自分なら下記のようなディレクトリ構成をします。
.
└── App/
├── main.swift //メイン関数
├── Extensions/
│ └── //「型名+Extensions.swift」
├── Models/
│ └── //「モデル名.swift」
└── Managers/
└── //「モデル名+Manager.swift」
- Extensions
- 主に型変換の処理をここに書きます
- Models
- ユーザーやチケット情報など、情報をひとまとめにするモデルをここに書きます
- Managers
- モデルの追加、取得、更新、削除処理(CRUD)をここに書きます
文字列の受け取り
それではモデルを構築していくために、標準入力をみて問題に適した型で受け取っていきましょう。
例として、ユーザー情報をモデルに当てはめていきます。
register user1 taro 2024/12/13-16:13 21
という入力を、Userモデルを作成して当てはめていきましょう。
ちなみに、標準入力の内容は、左から、
- クエリー内容(register)
- ユーザーID(user1)
- ユーザー名(taro)
- 登録日時(2024/12/13-16:13)
- 年齢(21)
モデルの作成
それでは、Userという名前でモデルを作成していきましょう。
import Foundation
struct User {
let id: String
let name: String
let registrationDate: Date
let age: Int
}
ここで、import Foudation
についてですが、Date型を扱うために必要です。日時が入力で与えられた場合、Intに変換して秒単位で管理する方法もありますが、一年前とか簡単に取得できるので、Dateで受け取ることをお勧めします。
型変換処理を宣言
モデルの代入に早くいきたいところですが、今回のデータには、Date形式の入力が含まれていますので、入力をDate型に変換しなければなりません。
String型からDate型への変換処理を、Extensions
フォルダ内に作成します。フォルダ名は、Stringに対して拡張処理を行いたいので、String+Extensions.swift
としました。
import Foundation
extension String {
func toDate(format: String = "yyyy/MM/dd-HH:mm") -> Date? {
let formatter = DateFormatter()
formatter.dateFormat = format
formatter.timeZone = .current
return formatter.date(from: self)
}
}
この時、formatは標準入力に従います。今回の日時の標準入力が、
2024/12/13-16:13
なので、上記のように設定しています。
秒まである場合は、ss
を追加宣言してあげてください。
モデルに代入
やっとです、入力をモデルに入れていきましょう。
入力は下記を想定します。
register user1 taro 2024/12/13-16:13 21
これを先ほど作ったUserモデルに入れていきます。
let line = readLine()!.split(separator: " ").map { String($0) }
let id = line[1]
let name = line[2]
let registrationDate = line[3].toDate()!
let age = Int(line[4])!
let user: User = User(
id: id,
name: name,
registrationDate: registrationDate,
age: age
)
最初の行で、空白区切りで配列として受け取ります。splitをするとSubstring型
に変わるので、map
でString型に変換して扱いやすくしています。
この時、Int変換やDate変換はnilを許容するoptionalを返すので、!をつけてoptionalを消し去ります。本来のSwift開発ではお勧めしませんが、コーディングテストならいいでしょう。
Managerでユーザー情報を収める
ユーザー情報を入れることができたら、ユーザー一覧を管理するユーザーマネージャーを作成します。
主に必要になってくる処理は、追加、取得、更新、削除処理(CRUD)と検索処理です。書いていきましょう。
final class UserManager {
private var userList = [String : User]()
//ユーザー追加処理
func addUser(user: User) {
userList[user.id] = user
}
//ユーザー取得処理
func getUser(id: String) -> User? {
return userList[id]
}
// ユーザー更新処理
func updateUser(newUser: User) {
if let _ = userList[newUser.id] {
userList[newUser.id] = newUser // 正しいキーで更新
} else {
print("ユーザーが見つかりません")
}
}
//ユーザー削除処理
func deleteUser(id: String) {
if userList.removeValue(forKey: id) == nil {
print("ユーザーが見つかりません")
}
}
//呼び出し側で条件を指定する
func search(filter: (User) -> Bool) -> [User] {
return userList.values.filter(filter)
}
}
工夫したポイントは、ユーザーリストをidとuserの辞書型にしたことです。こうすることで、ユーザーの取得処理、更新処理において非常に高速に処理を行うことができます。もし、userListだけだったら、ユーザーのidを取得して、一致しているidを探して... という風に、取得や削除に時間がかかってしまうことがわかります。
(final classは、継承する予定がないので一応つけてます。)
Managerの使い方
自分のmain関数の最初に宣言しています。
let userManager = UserManager()
では、先ほど作成したユーザー情報をマネージャーに追加してみましょう!
let userManager = UserManager()
let user: User = User(
id: id,
name: name,
registrationDate: registrationDate,
age: age
)
userManager.addUser(user: user)
お疲れ様でした。これで準備は完了です
クエリごとに処理を分ける
コーディングテストで与えられる問題には、おそらくこの処理をして、あの処理をしてって優しく指定してくれますので、そのクエリ毎に処理を分ける関数を書いていきます。
クエリの種類として、
- register
- delete
- update
- search
の4種類にしていきます。
//クエリ毎処理を分ける
func processLine(_ line: String) {
let items = line.split(separator: " ").map { String($0) }
guard let query = items.first else { return }
switch query {
case "register":
register(items)
case "delete":
delete(items)
case "update":
update(items)
case "search":
search(items)
default:
print("クエリーが見つかりません")
}
}
こちらの関数は、与えられた標準入力を1行ずつforさせるときに毎回呼びます。
解いてみる
それでは、本番の問題みたいに出題しながら、解いていきましょう。
まず、今回与える標準入力として、ユーザーデータの操作をしていきます。
具体的には、クエリーとして、
- register: ユーザーの登録
- update: ユーザーデータの更新(ユーザー名の更新)
- delete: ユーザーの削除
- search: ユーザーの検索(年齢による検索)
以上の4つのクエリーを入力として与えられる形にします。
コーディングテストほど詳しくは書かないですが、今回想定する標準入力のフォーマットは以下の通りです。
register <id> <name> <registrationDate> <age>
delete <id>
update <id> <new_name>
search <age>
そして、それぞれの処理のアウトプットは以下のようにします。
register id: <id>, age: <age>, <name>さんが追加されました
delete <id>を削除しました
update <id>情報を更新しました
search <age>歳
- id: <id>, age: <age>, <name>さん
- id: <id>, age: <age>, <name>さん...(複数)
与えられる標準入力
20
register user1 taro 2024/12/13-16:13 21
register user2 hanako 2023/11/27-08:45 30
register user3 ichiro 2020/05/10-10:00 21
update user1 taroYamada
register user4 yuki 2022/06/15-14:30 25
delete user2
search 30
update user3 otani
register user5 kenji 2023/07/20-18:20 30
delete user4
search 21
register user6 miki 2024/01/05-09:00 21
update user5 kenjiTanaka
register user7 jun 2023/12/01-18:00 25
delete user1
search 25
register user8 akira 2023/03/10-09:45 30
update user8 age 31
delete user5
delete user3
想定される出力
register id: user1, age: 21, taroさんが追加されました
register id: user2, age: 30, hanakoさんが追加されました
register id: user3, age: 21, ichiroさんが追加されました
update user1情報を更新しました
register id: user4, age: 25, yukiさんが追加されました
delete user2を削除しました
search 30歳
update user3情報を更新しました
register id: user5, age: 30, kenjiさんが追加されました
delete user4を削除しました
search 21歳
- id: user1, age: 21, taroYamadaさん
- id: user3, age: 21, otaniさん
register id: user6, age: 21, mikiさんが追加されました
update user5情報を更新しました
register id: user7, age: 25, junさんが追加されました
delete user1を削除しました
search 25歳
- id: user7, age: 25, junさん
register id: user8, age: 30, akiraさんが追加されました
update user8情報を更新しました
delete user5を削除しました
delete user3を削除しました
大変優柔不断で申し訳ございませんが、今回の想定だとnameの更新が必要になるので、
var name
とする必要があります🙇
それでは、実装したmain関数です。
let userManager = UserManager()
let queryList = getQueryList()
for query in queryList {
processLine(query)
}
//標準入力を取得
func getQueryList() -> [String]{
let m = Int(readLine()!)!
var result: [String] = []
for _ in 0..<m {
let line = readLine()!
result.append(line)
}
return result
}
//クエリー毎処理を分ける
func processLine(_ line: String) {
let items = line.split(separator: " ").map { String($0) }
guard let query = items.first else { return }
switch query {
case "register":
register(items)
case "delete":
delete(items)
case "update":
update(items)
case "search":
search(items)
default:
print("クエリーが見つかりません")
}
}
func register(_ items: [String]) {
let id = items[1]
let name = items[2]
let registrationDate = items[3].toDate()!
let age = Int(items[4])!
let user: User = User(
id: id,
name: name,
registrationDate: registrationDate,
age: age
)
userManager.addUser(user: user)
print("\(items[0]) id: \(id), age: \(age), \(name)さんが追加されました")
}
func delete(_ items: [String]) {
let id = items[1]
userManager.deleteUser(id: id)
print("\(items[0]) \(id)を削除しました")
}
func update(_ items: [String]) {
let id = items[1]
let newName = items[2]
guard var user = userManager.getUser(id: id) else {
print("ユーザーが見つかりません")
return
}
user.name = newName
userManager.updateUser(newUser: user)
print("\(items[0]) \(id)情報を更新しました")
}
func search(_ items: [String]) {
let requestAge = Int(items[1])!
let filteredUserList = userManager.search { user in
user.age == requestAge
}
print("\(items[0]) \(requestAge)歳")
for user in filteredUserList {
print("- id: \(user.id), age: \(user.age), \(user.name)さん")
}
}
さいごに
Dateの期限をつける処理まではできなかった反省