- 投稿日:2020-09-10T23:32:43+09:00
Grand Central Dispatch (GCD) の理解を深める
GCD とは?
macOS、iOS、watchOS、tvOS のマルチコアハードウェアでの並列実行タスクをするために提供されている言語機能で、開発者は
DispatchQueue
を使用してスレッドでの処理を管理することができます。また、GCD の Queue は追加されたタスクの順番ごとに処理を実行する FIFO(First In First Out) の性質を持っています。一旦用語をまとめる
Process(プロセス)
アプリケーションの実行インスタンスであり、専用の仮想メモリ空間およびシステムのリソースが割り当てられる。また、プロセス内には複数のスレッドが含まれる。
Thread(スレッド)
プロセスが実行する処理などを格納した箱のようなもの。
Task(タスク)
スレッドに格納される具体的な処理の単位。
Queue(キュー)
アプリケーションに含まれるスレッドを管理するための高レベルな API で、Thread の作成・管理の役割を担っている。また、実際に処理される時にはシステムが効率的なタスクの実行を行うことで、キューはスレッドの管理に集中することができる。
Queue(キュー)の種類
言語で提供されているキューは主に3つあり、それぞれスレッドの管理範囲が異なります。
Main
DispatchQueue.main
でアクセスできるキューで、スレッドはシステムから提供されています(メインスレッド)。また処理の実行は直列で行われていて、原則として UI の更新などはこのスレッドで行う必要があります。DispatchQueue.main.async { // 処理 }Global
DispatchQueue.global(qos:)
でアクセスできるキューで、引数にDispatchQoS.QoSClass
を指定してタスクを実行するスレッドを指定します。また、システムのタスクの優先度にはhigh
・default
・low
・background
の4つがあり、DispatchQoS.QoSClass
の値はシステムが効率的にタスクを行う際の優先順位を決める判断材料としても使用されます。基本的には、開発者はどれが最も優先度が高いなどと考える必要がなく、それぞれのタスク内容に応じて、適切なDispatchQoS.QoSClass
を指定する必要があります。それぞれのタイプの詳細については公式のドキュメントを参照するといいでしょう。また、このキューはシステムで共有され、それぞれ並列で処理の実行が行われます。DispatchQueue.global(qos: .background).async { // 処理 }Custom
DispatchQueue(label: "hoge")
のようにlabel
引数に任意の値を追加することで、独自のスレッドにタスクを追加することができます。また、直列また並列でのタスクの実行を選択することができます。基本的にはattributes
引数で.concurrent
を指定すると並列で実行され、指定がない場合は(Swift2以前は.serial
で指定できたっぽい)、直列での実行になります。DispatchQueue(label: "hoge.hoge.hoge").async { // 処理 }同期・非同期
先で説明した、どのタイプのキューも
同期
・非同期
での実行を選択することができ、それぞれsync(同期)
・async(非同期)
ないで処理を書くことができます。また具体的な挙動としては下記のようになります。
- 同期処理
- スレッドにタスクを追加した時に、呼び出し元のスレッドはそのタスクの完了をまってから次の処理を行います。
- 非同期処理
- スレッドにタスクを追加した時に、呼び出し元のスレッドはそのタスクの完了を待たずに次の処理を行います。
メインスレッド内でバックグランドスレッドにタスクを追加して、
同期
・非同期
でそれぞれ実行したサンプルが下記のようになります。// 同期処理 print("a") DispatchQueue.global(qos: .background).sync { print("b") } print("c") // 結果 // a // b // c // 非同期処理 print("a") DispatchQueue.global(qos: .background).async { print("b") } print("c") // 結果 // a // c // bこれらは、キューの種類の説明でも出てきた
直列
・並列
の言葉と混合しがちなので、スレッド間の実行順序に使われるのが同期
・非同期
で、スレッド内の処理の実行順序に使用されるのが直列
・並列
という認識で差し支えないかと思われます。参考
- https://developer.apple.com/documentation/dispatch/dispatchqos
- https://developer.apple.com/documentation/dispatch/dispatchqueue
- https://developer.apple.com/videos/play/wwdc2016/720/
- https://qiita.com/ShoichiKuraoka/items/9599c32bc27c3c822f02
- https://qiita.com/ShoichiKuraoka/items/bb2a280688d29de3ff18
- https://qiita.com/shiz/items/693241f41344a9df6d6f
- 投稿日:2020-09-10T23:30:17+09:00
【Swift5】FSCalendarのカレンダーにイメージ画像を表示する
はじめに
カレンダー付きToDoアプリを制作する際にFSCalendarというライブラリを使用しましたので備忘録として投稿します。
初学者ですので訂正点ございましたら、ご指摘よろしくお願いします。概要
FSCalendarではカレンダーの日付の下に任意の条件でイメージ画像を表示することができます。
制作したアプリの用途に絡めると「ToDoの登録のステータスが完了済である日付にイメージ画像を表示」になります。今回はRealmSwiftを組み合わせての実装になりますのでRealmSwiftに関しましてはこちら参照ください。
また、FSCalendarの導入に関しましてはこちらを参照ください。実行環境
【Xcode】 Version 11.7
【Swift】 version 5.2.4
【CocoaPods】version 1.9.3
【RealmSwift】 version 5.3.2
【FSCalendar】version 2.8.1実装後の画面
実装コード
全体のコードになります。
サンプルコードではなく、自作アプリのコードになりますので関連箇所を抜粋しております。
また、FSCalendarのDelegateとDataSourceはstoryboard上で追加しております。Todo.swiftimport Foundation import RealmSwift class Todo: Object { # ・・・省略・・・ @objc dynamic var status: Bool = false # ・・・省略・・・ }MainViewController.swiftclass MainViewController: UIViewController { @IBOutlet weak var calendar: FSCalendar! # ・・・省略・・・ var datesWithStatus: Set<Bool> = [] var checkWithStatus: Set<Bool> = [true] # ・・・省略・・・ override func viewDidLoad() { super.viewDidLoad() # ・・・省略・・・ } } // UIImageのリサイズ extension UIImage { func resize(size _size: CGSize) -> UIImage? { let widthRatio = _size.width / size.width let heightRatio = _size.height / size.height let ratio = widthRatio < heightRatio ? widthRatio : heightRatio let resizedSize = CGSize(width: size.width * ratio, height: size.height * ratio) UIGraphicsBeginImageContextWithOptions(resizedSize, false, 0.0) // 変更 draw(in: CGRect(origin: .zero, size: resizedSize)) let resizedImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return resizedImage } } extension MainViewController: FSCalendarDelegate, FSCalendarDataSource, FSCalendarDelegateAppearance { // イメージ画像をつける func calendar(_ calendar: FSCalendar, imageFor date: Date) -> UIImage? { let formatter = DateFormatter() formatter.dateFormat = "yyyy/MM/dd" formatter.calendar = Calendar(identifier: .gregorian) formatter.timeZone = TimeZone.current formatter.locale = Locale.current let calendarDay = formatter.string(from: date) let perfect = UIImage(named: "perfect") let Resize:CGSize = CGSize.init(width: 18, height: 30) // サイズ指定 let perfectResize = perfect?.resize(size: Resize) // Realmオブジェクトの生成 let realm = try! Realm() // 参照(全データを取得) let todos = realm.objects(Todo.self).filter("dateString == '\(calendarDay)'") if todos.count > 0 { for i in 0..<todos.count { if i == 0 { datesWithStatus = [todos[i].status] } else { datesWithStatus.insert(todos[i].status) } } } else { datesWithStatus = [] } if datesWithStatus == checkWithStatus { return perfectResize } return nil } }実装方法
以下のメソッドを用いることでイメージ画像がつけられるようになります。
func calendar(_ calendar: FSCalendar, imageFor date: Date) -> UIImage? { return image }画面に表示されている月の日付(Date型)がループで投げられます。自動的にFor-in構文のようにdateの数だけこのメソッドが呼ばれるイメージです。
しかしこのまま用いても全ての日付の下にイメージ画像が付くようになるだけになります。
これをRealmSwiftと組み合わせることで、ToDoの登録のステータスが完了済である日付にイメージ画像を表示するようにしました。1.Todoのステータスを判別するデータ(status)を用意し、RealmSwiftに登録
- RealmSwiftに登録するTodoクラスに下記変数を用意します。
@objc dynamic var status: Bool = false2.RealmSwiftからデータを取得
- メソッドで呼ばれる日付(Date型)をRealmSwiftに保存している「dateString」と同じフォーマットに変換します。「dateString」はメソッドにより呼ばれた日付と照合させるために用意しました。詳しくはこちら参照ください。
formatter.dateFormat = "yyyy/MM/dd" let calendarDay = formatter.string(from: date)
- 変数「calendarDay」でフィルターをかけてRealmSwiftからデータ取得します。今回の用途は日付ごとに判別する必要があるため、filterメソッドで検索条件を指定します。
// Realmオブジェクトの生成 let realm = try! Realm() // 参照(全データを取得) let todos = realm.objects(Todo.self).filter("dateString == '\(calendarDay)'")3.UIImageをリサイズ
- 元の画像サイズだと大きかったので、リサイズしてカレンダーの日付の下に表示できるサイズに変更しました。
// UIImageのリサイズ extension UIImage { func resize(size _size: CGSize) -> UIImage? { let widthRatio = _size.width / size.width let heightRatio = _size.height / size.height let ratio = widthRatio < heightRatio ? widthRatio : heightRatio let resizedSize = CGSize(width: size.width * ratio, height: size.height * ratio) UIGraphicsBeginImageContextWithOptions(resizedSize, false, 0.0) // 変更 draw(in: CGRect(origin: .zero, size: resizedSize)) let resizedImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return resizedImage } }4.取得したデータの中から必要なデータ(status)をピックアップして変数に格納
- RealmSwiftに登録データがある場合は、変数「datesWithStatus」に「status」の内容を格納。登録データがない場合は、変数「datesWithStatus」には何も入れません。
if todos.count > 0 { for i in 0..<todos.count { if i == 0 { datesWithStatus = [todos[i].status] } else { datesWithStatus.insert(todos[i].status) } } } else { datesWithStatus = [] }
- 変数「datesWithStatus」は配列のSetクラス(集合型)になります。Arrayクラスと異なる箇所としましてはインデックス番号が存在せず、重複が許されない型ということ違いがあります。今回RealmSwiftから取得するデータがtrueかfalseの重複する必要がなかったためSetクラス(集合型)にしております。詳細はこちら参照ください。
var datesWithStatus: Set<Bool> = []5.「true」のみの場合、画像(UIImage)を返す
- 変数「datesWithStatus」がtrueのみの場合は画像(UIImage)を返し、そうでなければnilを返す。
if datesWithStatus == checkWithStatus { return perfectResize } return nil
- 変数「datesWithStatus」と比較する変数「checkWithStatus」の型は合わせましょう。
var checkWithStatus: Set<Bool> = [true]参考
- 投稿日:2020-09-10T22:05:31+09:00
Google Maps で吹き出しが表示されない
要件
Google Maps 上のマーカーをタップした際に吹き出しを表示してください。
Google さんの教え
それ info window でできるよ。
https://developers.google.com/maps/documentation/ios-sdk/marker#add_an_info_window
title
orsnippet
のどちらかを設定すれば、マーカーをタップした際に吹き出し( info window )が表示されるようです。
どちらもnull
もしくは空文字の場合は吹き出しは表示されません。let position = CLLocationCoordinate2D(latitude: 51.5, longitude: -0.127) let marker = GMSMarker(position: position) marker.title = "London" marker.snippet = "Population: 8,174,100" marker.map = mapViewやってみた
やってみました、表示されませんでした。
タップが無視されているような感じだったので
isUserInteractionEnabled
を一通り確認しましたが、全てtrue
でした。mapView: didTapMarker
怪しげな delegate メソッドが実装されていました。
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool { print(marker) return true }
true
を返す場合はこのメソッド内でタップイベントを処理し、false
を返す場合はイベントを流すようです。
吹き出しを表示する場合はタップイベントを流して、 GMSMarker のデフォルトのタップ時の処理( info window )に任せる必要がありそうです。ということで、
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool { print(marker) return false }やってみました、表示されませんでした。
ログを確認したところ
print(marker)
が出力されていませんでした。
ブレークポイントを貼ってみてもこのメソッドは呼び出されていないようです。
つまり info window の話以前にマーカーがタップイベントを取得できていないという事です。ちなみに
mapView.delegate = self
は適切に設定されています。
他の delegate メソッドが呼び出されることは確認しました。clusterManager: didTapCluster
怪しげな delegate メソッドが実装されていました。
func clusterManager(_ clusterManager: GMUClusterManager, didTap cluster: GMUCluster) -> Bool { print(cluster) return true }Cluster とは複数のマーカーがマップ上の近い位置に存在するときに、 1 箇所に纏めて 1 つのアイコンとして表示する機能です。
Google Maps SDK for iOS Utility Library という Google Maps SDK の機能を拡張するためのライブラリに含まれる機能です。ということで、
func clusterManager(_ clusterManager: GMUClusterManager, didTap cluster: GMUCluster) -> Bool { print(cluster) return false }やってみました、表示されました。
今回の犯人はこいつでした。
Cluster のアイコンがタップイベントを処理しており、 Marker にイベントが流れてこない状態となっていました。まなび
基本的には Google さんの教えに従えば吹き出しは表示されます。
今回はイレギュラーなケースでしたが、その際の着眼点を記事に残しておきたいと思いました。タップが無視されるという現象はアプリ開発をしていると割と頻繁に遭遇します。
これまでの経験では
isUserInteractionEnabled
- UIButton の
isEnabled
- フォーカスの位置
辺りを見ていると大方解決できていました。
delegate の処理でタップイベントを 流す / 流さない を管理するパターンは初めてだったので知見が広がりました。
- 投稿日:2020-09-10T21:50:18+09:00
勢いでMacbook買ってから初めてのiPhoneアプリをリリースするまでのメモ
はじめに
3月にMacbookProを購入し、独学でプログラミング(Swift)を学習し、8月に初めてのiOSアプリをリリースしました!
今回はアプリリリースまでにどうプログラミングを勉強したのか、どういう教材でアプリ開発を学んだのかを紹介します!リリースしたアプリ
思いっきり宣伝ですがこちらからダウンロードできます↓
https://apps.apple.com/jp/app/prophecy-%E4%BA%88%E8%A8%80%E3%83%9E%E3%82%B8%E3%83%83%E3%82%AF/id1528918453記念すべき初のアプリは【手品アプリ】です!
iPhoneでお客さんが選んだカードを当てる簡単なマジックです!
実演動画はYouTubeに載せています↓
https://www.youtube.com/watch?v=yabrcQwxoSYリリースに向けてSwiftの勉強
まず私のMacbook購入時点のプログラミングスキルですが
・プログラミングの基本文法はわかる。(変数、メソッド、条件分岐、繰り返しなど)
・Java、Javascriptを少し触れたことがある。
・Swiftは一度も触ったことがない。なので、完璧に初心者というわけではないですが、
Progate(無料のプログラミン技学習サイト)で基本文法を勉強したくらいのレベルだと思います。以下、私が学習する際に用いた教材を紹介します。
①絶対に挫折しない iPhoneアプリ開発「超」入門 第8版 【Xcode 11 & iOS 13】 完全対応
こちらの本ではiPhoneアプリの開発に必要なXcodeという開発ツールのインストール方法からSwiftの基本文法、いくつかのサンプルアプリの作成手順などが画像付きで載っており、初心者にはオススメです!
ちなみに私がこの本を買った当時は第7版だったので、本の中の画像が一部古かったりもしましたが、
2020年6月に最新の第8版が出たので、今時点では画像に差異はほとんどないかと思われます。私はこの本を1ヶ月ほどかけて読み終えましたが、本の内容で一部理解が曖昧だったところ(デリゲート、クロージャなど)があったため、別の教材を手に取りました。
②【iOS13対応】未経験者がiPhoneアプリ開発者になるための全て iOS Boot Camp
こちらは動画学習サイトUdemyの教材となります。
https://www.udemy.com/course/ios13_swift5_iphone_ios_boot_camp/こちらの動画では30個近くのサンプルアプリの作成手順が動画で解説されており、サンプルアプリ開発を通してSwiftの様々な機能について学べます。
Swiftの基本文法はもちろん、GoogleAdmobの広告の付け方までかなり幅広く解説されています。しかも、動画内でわからないところがあればコメントにて質問することができます。
動画時間は全て合計すると約30時間ほどあるので、毎日1〜2時間視聴したとしても1ヶ月ほどかかります。
私は普段仕事もあったので、週5日1〜2時間ほどのペースで進めており、1.5ヶ月ほどかかりました。
本である程度理解できているところは1.5倍速で見ていました。①の本で分からなかったところは動画で理解することができ、動画で分からなかったところは本を読み返して理解することができたので、教材は2つ以上あると勉強しやすいのかなと思いました。
その他
分からないところは随時検索したり、QiitaやYouTubeを見たりしていました。
特にAppleにアプリの申請を行うところなどはたくさん検索しました。
先人様の知恵に感謝です。いざオリジナルアプリ開発へ!
アプリの構想自体は②の動画教材を勉強しながらぼんやりと考え始めていました。
構想時点ではあれもこれもと色々機能をつけがちですが、
初心者にとって一番大切なのは【シンプルでもいいからとりあえず物を完成させる】ことです。
いきなり自動車を作るのではなく、まずは一輪車を作るようにした方がいいです。初めからあれもこれもと色々な機能を付けようとすると、思っている以上にうまく動作しなくなります。
そうなってくると開発のモチベーションが下がり挫折してしまいます。
私も最初は多機能アプリの構想を練っていたので、かなり大変でした。。。なので、今回は画面数も極力減らし、かなりシンプルな画面構成にしてみました。
結果的にバグもあまり発生せず、かなりスムーズに開発を進めることができました!
おわりに
今回はアプリリリースまでにどうプログラミングを勉強したのか、どういう教材でアプリ開発を学んだのかを紹介してみました!
リリースしたアプリは正直かなりシンプル構造です。誰でも作れますレベルです。が、やはり自分で作ったアプリがAppStoreに表示されるともうそれだけで嬉しくなります…!!!
これからiPhoneアプリを作ってみたいという方にぜひ参考になればと思います!
- 投稿日:2020-09-10T19:57:03+09:00
アプリで取り扱っている画像(UIImage)を端末に保存する
UIImageViewに表示したりするのに使っている画像(UIImage)を端末のアルバムに保存したかったのでやり方を調べました。
まず、Info.plistの
<dict>
の中にNSPhotoLibraryAddUsageDescription
があるか確認します。なかったら以下のように追加します。パーミッション要求時の文言は、アプリの内容に応じて指定します。<key>NSPhotoLibraryAddUsageDescription</key> <string>パーミッション要求時の文言</string>そしたら、画像を保存しようとしているところで以下のような処理を実行します。
func save(image: UIImage) { // パーミッションを確認 PHPhotoLibrary.requestAuthorization { status in guard status == .authorized else { // 権限がない return } // 保存処理 PHPhotoLibrary.shared().performChanges( { // このブロック内でPHAssetCreationRequestを作成すると保存される PHAssetCreationRequest.creationRequestForAsset(from: image) }, completionHandler: { success, error in if let error = error { // エラー処理 return } else if success { // 保存完了 } else { // ここに来ることがあるのかどうか、ちょっと調べた限りわからない… } }) } }
- 投稿日:2020-09-10T16:24:07+09:00
swiftメモ(自分用)
自分用メモ
・tableViewでのreturn cell で、検索数以上に表示数を設定すると
ループして表示されるので、return cell では表示数をカウントしたものを設定する。
・Label内のフォントサイズを自動調節する。
【Xcode】UILabelの文字サイズを自動調整する
https://an.hatenablog.jp/entry/2017/12/03/234839
tableViewのクラッシュは解決したので、別記事にしました。
- 投稿日:2020-09-10T14:26:04+09:00
Swiftでデザインパターン【Observer】
元ネタ → [https://github.com/ochococo/Design-Patterns-In-Swift#-observer)
クラス図
図の引用元:Wikipedia: Strategy パターン
概要
The observer pattern is used to allow an object to publish changes to its state. Other objects subscribe to be immediately notified of any changes.
Observerパターンは、オブジェクト(Subject
)がその状態の変更を公開できるようにするのに使用される。他のオブジェクト(Observer
)は、変更があるとすぐに通知を受けるように購読する(Subject
がObserver
のインスタンスを保持する)。サンプルコード
// Observer protocol PropertyObserver : class { // notify() func willChange(propertyName: String, newPropertyValue: Any?) // notify() func didChange(propertyName: String, oldPropertyValue: Any?) } // Subject final class TestChambers { // 集約 weak var observer: PropertyObserver? private let testChamberNumberName = "testChamberNumber" // notifyOserver() var testChamberNumber: Int = 0 { willSet(newValue) { observer?.willChange(propertyName: testChamberNumberName, newPropertyValue: newValue) } didSet { observer?.didChange(propertyName: testChamberNumberName, oldPropertyValue: oldValue) } } } // ConcreteObserver final class Observer : PropertyObserver { // notify() func willChange(propertyName: String, newPropertyValue: Any?) { if newPropertyValue as? Int == 1 { print("Okay. Look. We both said a lot of things that you're going to regret.") } } // notify() func didChange(propertyName: String, oldPropertyValue: Any?) { if oldPropertyValue as? Int == 0 { print("Sorry about the mess. I've really let the place go since you killed me.") } } } // usage // var observerInstance = Observer() var testChambers = TestChambers() // addObserver() testChambers.observer = observerInstance testChambers.testChamberNumber += 1クラス図との対応
サンプルコード クラス図 PropertyObserver Observer willChange, didChange notify
サンプルコード クラス図 TestChambers Subject observer 集約(aggregate) willSet, didSet notifyOserver
サンプルコード クラス図 Observer ConcreteObserver willChange, didChange notify 考察
Observer = Subscriber(購読)/ Subject = Publisher(発行)
TestChambers
がPropertyObserver
型(Observer
をアップキャスト)のインスタンスを持つ(集約)。
TestChambers
はストアドプロパティにより値の状態を監視し、変化があるとPropertyObserver
インスタンスのwillChange/Didchange
を呼ぶ(ポリモーフィズム)。こうしてTestChambers
は値の変化があった場合、PropertyObserver
インスタンスに通知する。
- 投稿日:2020-09-10T09:09:03+09:00
[Swift] paiza スキルチェック見本問題セット 解答
はじめに
paizaスキルチェック見本問題をSwiftで解いてみました。
各問題のタイトルをクリックすると問題ページに飛ぶことができます。
実際のスキルチェックの難易度より全体的に簡単な問題が多いという印象でした。特にSランクの問題は実際よりもかなり簡単に感じました。
私はこの夏休みに競技プログラミングをかじり始めた新参者です。今回、初めてのブログを作成してみたくて書いてみました。
わかりにくいところ、改善点などあればご指摘ください。Dランク 掛け算
ポイント: 改行区切りで入力された数値の読み取り
Swiftで標準入力から一行の文字列を受け取る際には以下のように書きます。「!」をつけることを忘れないようにしましょう。let input_line = readLine()!しかし文字列のままでは扱いにくいのでInt型に型変換することを考えましょう。Int型に変換するためには以下のように書きます。
let input_line = readLine()! let a = Int(input_line)!一行にまとめて書く事もできます。
let a = Int(readLine()!)!では今回の問題の場合はどのように書けばいいでしょうか?
答えは簡単で同じことをもう一度書けばいいのです。つまり...let a = Int(readLine()!)! let b = Int(readLine()!)!とすることで、a、bに値を代入することができました。あとはa×bの答えを標準出力するだけですね!
解答
let a = Int(readLine()!)! let b = Int(readLine()!)! print(a*b)Dランク 足し算
ポイント: 空白区切りで入力された数値の読み取り
空白区切りで入力された値の読み取りは、一つ前の改行区切りで入力された値と同じように読み取ろうとしてもうまくいきません。
そこで、一行を文字列として読み取ったあとに空白で区切られた部分で分割して文字列の配列を作るという操作を行います。実際に書いてみましょう。let input_line = readLine()! let strings = input_line.split(separator: " ")stringsには文字列の配列が格納されています。それでは格納された文字列をInt型に直してみましょう。
今回は2つの値が空白区切りで入力されることがわかっているのでstrings[0]とstrings[1]に格納された値をそれぞれaとbとします。let strings = readLine()!.split(separator: " ") let a = Int(strings[0])! let b = Int(strings[1])!先ほどと同じように、1行にまとめて書くこともできます。
あとはa+bの答えを出力するだけですね。解答
let strings = readLine()!.split(separator: " ") let a = Int(strings[0])! let b = Int(strings[1])! print(a+b)Dランク 一番小さな値
ポイント: letとvarの違い、for文、if文
与えられる5つの数どれよりも大きいことが保証される数(この問題では101以上)の値をansに格納しておき、ansより小さな値が入力されるたびにansの値を更新していくと考えましょう。
この時、ansの値は変化するのでletではなく、varを使うことに注意してください。
swiftではfor文を次のように書きます。以下は0以上5 未満の数を出力するコードです。for i in 0..<5 { print(i) }ちなみに0以上5 以下の値を出力するコードは次のように書きます。
for i in 0...5 { print(i) }解答
var ans = 101 for _ in 0..<5 { let a = Int(readLine()!)! if(a < ans) { ans = a } } print(ans)Dランク 文字の一致
ポイント: 文字列の比較
swiftの文字列の比較は簡単です。解答
let a = readLine()! let b = readLine()! if(a == b) { print("OK") } else { print("NG") }Dランク 数の並び替え
ポイント: ソート、配列に数値を代入
改行区切りで入力されたn個の値を配列に格納することを考えます。今回は空の配列aを用意して、aに数値を足していくと考えましょう。let n = Int(readLine()!)! var a: [Int] = [] for _ in 0..<n { a.append(Int(readLine()!)!) }配列に数値を格納することができました!!それでは、aをソートして出力しましょう。
解答
let n = Int(readLine()!)! var a: [Int] = [] for _ in 0..<n { a.append(Int(readLine()!)!) } a.sort() for i in 0..<n { print(a[i]) }Cランク Fizz Buzz
ポイント: for文、if文
1以上n以下とするところに注意してください。解答
let n = Int(readLine()!)! for i in 1...n { if(i % 15 == 0) { print("Fizz Buzz") } else if(i % 3 == 0) { print("Fizz") } else if(i % 5 == 0) { print("Buzz") } else { print(i) } }Bランク 長テーブルのうなぎ屋
ポイント: for文、if文、Array
うなぎ食べたい。うなぎというより回転寿司だと思うのは私だけ?
座席が埋まっているかどうかを表す配列seatを始めに用意します。
m個のグループについて座ることができるかどうかを判定し、座ることができたらansの値を+1していきます。最終的にansの値が答えとなります。
それでは、i番目のグループが座ることができるかどうかはどのように判定するのでしょうか?
i番目のグループが座りたい座席番号は b[i] 以上 a[i] + b[i] 未満の位置であることがわかります。この中に1席でも埋まっている席があったらcanSitをfalseにするとしましょう。
ここで、座席の位置jがnとなった場合には座席番号0が、n+1の場合には座席番号1...と対応していきます。よって、座席の位置jをnで割ったあまりの位置について調べればいいということがわかります。var canSit = true for j in b[i]..<a[i]+b[i] { if(seat[j%n] == true) { canSit = false } }canSitがfalseの場合にはお客さんを逃した、trueの場合にはお客さんが入店したとわかります。
お客さんが入店した場合には、b[i] 以上 a[i] + b[i] 未満の座席が埋まったのでseatの値を更新しましょう。var canSit = true for j in b[i]..<a[i]+b[i] { if(seat[j%n] == true) { canSit = false } } if(canSit) { ans += a[i] for j in b[i]..<a[i]+b[i] { seat[j%n] = true } }それでは解答です。
解答
let input_line = readLine()! let split = input_line.split(separator: " ") let n = Int(split[0])! let m = Int(split[1])! var a: [Int] = [], b: [Int] = [] for _ in 0..<m { let input_line_s = readLine()! let s = input_line_s.split(separator: " ") a.append(Int(s[0])!) b.append(Int(s[1])!) } var seat: [Bool] = Array(repeating: false, count: n) var ans = 0 for i in 0..<m { var canSit = true for j in b[i]..<a[i]+b[i] { if(seat[j%n] == true) { canSit = false } } if(canSit) { ans += a[i] for j in b[i]..<a[i]+b[i] { seat[j%n] = true } } }Aランク じゃんけんの手の出し方
ポイント: for文、5*P + 2*C == m
Sランクまで含めて一番むずかしいと思います!複雑な問題になればなるほど落ち着いて考えましょう。
グー、チョキ、パーの出す順番は関係がありません。そこで、相手が出した手に対して、自分が勝てる手の数の合計をそれぞれg、c、pに格納しましょう。
例えば、相手がパーを5回出したら、自分はチョキを5回出すことで5回勝利することができるので、c=5となります。
自分が出した手を計算していきます。自分が出したての本数がm本となるためには
$$(パーを出した回数) × 5 + (チョキを出した回数) × 2 = m$$
となっていればよいです。そこで、パーを出した回数Pとチョキを出した回数Cをfor文で全探索して指の本数の合計がm本となったときだけ、勝負に勝った回数を計算しましょう。
なお、自分が出した手を大文字のG,C,P(グー、チョキ、パー)でそれぞれ表します。この時
$$ G = n-P-C $$
が成り立ちます。for P in 0...m/5 { for C in 0...m-P*5/2 { if(5*P + 2*C == m && n-P-C >= 0) { // 勝負に勝った回数の計算をここで行う } } }勝負に勝った回数はどのように計算すればいいでしょうか?
ちょっと混乱してきました??そんなときは例を用いて考えましょう。自分がチョキを出して勝てた回数を考えます。
相手がパーを出した回数を3回、自分がチョキを出す回数が5回だとします。
この時、 c=3, C=5 です。
自分がチョキを5回出したとしても勝利した回数は、相手がパーを出した3回のみなので勝利回数は3回です。逆に、自分がチョキを出した回数のほうが多い場合を考えてみましょう。
相手がパーを出した回数を5回、自分がチョキを出す回数が3回だとします。
この時、 c=5, C=3 です。
相手はパーを5回出してくれたので、チョキを5回出せば勝利回数は5回となります。しかしチョキは3回しか出さなかったので勝利回数は3回です。以上より、自分がチョキを出して勝てた回数はcとCの最小値であるとわかります。
ansを答えの値とすると、ansの値をできるだけ大きくしたいので、以下のように書くことができます。for P in 0...m/5 { for C in 0...m-P*5/2 { if(5*P + 2*C == m && n-P-C >= 0) { ans = max(ans, min(p, P) + min(c, C) + min(g, n-P-C)) } } }解答です。
解答
let input_line = readLine()! let split = input_line.split(separator: " ") let n = Int(split[0])! let m = Int(split[1])! var ans = 0 let string = readLine()! var c=0, g=0, p=0 for i in string { if(i == "C") { g += 1 } else if(i == "G") { p += 1 } else if(i == "P") { c += 1 } } for P in 0...m/5 { for C in 0...m-P*5/2 { if(5*P + 2*C == m && n-P-C >= 0) { ans = max(ans, min(p, P) + min(c, C) + min(g, n-P-C)) } } } print(ans)別解
はじめはこっちでやりました。解答のほうが鮮やかかな?
別解ははじめに
(パーを出した回数) × 5 + (チョキを出した回数) × 2 = m
となる組み合わせを一つ見つけてきて、そこから数値をずらしていって計算しています。let input_line = readLine()! let split = input_line.split(separator: " ") let n = Int(split[0])! let m = Int(split[1])! var ans = 0 let string = readLine()! var c=0, g=0, p=0 for i in string { if(i == "C") { g += 1 } else if(i == "G") { p += 1 } else if(i == "P") { c += 1 } } var P = m / 5 while((m - P*5) % 2 != 0) { P -= 1 } var C = (m - P*5) / 2 while(true) { let G = n - P - C if(G < 0 || P < 0 || C < 0) {break} ans = max(ans, min(p, P) + min(c, C) + min(g, G)) P -= 2 C += 5 } print(ans)Sランク mod7占い
ポイント: for文、mod
異なる3枚の組み合わせすべてを計算しても時間内に終えることはできないでしょう。そこで、カードに書かれた整数の値を7で割ったあまりを計算し、それぞれの個数を配列aに格納します。let n = Int(readLine()!)! var a = Array(repeating: 0, count: 7) var ans = 0 for _ in 0..<n { let A = Int(readLine()!)! a[A%7] += 1 }a[i]は7で割ったあまりがiとなるカードの枚数を表します。
あとは、0以上7未満の3つの整数の和が7の倍数となるすべての組み合わせについて、起こりうる場合の数を計算すればいいとわかります。
3つの整数の組み合わせをi, j, kをおいた時、同じ値がある場合には場合分けが発生することに注意してください。
パターン1 ( i == j == k)_{a[i]}C_{3}パターン2 ( i == j, j != k)
_{a[i]}C_{2} × a[k]パターン3 ( i != j, j == k)
a[i] × _{a[j]}C_{2}パターン4 ( i != j, j != k)
a[i] × a[j] × a[k]解答
let n = Int(readLine()!)! var a = Array(repeating: 0, count: 7) var ans = 0 for _ in 0..<n { let A = Int(readLine()!)! a[A%7] += 1 } for i in 0..<7 { for j in i..<7 { for k in j..<7 { if((i+j+k) % 7 == 0) { if(i == j && j == k && a[i] >= 3) { ans += a[i] * (a[i]-1) * (a[i]-2) / 6 } else if (i == j && j != k && a[i] >= 2) { ans += a[i] * (a[i]-1) / 2 * a[k] } else if (i != j && j == k && a[j] >= 2) { ans += a[i] * a[j] * (a[j]-1) / 2 } else if (i != j && j != k) { ans += a[i] * a[j] * a[k] } } } } } print(ans)Sランク 島探し
ポイント: 再帰関数、Queue、幅優先探索、構造体
有名問題です。解法は他にもっと詳しく説明したサイトがあるので省略します。
...さて、有名問題と言いながらこの問題は一般的な深さ優先探索では満点をとることができません。試しに次のコードを提出してみてください。let input_line = readLine()! let split = input_line.split(separator: " ") let w = Int(split[0])! let h = Int(split[1])! var mp: [[Int]] = Array(repeating: Array(repeating: 0, count: h), count: w) for i in 0..<h { let input_line_mp = readLine()! let split_mp = input_line_mp.split(separator: " ") for j in 0..<w { mp[j][i] = Int(split_mp[j])! } } var ans = 0 let dx = [-1,1,0,0] let dy = [0,0,-1,1] func search (x:Int, y:Int) { for i in 0..<4 { let nx = x + dx[i] let ny = y + dy[i] if(nx >= 0 && nx < w && ny >= 0 && ny < h && mp[nx][ny] == 1) { mp[nx][ny] = 0 search(x: nx, y: ny) } } } for y in 0..<h { for x in 0..<w { if(mp[x][y] == 1) { ans += 1 search(x: x, y: y) } } } print(ans)なぜ満点を取ることができないのでしょうか?原因は再帰関数の実行回数が増えすぎてしまったことです。(これに気づくのに時間かかってしまった...)
では再帰関数を使わずに問題を解いてみましょう。このまま深さ優先探索で解くためにはStackを使いますが今回は別の方法を取ります。幅優先探索を用いて解いてみましょう。そのためにはQueueを使うので、構造体でQueueを自作しました。
ここまで来たら、定石通り?に解答するだけです!!(Queue自作している時点で定石とは呼べない気がする...)解答
import Foundation struct Point { var x: Int var y: Int } struct Queue { var que: [Point], head: Int, tail: Int, MAX: Int init (size: Int) { MAX = size que = Array(repeating: Point(x:-1,y:-1), count: MAX) head = 0 tail = 0 } mutating func push(_ point: Point) { if(is_full()) { print("This queue is full") exit(1) } que[tail] = point tail += 1 if(tail == MAX) { tail %= MAX } } mutating func pop() { if(is_null()) { print("This queue is null") exit(1) } head += 1 if(head+1 == MAX) { head %= MAX } } func front() -> Point { return que[head] } func is_null() -> Bool { return tail == head } func is_full() -> Bool { return head == (tail + 1) % MAX } } let input_mp_size = readLine()!.split(separator: " ") let w = Int(input_mp_size[0])! let h = Int(input_mp_size[1])! var mp = Array(repeating: Array(repeating: 0, count: h), count: w) for i in 0..<h { let input = readLine()! let s = input.split(separator: " ") for j in 0..<w { let a = Int(s[j])! mp[j][i] = a } } var que = Queue(size: 1000001) var ans = 0 let dx = [-1,1,0,0] let dy = [0,0,-1,1] func search (x:Int, y:Int) { que.push(Point(x: x, y: y)) mp[x][y] = 0 while(!que.is_null()) { let point = que.front() que.pop() mp[x][y] = 0 for i in 0..<4 { let nx = point.x + dx[i] let ny = point.y + dy[i] if (0 <= nx && nx < w && 0 <= ny && ny < h && mp[nx][ny] == 1) { que.push(Point(x: nx, y: ny)) mp[nx][ny] = 0 } } } } for y in 0..<h { for x in 0..<w { if(mp[x][y] == 1) { ans += 1 search(x: x, y: y) } } } print(ans)まとめ
Aランク じゃんけんの手の出し方がやっぱりいちばん難しかったでしょうか?
Sランク 島探しもQueueの自作などしていたため時間がかかってしまいました。