1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SwiftAdvent Calendar 2024

Day 21

Swiftでコーディングテスト対策 - 応用編 -

Posted at

はじめに

初めまして、26卒の就活生です。
今回は、前回のアルゴリズム問題と同じ形式で、Swiftにおけるコーディングテスト対策を残します。

準備

応用問題は、落ち着いて綺麗なコードを書けば後々自分が楽になると思います。
手順に沿って準備していきましょう。

ディレクトリ構成

応用問題は、一つのファイルで作成するのでなく、複数のフォルダを作成して解くのがおすすめです。
ディレクトリ構成は、完全に個人の好みではありますが、自分なら下記のようなディレクトリ構成をします。

.
└── App/
    ├── main.swift  //メイン関数
    ├── Extensions/
    │   └── //「型名+Extensions.swift」
    ├── Models/
    │   └── //「モデル名.swift」
    └── Managers/
        └── //「モデル名+Manager.swift」
  • Extensions
    • 主に型変換の処理をここに書きます
  • Models
    • ユーザーやチケット情報など、情報をひとまとめにするモデルをここに書きます
  • Managers
    • モデルの追加、取得、更新、削除処理(CRUD)をここに書きます

文字列の受け取り

それではモデルを構築していくために、標準入力をみて問題に適した型で受け取っていきましょう。
例として、ユーザー情報をモデルに当てはめていきます。

input.txt
register user1 taro 2024/12/13-16:13 21

という入力を、Userモデルを作成して当てはめていきましょう。
ちなみに、標準入力の内容は、左から、

  • クエリー内容(register)
  • ユーザーID(user1)
  • ユーザー名(taro)
  • 登録日時(2024/12/13-16:13)
  • 年齢(21)

モデルの作成

それでは、Userという名前でモデルを作成していきましょう。

User.swift
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としました。

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を追加宣言してあげてください。

モデルに代入

やっとです、入力をモデルに入れていきましょう。
入力は下記を想定します。

input.txt
register user1 taro 2024/12/13-16:13 21

これを先ほど作ったUserモデルに入れていきます。

main.swift
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)と検索処理です。書いていきましょう。

UserManager.swift
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関数の最初に宣言しています。

main.swift
let userManager = UserManager()

では、先ほど作成したユーザー情報をマネージャーに追加してみましょう!

main.swift
let userManager = UserManager()

let user: User = User(
    id: id, 
    name: name, 
    registrationDate: registrationDate, 
    age: age
)

userManager.addUser(user: user)

お疲れ様でした。これで準備は完了です

クエリごとに処理を分ける

コーディングテストで与えられる問題には、おそらくこの処理をして、あの処理をしてって優しく指定してくれますので、そのクエリ毎に処理を分ける関数を書いていきます。
クエリの種類として、

  • register
  • delete
  • update
  • search

の4種類にしていきます。

main.swift
//クエリ毎処理を分ける
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つのクエリーを入力として与えられる形にします。

コーディングテストほど詳しくは書かないですが、今回想定する標準入力のフォーマットは以下の通りです。

input.txt
register <id> <name> <registrationDate> <age>
delete <id>
update <id> <new_name>
search <age>

そして、それぞれの処理のアウトプットは以下のようにします。

output.txt
register id: <id>, age: <age>, <name>さんが追加されました
delete <id>を削除しました
update <id>情報を更新しました
search <age>歳
- id: <id>, age: <age>, <name>さん
- id: <id>, age: <age>, <name>さん...(複数)

与えられる標準入力

input.txt
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

想定される出力

output.txt
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関数です。

main.swift
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の期限をつける処理まではできなかった反省

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?