東京ガスの
の「電力量料金」(電力を使用した分の電気代)を計算できる Web ツールを作った。
- 基本料金、燃料費調整額、再エネ促進賦課金、ガス・電気セット割については計算に含まれていません。
- ※計算結果が正しいことは保証しませんので、ご注意ください。
「段階の区切りになる使用量」、「段階ごとの 1kWh単位の料金」を変更すると、東京ガス以外にも使用できると思います。
periphery を使用すると Swift プロジェクトの中で未使用なコードを特定できる。
(未使用なクラス・プロパティ、不要な public
がわかる)
インストールして、コマンドのオプションを調べたのでメモする。
brew 経由だと
brew install peripher
periphery scan --help
実行結果。オプションが羅列される。
% periphery help scan OVERVIEW: Scan for unused code USAGE: periphery scan [<options>] [<build-arguments> ...] ARGUMENTS: <build-arguments> Arguments following '--' will be passed to the underlying build tool, which is either 'swift build' or 'xcodebuild' depending on your project OPTIONS: --setup Enable guided setup --config <config> Path to configuration file. By default Periphery will look for .periphery.yml in the current directory --workspace <workspace> Path to your project's .xcworkspace. Xcode projects only --project <project> Path to your project's .xcodeproj - supply this option if your project doesn't have an .xcworkspace. Xcode projects only --schemes <schemes> Comma-separated list of schemes that must be built in order to produce the targets passed to the --targets option. Xcode projects only (default: []) --targets <targets> Comma-separated list of target names to scan. Required for Xcode projects. Optional for Swift Package Manager projects, default behavior is to scan all targets defined in Package.swift (default: []) --format <format> Output format (allowed: xcode, csv, json, checkstyle) (default: xcode) --index-exclude <index-exclude> Path glob of source files which should be excluded from indexing. Declarations and references within these files will not be considered during analysis. Multiple globs may be delimited by a pipe (default: []) --report-exclude <report-exclude> Path glob of source files which should be excluded from the results. Note that this option is purely cosmetic, these files will still be indexed. Multiple globs may be delimited by a pipe (default: []) --index-store-path <index-store-path> Path to index store to use. Implies '--skip-build' --retain-public Retain all public declarations - you'll likely want to enable this if you're scanning a framework/library project --disable-redundant-public-analysis Disable identification of redundant public accessibility --retain-assign-only-properties Retain properties that are assigned, but never used --retain-assign-only-property-types <retain-assign-only-property-types> Comma-separated list of property types to retain if the property is assigned, but never read (default: []) --external-encodable-protocols <external-encodable-protocols> Comma-separated list of external protocols that inherit Encodable. Properties of types conforming to these protocols will be retained (default: []) --retain-objc-accessible Retain declarations that are exposed to Objective-C implicitly by inheriting NSObject classes, or explicitly with the @objc and @objcMembers attributes --retain-unused-protocol-func-params Retain unused protocol function parameters, even if the parameter is unused in all conforming functions --clean-build Clean existing build artifacts before building --skip-build Skip the project build step --strict Exit with non-zero status if any unused code is found --disable-update-check Disable checking for updates --verbose Enable verbose logging --quiet Only output results -h, --help Show help information.
対話形式でセットアップして分析する。
セットアップした結果は .periphery.yml
に構成が保存されるので次回以降はセットアップせずに yml から構成を指定して、分析できる。
.periphery.yml
のパスを指定する。
デフォルトはカレントディレクトリ。
Xcode プロジェクトへのパス。
ワークスペースがない場合は、こちらを指定する。
対象スキーム。
カンマ区切りで複数指定できる。
単体指定も可能。
対象ターゲット。
カンマ区切りで複数指定できる。
単体指定も可能。
出力フォーマット。以下が指定可能。
デフォルトは xcode
。
Xcode で Build Phase に追加して、Xcode のエディタ上で結果を表示したい時は xcode 指定にする。
表として、出力したい時は csv が便利。
インデックス作成から除外するパス。
出力される結果から除外するパス。
インデックス作成自体はされることに注意。
インデックスストアのパスを指定する。
この場合、プロジェクトのビルドをスキップする。
(--skip-build
と同様に)
public
された宣言はすべて外部から使用される想定にする。
フレームワークやライブラリのプロジェクトで、実際にそのインターフェースを使用していないプロジェクトの場合にこれを指定する。
冗長なパブリックアクセシビリティの識別を無効にする。
割り当てられているが使用されていないプロパティを保持する。
プロパティが割り当てられているが、読み取られない場合に保持するプロパティタイプのコンマ区切りリスト。
Encodableを継承する外部プロトコルのコンマ区切りリスト。 これらに準拠するタイプのプロパティプロトコルは保持されます。
NSObjectクラスを継承することによって暗黙的に、または@objc属性と@objcMembers属性を使用して明示的に、Objective-Cに公開される宣言を保持します。
パラメータがすべての準拠関数で使用されていない場合でも、未使用のプロトコル関数パラメータを保持します。
既存のビルドをクリアしてからビルドする。
誤った結果が出ている時は、インデックスストアが破損したり・ソースファイルと同期しなくなった可能性がある。
たとえば、スキャンを強制的に終了(^C
)した場合にこれが発生することがある。
この時は --clean-build
すると良い。
ビルドをスキップする。
未使用コードが見つかった場合は非ゼロのステータスで終了する。
更新のチェックを無効にする。
冗長なログを出力。
これを指定すると、
[configuration:begin]
から始まる箇所に、分析の構成が表示されるので、それを .periphery.yml
に貼り付けると、構成を永続化できる。
結果のみを出力。
ヘルプを表示。
https://github.com/peripheryapp/periphery#continuous-integration
たとえばテストを実行した後に periphery を実行する時は
--skip-build
でビルドをスキップできる。
インデックスパスが非標準の場所にあるときは
--index-store-path
で指定する。
たとえば以下のスキームがあって
それぞれ以下のターゲットがあるとき
これらをそれぞれ分析して、結果を CSV に出力する時は以下のシェルになる。
array=(A B C) for i in "${array[@]}" do periphery scan --project test.xcodeproj --schemes ${i} --targets ${i} --format csv > ~/Desktop/test/periphery/${i}.csv done
GCVirtualController
は iOS 15 から使用できるソフトウェアゲームコントローラー。
これを使うことで、アプリで自作のゲームコントローラーを作成しなくてよい、というメリットがある。
GCVirtualController - Apple Developer
アプリを作る際に GCVirtualController を触ってみて、わかったことを以下にまとめる。
コントローラー作成の簡単なコードが以下になる。
import UIKit import GameController class ViewController: UIViewController { private var virtualController: GCVirtualController! override func viewDidLoad() { super.viewDidLoad() let config = GCVirtualController.Configuration() config.elements = [ GCInputDirectionPad, GCInputButtonA, GCInputButtonB, ] self.virtualController = GCVirtualController(configuration: config) self.virtualController.connect { error in if let error = error { print(error) } } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.virtualController.disconnect() } }
これを実行すると、このようにボタンが表示される。
使えるボタンの種類はここに書いてあり、 https://developer.apple.com/documentation/gamecontroller/gcvirtualcontroller/configuration/3752038-elements
以下が使えるボタンのようだ。
以下のようにボタンに valueChangedHandler
を設定することで、「ボタンが押された」を取得することができる。
if let dpad: GCControllerDirectionPad = self.virtualController.controller?.extendedGamepad?.dpad { dpad.valueChangedHandler = { (dpad: GCControllerDirectionPad, xValue: Float, yValue: Float) in if dpad.up.isPressed { print("↑") } if dpad.down.isPressed { print("↓") } if dpad.left.isPressed { print("←") } if dpad.right.isPressed { print("→") } } } if let buttonA: GCControllerButtonInput = self.virtualController.controller?.extendedGamepad?.buttonA { buttonA.valueChangedHandler = { (button: GCControllerButtonInput, value: Float, pressed: Bool) in if buttonA.isPressed { print("A") } } } if let buttonB: GCControllerButtonInput = self.virtualController.controller?.extendedGamepad?.buttonB { buttonB.valueChangedHandler = { (button: GCControllerButtonInput, value: Float, pressed: Bool) in if buttonB.isPressed { print("B") } } }
valueChangedHandler
は「ボタンが押された」だけでなく「ボタンが離された」タイミングでも呼ばれるため、どのボタンが離されたかも判断できる。
また valueChangedHandler
を使わずに Timer などで周定期で各ボタンの isPressed
を確認してボタン状態を得ることもできる。
(たとえば、「Nフレームボタンが押されたら」という処理がしたい場合は、周定期でボタン状態を監視することになる)
ボタンの要素は ElemntConfiguration で変更できる。
これを見ると
UIBezierPath
で変更することはできる。ボタンの見た目の変更は、たとえば以下のコードで
self.virtualController.updateConfiguration(forElement: GCInputButtonA, configuration: { _ in let elementConfiguration: GCVirtualController.ElementConfiguration = GCVirtualController.ElementConfiguration() elementConfiguration.path = UIBezierPath() let bezierPath: UIBezierPath = UIBezierPath() bezierPath.move(to: CGPoint(x: 0, y: 0)) bezierPath.addLine(to: CGPoint(x: -10, y: -10)) bezierPath.addLine(to: CGPoint(x: 10, y: -10)) bezierPath.addLine(to: CGPoint(x: 10, y: 10)) bezierPath.addLine(to: CGPoint(x: -10, y: 10)) bezierPath.close() elementConfiguration.path?.append(bezierPath) return elementConfiguration })
このように A ボタンの見た目が、パックマンに変わる。
ElementConfiguration の isHidden
で非表示にすることができる。
たとえば、 A ボタンを押したら B ボタンを非表示にするコードは以下だ。
if let buttonA: GCControllerButtonInput = self.virtualController.controller?.extendedGamepad?.buttonA { buttonA.valueChangedHandler = { (button: GCControllerButtonInput, value: Float, pressed: Bool) in if buttonA.isPressed { self.virtualController.updateConfiguration(forElement: GCInputButtonB, configuration: { _ in let elementConfiguration: GCVirtualController.ElementConfiguration = GCVirtualController.ElementConfiguration() elementConfiguration.isHidden = true return elementConfiguration }) } } }
B ボタンが非表示になると見た目がこうなる。
ちなみに最初から B ボタンがない(GCVirtualController の init(configuration:)
の時点で B ボタンの要素を含めない)場合は、見た目がこうなる。
上の2つのボタン配置位置が違うことから
はそれぞれ配置が異なることがわかるので、注意したい。
(途中でボタンを非表示にしても、その分、配置が詰められる訳ではない)
もし、ボタンを非表示にして、かつボタン配置も最初からボタンがないものと同じにしたい場合は、GCVirtualController init(configuration:)
でバーチャルコントローラー自体を作り直せば良い。
↑のようにいろいろ GCVirtualController
を調べて作ったゲームがあったりしますよ。
SKStoreProductViewController を使うと、アプリから離脱せずに、AppStore で特定のアプリを表示することができる。これを使うと、アプリ内で他のアプリのインストールを促すことに使えたりする。
ただし、SKStoreProductViewController に delegate
を設定するとき、SwiftUI ではそのままでは使えないので、UIViewControllerRepresentable
を使ったパータンを考えてみる。(delegate を設定しない場合は、UIViewControllerRepresentable は不要で、そのまま使える)
まず、SKStoreProductViewController
で AppStore を開く機能を持った ViewController を用意する。
import UIKit import SwiftUI import StoreKit class StoreProductViewController: UIViewController { private var isPresentStoreProduct: Binding<Bool> private let appId: Int init(isPresentStoreProduct: Binding<Bool>, appId: Int) { self.isPresentStoreProduct = isPresentStoreProduct self.appId = appId super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) } func presentStoreProduct() { let storeProductViewController = SKStoreProductViewController() storeProductViewController.delegate = self let parameters = [SKStoreProductParameterITunesItemIdentifier: self.appId] storeProductViewController.loadProduct(withParameters: parameters) { status, error -> Void in if status { self.present(storeProductViewController, animated: true, completion: nil) } else { if let error = error { print("Error: \(error.localizedDescription)") } } } DispatchQueue.main.async { self.isPresentStoreProduct.wrappedValue = false } } } // MARK: - SKStoreProductViewControllerDelegate extension StoreProductViewController: SKStoreProductViewControllerDelegate { func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) { viewController.presentingViewController?.dismiss(animated: true, completion: nil) } }
上記の ViewController の UIViewControllerRepresentable
を用意する。
import SwiftUI struct StoreProductViewControllerRepresentable: UIViewControllerRepresentable { typealias UIViewControllerType = StoreProductViewController var isPresentStoreProduct: Binding<Bool> let appId: Int func makeUIViewController(context: Context) -> UIViewControllerType { let viewController = StoreProductViewController(isPresentStoreProduct: isPresentStoreProduct, appId: appId) return viewController } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { if self.isPresentStoreProduct.wrappedValue { uiViewController.presentStoreProduct() } } }
作成した StoreProductViewControllerRepresentable
を使った SwiftUI の View が以下になる。
import SwiftUI struct ContentView: View { @State private var isPresentStoreProduct: Bool = false var body: some View { VStack { Button { self.isPresentStoreProduct = true } label: { Text("App Store Link") } StoreProductViewControllerRepresentable(isPresentStoreProduct: self.$isPresentStoreProduct, appId: 284602850) .frame(width: 0, height: 0) } } }
処理の流れはこうなる。
SwiftUI, UIViewControllerRepresentable, UIViewController 間で、Bool のフラグ isPresentStoreProduct
を Binding しておいて
isPresentStoreProduct
を true にする。isPresentStoreProduct
が true になったら ViewController の AppStore を開くメソッドを実行する。isPresentStoreProduct
を false に戻す。
DispatchQueue.main.async
で false にする。動作させるとこんな感じになる。
ちなみに id 284602850 のアプリは、 Texas Hold’em である。
コードは GitHub にもあります。
https://github.com/daisuke-t-jp/SwiftUIPresentAppStoreProduct
たとえば
UIView
を key にして、Date
を value にした Dictionary
を使いたいと考えたとき。
ここで問題があるのは Dictionary の key, value は strong
で参照するため、 UIView を key にするとメモリリークが発生する。(本来の UView のライフサイクルが終了しても、解放されない)
この対応策が以下。
2つ方法を書いたが、他にもあると思う。
NSMapTable
は Dictionary と似ているが、key と value の参照を weak, strong からそれぞれ選ぶことができる。
https://developer.apple.com/documentation/foundation/nsmaptable
key を weak, value を strong にすると以下のコードになる。
let table: NSMapTable = NSMapTable<UIView, NSDate>(keyOptions: .weakMemory, valueOptions: .strongMemory) autoreleasepool { let view1 = UIView() table.setObject(Date() as NSDate, forKey: view1) } let view2 = UIView() table.setObject(Date() as NSDate, forKey: view2) let view3 = UIView() table.setObject(Date() as NSDate, forKey: view3) // 追加した要素は3個だが、うち1つは autoreleasepool ブロック内でライフサイクルが終わっている(=weak 参照の NSMapTable の要素も消える)ので、count は 2 となる。 print(table.count)
要素の数が限られていて良い。という場合は、NSCache
を使う方法も取れる。
その場合、UIView をそのままキーにするのではなく ObjectIdentifier
を key にする。
https://developer.apple.com/documentation/swift/objectidentifier
ObjectIdentifier を key にすることで、UIView の参照カウントを増やさずに、UIView を一意なキーとして Dictionary で扱える。
コードはこんな感じ。
let cache = NSCache<AnyObject, NSDate>() cache.countLimit = 2 let view1 = UIView() cache.setObject(Date() as NSDate, forKey: ObjectIdentifier(view1) as AnyObject) let view2 = UIView() cache.setObject(Date() as NSDate, forKey: ObjectIdentifier(view2) as AnyObject) let view3 = UIView() cache.setObject(Date() as NSDate, forKey: ObjectIdentifier(view3) as AnyObject) // キャッシュ最大個数(2)を超えているので、一番最初に追加した要素はなくなっている print("view1 -> \(cache.object(forKey: ObjectIdentifier(view1) as AnyObject))") // view1 -> nil print("view2 -> \(cache.object(forKey: ObjectIdentifier(view2) as AnyObject))") // view2 -> Optional(2022-01-09 06:12:40 +0000) print("view3 -> \(cache.object(forKey: ObjectIdentifier(view3) as AnyObject))") // view3 -> Optional(2022-01-09 06:12:40 +0000)
Travis CI を使って GitHub のプロジェクトに push した時に、自動でテストが実行されるようになったいたのを GitHub Actions に変えてみた。
GitHub actions の yaml の書き方や、Travis CI からの移行については以下のドキュメントが参考になる。
実施した手順のメモ。
1. プロジェクトのルートに .github
フォルダを作成
2. .github
フォルダ内に workflows
フォルダを作成
3. workflows
に yml ファイルを作成し、そこにテストの内容を記述していく。
たとえば、こんな感じ。
name: ci on: push: branches: - master pull_request: branches: - '*' jobs: testing_macos: runs-on: macos-11 steps: - uses: actions/checkout@v1 - name: "Xcode13 iOS" run: xcodebuild clean test -project Test.xcodeproj -scheme Test-iOS -sdk iphonesimulator -destination "platform=iOS Simulator,OS=15.0,name=iPhone 8" -configuration Debug
4. ブランチを新たに作り、今までの作業内容をコミットして、プルリクエストする。
on: push: branches: - master pull_request: branches: - '*'
この部分の記述で pull request した時も GitHub アクションが実施されるようにしているので、このプルリクエストのタイミングで実際にテストが開始されるのを確認できる。
5.プルリクエストのテストが成功したら、main ブランチにマージする。
GitHub のページから UI でワークフロー・GitHub アクションも作れるのだけど、上のような感じで UI を介さずに、作ることもできる。
実際 Travis CI と GitHub Actions だとどう yaml が変わったというと、、、
Travis CI の時(macOS 上で iOS, macOS, tvOS のテスト+Linux 上のテスト)
branches: only: - master matrix: include: - os: osx language: swift osx_image: xcode12 script: # iOS - xcodebuild clean -project "MurmurHash.xcodeproj" - xcodebuild test -project "MurmurHash.xcodeproj" -scheme "MurmurHash-iOS" -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 11 Pro Max" -configuration Debug # macOS - xcodebuild clean -project "MurmurHash.xcodeproj" - xcodebuild test -project "MurmurHash.xcodeproj" -scheme "MurmurHash-macOS" -destination "platform=OS X" -configuration Debug # tvOS - xcodebuild clean -project "MurmurHash.xcodeproj" - xcodebuild test -project "MurmurHash.xcodeproj" -scheme "MurmurHash-tvOS" -sdk appletvsimulator -destination "OS=12.2,name=Apple TV 4K" -configuration Debug - os: linux language: generic dist: xenial sudo: false addons: apt: packages: - wget # Ubuntu needs following packages to build Swift. # # Reference: # https://github.com/apple/swift#System-Requirements - git - cmake - ninja-build - clang - python - uuid-dev - libicu-dev - icu-devtools - libbsd-dev - libedit-dev - libxml2-dev - libsqlite3-dev - swig - libpython-dev - libncurses5-dev - pkg-config - libblocksruntime-dev - libcurl4-openssl-dev - systemtap-sdt-dev - tzdata - rsync script: - wget https://swift.org/builds/swift-5.3-release/ubuntu1604/swift-5.3-RELEASE/swift-5.3-RELEASE-ubuntu16.04.tar.gz - tar xvfz swift-5.3-RELEASE-ubuntu16.04.tar.gz - export PATH=$(pwd)/swift-5.3-RELEASE-ubuntu16.04/usr/bin:"${PATH}" - swift build - swift test
GitHub Actions の時(テストの内容は、Travis CI と同じ。Xcode や OS のバージョンは上げている)
name: ci on: push: branches: - master pull_request: branches: - '*' jobs: testing_macos: runs-on: macos-11 steps: - uses: actions/checkout@v1 - name: "Xcode13 iOS" run: xcodebuild clean test -project MurmurHash.xcodeproj -scheme MurmurHash-iOS -sdk iphonesimulator -destination "platform=iOS Simulator,OS=15.0,name=iPhone 8" -configuration Debug env: DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer - name: "Xcode13 macOS" run: xcodebuild clean test -project MurmurHash.xcodeproj -scheme MurmurHash-macOS -destination "platform=OS X" -configuration Debug env: DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer - name: "Xcode13 tvOS" run: xcodebuild clean test -project MurmurHash.xcodeproj -scheme MurmurHash-tvOS -sdk appletvsimulator -destination "platform=tvOS Simulator,OS=15.0,name=Apple TV" -configuration Debug env: DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer testing_linux: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v1 - name: "Linux" run: swift test
となった。
まあ、そこまで変わらない感じでかけた。(使っていないけど、GitHub actions でも matrix を書けるらしい)
あと、Travis CI の時は、Linux で Swift 実行環境を作るためにパッケージインストールをしていたのが、GitHub だと最初から入っているので、楽。
GitHub で使える OS 環境は以下を見た。
ちなみに GitHub アクションでも、ビルドステータスのバッチがつけれます。
スーパーのマルエツのキャンペーンでは、レシート3000円で1口応募できる。(レシート合算可) https://www.ichance.jp/cp/maruetsu-dreamwinter/
たとえば
以下のレシートがあった場合は
以下のようにまとめることで「2口応募」できる。
以下のレシートがあった場合はどうだろう。
これは、パッとわからない。
パッとわからないので、テキトーに組み合わせて出来た結果が「10口」だとする。
もうすこし組み合わせを頑張れば「11口」や「12口」になったりするかもしれない。
しかし、テキトーに手作業だとだいぶ疲れるし、無意味なような気がするので、以下のアルゴリズムを考えた。
計算1
計算1をN回繰り返し、出来たコップの数の記録を見る。
最も多い「コップの数」が、乱択の結果、確率的に分かった応募できる最大の口数。(=必ずしも正解とは言えない)
上のコードを swift で書いた。(Nは100万回)
import Foundation let passValue: Int = 3000 // 基準値 let tryMax: Int = 1000000 // 試行回数 // 入力 let inputValues: [Int] = [ 1676, 843, 1000, 233, 1731, 321, 179, 259, 460, 1594, 3544, 632, 2172, 2623, 1102, 84, 466, 306, 855, 3255, 1609, 1345, 1170, 4220, 6231, 180, 91, 2937, 544, 1577, 421, 3899, 585, 671, 1811, 875, 384, 950, 1618 ] // あらかじめ基準値以上のグループを作成する var passedGroups: [[Int]] = [] // すでに基準値以上のグループ var inputValues2: [Int] = [] for value in inputValues { if value >= passValue { passedGroups.append([value]) continue } inputValues2.append(value) } var groupNumMap: [Int: Int] = [:] // グループ数の出現頻度のマップ var bestGroups: [[Int]] = [] // 最適なグループ配列 var lastPercent: Int = 0 // print("- count \(inputValues2.count)") // print("- sum \(inputValues2.reduce(0, +))") // print("") // ランダムに基準値を超えるグループを作成する for i in 0..<tryMax { // 一時変数を準備する var tempGroups: [[Int]] = [] // 一時的なグループの配列 var tempValues: [Int] = inputValues2 // 一時的な値の配列 var tempGroup: [Int] = [] // 一時的なグループ // ランダムに基準値以上のグループの配列を作成する while true { // ランダムなインデックスを得る let index: Int = Int.random(in: 0..<tempValues.count) let value: Int = tempValues[index] tempValues.remove(at: index) // 一時的なグループに値を追加する tempGroup.append(value) let reduceValue: Int = tempGroup.reduce(0, +) if reduceValue >= passValue { // 一時的なグループの合計が基準値以上の場合 // -> グループを、一時的なグループ配列に追加する tempGroups.append(tempGroup) tempGroup = [] // 一時的なグループをクリア } if tempValues.count > 0 { continue } // 値がなくなったので試行終了 break } // グループ数の出現カウントを控える let groupsCount: Int = tempGroups.count + passedGroups.count groupNumMap[groupsCount] = (groupNumMap[groupsCount] ?? 0) + 1 if tempGroups.count > bestGroups.count { // 最適なグループ数を超えたら、最適なグループを更新する bestGroups = tempGroups } let percent: Int = Int((Float(i) / Float(tryMax)) * 100) if percent > lastPercent { lastPercent = percent print("\(percent)%") } } // 計算の結果得た最適なグループ配列に、あらかじめ作成したすでに基準値以上のグループを合成する bestGroups.insert(contentsOf: passedGroups, at: 0) print("\n\n") // 結果を出力 print("# Result\n") // 最適なグループ配列を出力 print("## Best groups") print("group numbers : \(bestGroups.count)") print("") for i in 0..<bestGroups.count { print("### Group\(i + 1)") for j in 0..<bestGroups[i].count { print("- \(bestGroups[i][j])") } print("") } // グループ数の出現頻度 print("## Frequency of group numbers\n") let sortedKeys: [Int] = groupNumMap.keys.sorted() for key in sortedKeys { print("### \(key)") print("- count \(groupNumMap[key] ?? 0)") let percent: Float = (Float(groupNumMap[key] ?? 0) / Float(tryMax)) * 100 print("- percent \(percent)%") }
これを実行すると、結果の出力は以下。
15口応募できるようだ。
全体のうち、口数の出現頻度が
となる。
# Result ## Best groups group numbers : 15 ### Group1 - 3544 ### Group2 - 3255 ### Group3 - 4220 ### Group4 - 6231 ### Group5 - 3899 ### Group6 - 2623 - 1102 ### Group7 - 306 - 855 - 259 - 1609 ### Group8 - 233 - 1594 - 544 - 180 - 632 ### Group9 - 1676 - 1345 ### Group10 - 466 - 2937 ### Group11 - 950 - 460 - 321 - 1731 ### Group12 - 1577 - 1000 - 585 ### Group13 - 84 - 1170 - 2172 ### Group14 - 91 - 1618 - 384 - 671 - 843 ### Group15 - 179 - 1811 - 421 - 875 ## Frequency of group numbers ### 12 - count 7584 - percent 0.7584% ### 13 - count 453582 - percent 45.3582% ### 14 - count 528201 - percent 52.8201% ### 15 - count 10633 - percent 1.0633%