マルエツのレシート応募キャンペーンの最大口数を知りたい
スーパーのマルエツのキャンペーンでは、レシート3000円で1口応募できる。(レシート合算可) https://www.ichance.jp/cp/maruetsu-dreamwinter/
たとえば
以下のレシートがあった場合は
- 600
- 1000
- 2000
- 2500
以下のようにまとめることで「2口応募」できる。
- 1口目 600, 2500
- 2口目 1000, 2000
以下のレシートがあった場合はどうだろう。
- 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
これは、パッとわからない。
パッとわからないので、テキトーに組み合わせて出来た結果が「10口」だとする。
もうすこし組み合わせを頑張れば「11口」や「12口」になったりするかもしれない。
しかし、テキトーに手作業だとだいぶ疲れるし、無意味なような気がするので、以下のアルゴリズムを考えた。
計算1
- レシートから1枚ランダムに選んで、テーブルに置く
- テーブルの上にあるレシートを見る
- レシートの合計が3000円以上なら、コップを用意してそこにテーブルの上のレシートを入れる。1. を実行する
- レシートの合計が3000円未満なら、1. を実行する
- と 2. をレシートを取り尽くすまで実行する。
- レシートを取り尽くしたら、出来たコップ(3000円以上のレシートが入ったコップ)の数を記録する
計算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口応募できるようだ。
全体のうち、口数の出現頻度が
- 12口が 0.7%
- 13口が 45%
- 14口が 52%
- 15口が 1%
となる。
# 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%
青森県のオープンデータを使った「公衆 Wi-Fi スポット」を探せる iOS アプリを作った
青森県のオープンデータは「青い森オープンデータカタログ」にある。
オープンデータで作成されたものはサイト内の「アプリマーケット」というところに登録できるが、みた感じスマホアプリがなく、
アプリマーケット - 青い森オープンデータカタログAoi Mori Open Data Catalog
「活用事例」を見ても個人でスマホアプリ作っている感じがなかったので、今回試しに作ってみた。
活用事例 - 青い森オープンデータカタログAoi Mori Open Data Catalog
アプリの元になったデータはこの「青森県公衆無線LANアクセスポイント一覧」
青森県公衆無線LANアクセスポイント一覧 - 青い森オープンデータカタログAoi Mori Open Data Catalog
データは CSV であるが、実際にはこれをそのままアプリで使うのではなく、アプリで使いやすいように Python で加工して、json ファイルにしたものをアプリで使用。
たとえば、まったく同じ内容の施設が重複していたりするので、それをひとつにしたりとか、ひとつの施設に複数の Wi-Fi スポットがあったりするのでまとめたりとか。。
そんな感じで作りましたよ。
あ、、青森県が出身地です。
今回 iOS 15 以降がターゲットのアプリにしたのだけど
iOS 15 だと属性付き文字列を NSAttributedString
ではなく新しい AttributedString
を使って作れるので、以前より直感的に作れる気がする。
たとえばこんな表示をしたければ、AttributedString
はこう作る。
var text1 = AttributedString("これは赤く小さい ") text1.foregroundColor = .red text1.font = .systemFont(ofSize: 16) var text2 = AttributedString("これは青く大きい ") text2.foregroundColor = .blue text2.font = .boldSystemFont(ofSize: 20) label.attributedText = NSAttributedString(text1 + text2)
iOS の Google AdMob のテスト広告が出なくなってしまった時の対応
ここに書いてある「デモ広告」のユニット ID を使って、テスト広告を表示させていたのだが、いつの間にか出なくなっていた。
実行時にこんなログが出る。
<Google> Cannot find an ad network adapter with the name(s): com.google.DummyAdapter. Remember to link all required ad network adapters and SDKs, and set -ObjC in the 'Other Linker Flags' setting of your build target.
ドキュメントをよく見たら
注意:アプリで app-ads.txt ファイルを設定している場合は、デモ広告ユニットを使って広告を読み込むために、次の行を app-ads.txt ファイルに含める必要があります。
と記載があったので、 app-ads.txt にコードを追加する。
... 既存の記載 ... google.com, pub-3940256099942544, DIRECT, f08c47fec0942fa0
そうして数時間経ったら、前のように「デモ広告」が表示されるようになった。 以前はドキュメントにデモ用の app-ads.txt の記載あったかなぁ・・・
ちなみに、↑とは別に「テストデバイス」設定をする方法でも、デモ広告は表示される。
SwiftUI で下から出てくる Picker を作ってみる
UIKit の場合だと UITextField の inputView に UIPicker を設定して、UITextField をタッチすると下からニュッと Picker が出てくる。ができる。
それを SwiftUI でやろうとすると、適当なものが用意されていなかったので、作ってみた。
こんな感じ。
コードは GitHub にあります。
Xcode プロジェクトにローカルの SwiftPackage を追加する
SwiftPackageManager で配布されているパッケージを、ローカルに持ってきてそれを Xcode プロジェクトで参照して使用するメモ。
- パッケージのリポジトリを clone する。
- 対象のアプリのプロジェクトを開く
- Xcode のプロジェクトツリーに、パッケージのフォルダ(
Package.swift
があるフォルダ)をドラッグ&ドロップする。- このスクリーンショットの状態になる。
- このスクリーンショットの状態になる。
- ターゲットの
Link Binary With Libraries
を開く- 追加の + ボタンをクリックする。
- 対象の Package を選び、追加する。
- 追加の + ボタンをクリックする。
import
できるようになっているので、ビルドする。
ローカルで参照したこの状態では Xcode 上でパッケージのコード編集してビルドすると、変更したパッケージの動作を試せるので、パッケージの挙動を試したり、修正することができる。
写真に写っている人・動物をヒエログリフにする iOS アプリを作ってみた
機械学習のフレームワークを使ったアプリを作ってみたくなり、試しに作ってみた。ついでに最近、エジプトのヒエログリフが面白いなあ、と思っていたのでそれをアプリのテーマにした。
Apple の review に提出する際に、かなり機能が単純なアプリだからリジェクトされるかと思ったが、大丈夫だった。
(審査にデモビデオは添付した)
アプリを使って変換すると、こんな感じで写真 → エジプトのヒエログリフの壁画風の画像になる。
アプリの動作はこんな感じ。
精度はそんなに良くなく、たとえば写真全体に人や動物が大きく写っていると、残念ながらうまくいかない。。
ざっくりとした処理の流れは↓の感じ。
- あらかじめ、使用するヒエログリフの画像を作成して用意しておく
- 写真から人や動物を認識する
- 認識した領域にもっとも特徴が近いヒエログリフを探し、該当するヒエログリフで写真を置き換える
- Vision(CoreML) VNFeaturePrintObservation で画像間の特徴の近さが分かる
やってみて悩ましかった点は
多数の画像(ヒエログリフ)から VNFeaturePrintObservation
を作ると時間がかかりすぎる。アプリの利用上の障害がある、という問題があった。
解決策としては
- 実行時に特徴検出を逐次作成するのではなく
- あらかじめ、各画像の
VNFeaturePrintObservation
を作成しておいてそれを CoreData で DB に保存しておく- そしてそれをアプリにバンドルしておく
- アプリの実行時には作っておいた DB から
VNFeaturePrintObservation
を fetch する
という形にした。
VNFeaturePrintObservation
の継承元の VNObservation
は NSSecureCoding
を採用しているので、CoreData の Attribute の型を Transformable
にして、下のようなカスタムの Transformer クラスを使用することで CoreData に保存できる。
@objc(VNFeaturePrintObservationTransformer) class VNFeaturePrintObservationTransformer: NSSecureUnarchiveFromDataTransformer { static let name = NSValueTransformerName(rawValue: String(describing: VNFeaturePrintObservationTransformer.self)) override class var allowedTopLevelClasses: [AnyClass] { return super.allowedTopLevelClasses + [VNFeaturePrintObservation.self] } public static func register() { let transformer = VNFeaturePrintObservationTransformer() ValueTransformer.setValueTransformer(transformer, forName: name) } }
Google ML Kit や Core ML はほとんど触ったことがなく難しい印象だったが、やってみると楽しかった。
iOS の Google MLKit で静止画像の ObjectDetection を試してみた
Google MLKit の ObjectDetection での静止画像の解析を試してみた。
公式のサンプルもあるが、バンドルされている静止画像しか解析できない。
そのため、写真を選んで解析する iOS サンプル を作ってみた。
動かすとこんな感じ。
試してみて分かったこと
- Firebase のセットアップをしなくても使える
- Google MLKit はもともとは Firebase MLKit という名前だったが
- Firebase ではなくなり、Firebase のセットアップ(
GoogleService-Info.plist
の追加など)をしなくても最低限使えるようになったため、とっつきやすい。
- 認識できるオブジェクトの最大数は 5 である
- TensorFlowLite と比べると
- ベースモデル以外にカスタムモデルも使用可能
- カスタマイズされたモデルを使うことも可能なので、拡張性もある。
- カスタマイズモデルの取り込み方法は、以下がある。
- ローカルファイルから読み込む
- Firebase でホスティングされているモデルを読み込む