電車の時刻になったら通知を飛ばすシステム Rubyでウェブスクレイピング編
お久しぶりです、かささぎです。
就活やら、テストやらで忙しく更新できない状態が続いてました。
まあなんやかんや無事(?)に終わらせることができたので、ブログの更新を再開します。
週1くらいで更新していきたいです。
「Rubyで何か作りてー」となり、ありがちではありますが、電車発車時間の数分前にwindowsに通知を行うシステムを作ることにしました。
作った過程をWriteUPしようとしたのですが、一度にまとめるととても長くなりそうなので、今回は前半です。
今回は電車の時刻表をWebから取ってくる過程をお伝えします。
ウェブスクレイピングとは
ウェブスクレイピング(英: Web scraping)とは、ウェブサイトから情報を抽出するコンピュータソフトウェア技術のこと。 - wikipeida
つまりそういうことです。
なおスクレイピングは著作権的にどうなんだという声があると思います。
調べたところ会員制サイトでなければあまり問題はなさそうです。
こちらの方の記事でわかりやすくまとめられています。
Webスクレイピングの法律周りの話をしよう!
まあ今回はだれでも閲覧可能なHPで、また取得するものは時刻表の時間という、数値のデータであり著作物ではないので大丈夫でしょう。問題があれば対応します。
スクレイピングツール
スクレイピングには、HTMLをパースして情報を取得できるスクレイピングツールを用います。
RubyではNokogiriというライブラリを使います。
以下のコマンドでNokogiriをインストールします。
gem install nokogiri
ちなみにスクレイピングにはこする、削るといった意味があるそうです。
Nokogiriというネーミングセンスいいですね、好きです。
HTML解析
私が利用している駅・路線である、津幡駅富山方面の時刻表をスクレイピングします。
始めはIRいしかわ鉄道のHPにしようかと思ったのですが、開発者ツールでHTMLを見ているとなかなか面倒くさそうな構文をしていたので、駅から時刻表さんのHPを利用させていただくことにしました。
NokogiriではHTMLタグを指定してその中身を取得する処理で情報を得るため、HTMLと格闘する必要があります。
この時点でスクレイピングするより手入力したほうが早いと気づいたのですが、まあそれはご愛敬で。
実装
時刻表をCSVファイルとして出力するプログラムです。
<ソースコード>
gist7ce5da620acdc7a28be48d2b47d2e8cd
解説します。
charsetには文字コードを格納し、データが文字化けしないようにしています。
xpathやcssはHTMLタグ内のデータを取得する関数です。
xpathは中身のHTMLタグごと、cssは中身の文字データのみを取得しているんだと思います。
正直詳しい文献が少なく、なんとなくな実行で動いてしまったので理解が追い付いていません。
xpath一つ目では時間、二つ目では分のデータを格納しています。
ここらへんはHTMLの構造によって違いますので、ホームページによってやり方を変える必要があります。
取得したtableの情報をCSVで出力して終了です。
<実行結果>
いやー田舎ですね。
各行の最初のデータが時間、それ以降が分の情報です。
あまり良いデータ構造ではないかもしれませんが、それは利用時にカバーします。
今回はここまでです。次回はこの情報をもとにWindowsに通知を飛ばします。
それでは。
Processingで手動でウィンドウサイズを変更する
こんばんは、かささぎです。エイプリルフールですが真面目に書いていきます。
今回は久しぶりにProcessingについて書きます。題材は描画ウインドウのサイズを手動で変更する処理についてです。
ブラウザやエクスプローラーなどで立ち上がる画面の端にマウスカーソルを合わせると矢印のマークに変わり、そのままドラッグするとウインドウサイズが変わります。
この機能はProcesingで立ち上がる画面ではデフォルトではできません。有効化する方法は簡単なのですが、今回はその機能について詳しく掘り下げていきます。
実装方法
この一文を加えるだけでできます。
surface.setResizable( true ); |
引数は変更できるかどうかを表しているので、trueにすることでできるようになります。falseにすると変更不可能になります。デフォルトはfalseです。
詳細の解説
surfaceというのはPSurfaceクラスのインスタンスです。
PsurfaceクラスはProcessing3で実装され、主にウインドウに関する処理をすることができます。
surfaceはProcessing側で用意されていますが、別途インスタンスを取得することもできます。
このように記述します。
PSurface psf = getSurface(); psf.setResizable( true );
変更前の状態を保持したい際などに利用します。
サンプルプログラム
今回は少し長いので、Gistにして貼ります。
gista2b2e67445f802386a4dc0d1357d7363
<実行結果>
ウインドウのサイズに応じて、描画するもののサイズを変更し、全体を表示できるようにしたプログラムです。
比率をScale関数に入れるだけなので、それ以外の部分を考慮せずにUIを作ることができるのが利点です。
例外処理について
このウインドウサイズ変更ですが、サイズを特定の大きさに変更しようとしたとき、環境によるかもしれませんがエラーが発生します。
height(高さ)を0以下にしようとドラッグすると、コマンドラインにこのように表示され動作が停止します。
java.lang.IllegalArgumentException: Width (500) and height (0) cannot be <= 0 at java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1016) …
これはたぶんProcessing側の問題であると思います。
解決するにはサンプルプログラムのようにIllegalArgumentExceptionを関数全体でthrowsしてやるか、プログラム全体にtry,catchの例外処理でIllegalArgumentExceptionをはじいてやる必要があります。
しかし、Javaとは違い完全なオブジェクト指向ができない(void setup()等をクラスにできない)Processingでは、わざわざこの処理をすべての範囲で記述するのは現実的ではありません。
まあウインドウの高さを負にする機会もないでしょうし、あまり気にする必要もないかもしれませんね。
なにか解決策がありましたら、教えてください。
それでは。
Atcoder Beginners SelectionをRubyで解いてみた
こんにちは、春の応用情報を受けるため参考書を買ったのですが開いてすらない、かささぎです。たぶん落ちる。。。
Atcoder で初心者が解いてみるべき問題を集めたAtcoder Beginners Selectionというコンテストができたみたいなので、解いてみました。
軽く調べたところ、drkenさんの記事 AtCoder に登録したら次にやること ~ これだけ解けば十分闘える!過去問精選 10 問 ~ で選ばれた問題を、Atcoderの実際のコンテストと同じ形式で解けるようです。
期限は4018年までです。
普段AtcoderではJavaを使って解いているのですが、難易度があまり高くないことから、勉強のためRubyですることにしました。
Rubyは1年ほど前に軽く勉強して以来ほとんど触っておりません。
それでは解いたものの解説をしていきます。
- PracticeA - はじめてのあっとこーだー(Welcome to AtCoder)
- ABC086A - Product
- ABC081A - Placing Marbles
- ABC081B - Shift only
- ABC087B - Coins
- ABC083B - Some Sums
- ABC088B - Card Game for Two
- ABC085B - Kagami Mochi
- ABC085C - Otoshidama
- ABC049C - 白昼夢 / Daydream
- ABC086C - Traveling
- 感想
PracticeA - はじめてのあっとこーだー(Welcome to AtCoder)
<ソースコード>
N1 = gets.to_i N2,N3 = gets.chomp.split.map(&:to_i) S = gets.chomp puts("#{N1+N2+N3} #{S}")
入力では、普段はJavaで書いているため、改行などをあまり気にすることなくScannerクラスのsc.nextを何も考えず使いまくっていたのですが、Rubyではそうもいきませんでした。
同じ行に空白区切りで複数のデータを取得する際はsplitが必要です。
出力はprintだと改行なし、putsだと改行ありです。変数の値は#{}内に記述することで出力できます。ちなみにRubyでのコメントアウトには#を用います。
ABC086A - Product
<ソースコード>
a,b = gets.split.map(&:to_i) puts (a*b)%2==0 ? "Even" : "Odd"
puts C ? "A" : "B" で、Cの条件がtrueならA、falseならBを出力という処理になります。
ちなみにRubyでは偶数かどうかを返すEven?メソッド、奇数かどうかを返すodd?メソッドもあります。
せっかくRubyの練習をしているので、そちらを使ったほうがよかったかもしれなかったですね。
ABC081A - Placing Marbles
<ソースコード>
s = gets puts s.count("1")
対称データの個数を数えて返してくれるcountメソッドを用いました。このような便利なメソッドがいろいろあるのはRubyの強みですね。
ABC081B - Shift only
<ソースコード>
N = gets.to_i s = gets.chomp.split.map(&:to_i) min = 99 N.times do |num| cnt = 0 while s[num]%2 == 0 do cnt += 1 s[num] /= 2 end if(cnt < min) then min = cnt end end puts min
ここから少し難しくなります。
それぞれの値を2で割れるだけ割って、それぞれの値で割れた回数の最低値を出力しています。
minの初期値は割り切れうる最大回数より大きな値にしておく必要があります。問題の条件より、
よって30以上であればどんな値でもいいです。今回は面倒くさかったので99にしておきました。
N.timesループは、0~N-1までN回繰り返されます。何回目のループなのかはイテレータ |変数名| で参照できます。
イテレータによる処理が高機能であるため、Rubyではインクリメント・デクリメントが用意されていないそうです。
そのためcnt += 1と記述しています。それを知らず最初はエラーを吐きました。
ABC087B - Coins
<ソースコード>
A = gets.to_i B = gets.to_i C = gets.to_i X = gets.to_i ans = 0 (A+1).times do |a| (B+1).times do |b| (C+1).times do |c| m = a*500 + b*100 + c*50; ans += 1 if m==X break if m>X end end end puts ans
3重ループを用いました。O(n^3)ですが、nの最大が50なので問題ありません。
Rubyでのif文は
if (条件) then 処理 end
ですが、処理が1つのみの場合は、
処理 if (条件)
と書くことができます。ただしJavaのように、
if (条件)
処理
のように書くことはできません。
ABC083B - Some Sums
<ソースコード>
N,A,B = gets.split.map(&:to_i) ans = 0 N.times do |num| val = num+1 cnt = 0 val.to_s.size.times do |v| cnt += val.to_s[v].to_i end if cnt >=A && cnt<=B then ans += val end end puts ans
こちらもNの最大値が10^4のため、2重ループ(O(n^2))を用いています。これが10^9とかだと、もう少し効率の良いアルゴリズムを考えなければなりません。
val.to_s[v].to_i という処理は、v文字目を取得するためにいったんString型に変換し、得たデータをint型にキャストしています。
ABC088B - Card Game for Two
<ソースコード>
N = gets.to_i A = gets.split.map(&:to_i) A.sort! {|a,b| b <=> a} ans = 0 A.size.times do |a| a%2==0 ? ans+=A[a] : ans-=A[a] end puts ans
最終的に求めるものは、Aliceの点数とBobの点数の差分ですから、Aliceを正、Bobを負として点数計算しています。
単純にAliceの総点とBobの総点を求めてから差分をとってもいいと思います。
.sort!で配列を昇順にソートします。元のデータを変更する際には!を付けます。
今回はデータを高い順、つまり降順にソートしたいです。
わからなかったのでggったのですが、A.sort! {|a,b| b <=> a} と書けばいいみたいですね。
両端のデータをイテレータを用いて取得していきながら、swapしているみたいです。
ABC085B - Kagami Mochi
<ソースコード>
N = gets.to_i D = [] N.times do D << gets.to_i end puts D.uniq.size
この問題では値の重複を除いた数を出力すればACです。
<<で、配列末尾に値を追加できます。
.uniqメソッドは配列の重複値を除いた配列を返してくれます。つまりそれのsizeを出力すればOKです。
ABC085C - Otoshidama
<ソースコード>
def ans(a,b,c) puts("#{a} #{b} #{c}") exit end X,Y = gets.split.map(&:to_i) ans = [] N = Y/1000 val = N-X (0..val/9).each do |v| if (val - v*9)%4==0 then y = v i = (val - y*9)/4 h = N - 10*y - 5*i ans(y,i,h) if(y+i+h == X && h>=0) end end ans(-1,-1,-1)
この問題は一度解いたことがあったので、その時とは別のアルゴリズムを考えました。
Yは1000の倍数だという条件を用いて、各お札の枚数をa,b,c とおくと次のようになります。
(1)-(2)より、
ここまでできれば、あとはループし、それぞれの合計枚数がXであるときに出力するだけです。
まあ5000円札の計算式を少し間違えてて1WA出してしまいましたが...
ちなみに、一度出力したらプログラムを終了するために関数を用いました。
Rubyでの関数は以下のように作れます。
def 関数名(引数の変数名) 処理 end
引数の変数名は大文字で始めてはいけないみたいです。
ABC049C - 白昼夢 / Daydream
<ソースコード>
s = gets.chomp s.gsub!('eraser','') s.gsub!('erase','') s.gsub!('dreamer','') s.gsub!('dream','') puts s.length==0 ? 'YES' : 'NO'
文字列から、4つの単語をすべて取り除いたときに、何か文字が残っていたらNO、なにもなかったらYESです。
気をつけなければならないのは取り除いていく順番です。
eraserよりerase、dreamerよりdreamを先に取り除くと、'eraserdreamer'などでWAです。
また、erase,eraserよりdreamerを先に取り除くと、'dreamerase'などでWAです。
従ってソースコードのような順にする必要があります。
gsub("A","B")メソッドでは、正規表現の"A"をすべて”B"に変換してくれます。
また、文字の取得時にchompメソッドを付け、改行コードを取り除く処理が必要です。
ABC086C - Traveling
<ソースコード>
N = gets.to_i turn = [] px = [] py = [] N.times do t,x,y = gets.split.map(&:to_i) turn << t px << x py << y end x=y=t=n=0 N.times do if(((x-px[n]).abs() + (y-py[n]).abs() > turn[n] - t )|| ((x-px[n]).abs() + (y-py[n]).abs() ) %2 != (turn[n] - t)%2) then break end x = px[n] y = py[n] t = turn[n] n = n+1 end puts (n == N) ? 'Yes' : 'No'
xの差分とyの差分の合計が、turnの差分より大きい場合はもちろん実行不可能です。
また、その場にとどまることができないという条件より、
xの差分とyの差分の偶奇とturnの差分の偶奇が違う場合も実行不可能です。
そのどちらかが現れた場合、ループをbreakします。
最終的に最後までたどり着いたかどうかでYesかNoを出力しています。
YesとNoをすべて大文字にしていて1WA出しました。
初歩的なミスですね。気を付けます。
SublimeText3のカラースキームを作った話
こんばんは、春の応用情報を受けるのに参考書を買ってすらない、かささぎです。たぶん落ちる。。。
機械学習を試してみたいと思い、tensorflowのサンプルを動かそうとしたのですが、動きませんでした。動いたらwriteUP仕様と思っていたのですが、残念な結果になったため、今回は全く別の話をします。chainerのほうがいいのかな...
皆さんはどんなエディタを使っていますかね。vimやatom、emacsにterapadなど、さまざまなエディタが存在しますが、僕はSublime Text3を利用しています。最初は「sublimeって崇高って意味か、かっけー」とか適当な理由で使い始めたのですが、今日では使い慣れて、とても重宝しています。前に使っていたgeditが使いづらかったのもあるかも。
そのSublimeTextで、自作したカラースキームを利用できると聞き、興味を持ったのでやってみました。今回はそれについてまとめます。
1. カラースキームの作成
まずはカラースキームを作成します。Sublime Text内のxmlファイルを直接編集して作成することも一応可能ですが、クソ面倒くさいので、カラースキームエディタを用います。
tmtheme editorというものを利用しました。
https://tmtheme-editor.herokuapp.com/#!/editor/theme/Monokai
こちらでは、変更をリアルタイムで確認しながら、カラースキームのデータを作成し、ファイルをダウンロードできます。sublimeTextのほかに、vscodeやtextmateなどでも利用できるそうです。
使い方を説明します。
左から二番目の"general"では、エディタ全体の色について調節できます。項目を説明すると、
- background 背景色
- caret カーソルの色
- foreground 通常の文字の色
- invisibles なんやこれ(分かりませんでした)
- lineHighLight カーソルがある行の左端の数字につく色
- selection 選択した位置につく色
という感じです。
一番左の"scopes"では、特定の文字につける色を調節できます。
例えば"String"をダブルクリックしてみると、
このように表示されます。
"scope"が範囲を示していて、今回は"String"、つまり、シングルクォーテーションやダブルクォーテーションで囲まれている文字、ということになります。
このscopeについては、SublimeTextの公式HPで解説されています。
https://www.sublimetext.com/docs/3/scope_naming.html
ですが、いちいち翻訳して、この色に決めてと繰り返すのはまあまあ大変です。
なので、左上にある"Gallery"から、自分の作りたいスキームと近いものを選んで、それを変更していく感じで作っていくのをおすすめします。
利用できるスタイルは、文字色だけでなく、背景色(通常は警告などに使われる)もあります。また、文字を太文字にしたり、イタリックにしたり、下線を引いたりすることもできます。
画面右側では、スタイルのプレビューを見ることかできます。右下のボタンではサンプルコードの言語を切り替えることができます。また、その隣の"Customize preview code"を押すと、自分で書いたコードでスタイルの確認をすることができます。
完成したら、緑色の"Save"と書かれたボタンを押し、隣の"Download"を押して、"tmTheme.txt"というファイルがダウンロードされれば成功です。
私は今回、このようにしてみました。
ごちうさカラーです。
ごちうさの画像を、ペイントのスポイトを利用して色データを取得しながら、地道に作りました。
2. Sublime Textへ反映
Webの参考ソースではsublime text2を使っている方が多かったため、反映するのがなかなか大変でした。
まず、ダウンロードされたファイルのファイル名を次のように変更します。
(利用したいファイル名).tmTheme
拡張子が変更されますが、テキストエディタなどで開くことができるため問題ありません。
次にSublime Textを開き、Preferences->Browse Packagesを開きます。
そして、Userというフォルダを作成し、その中に先ほど作ったスキームのtmThemeファイルをぶっこみます。
その後、Sublime Textを再起動します。
起動したら、Preferences->Color Schemeを開き、作ったscheme名で検索して、それを押せば、完成です。
いやーいいですね。
文字をごちうさのタイトルの色にしたのが工夫点です。
ちなみに実行結果
ごちうさ三期待ってます...
今回作ったテーマはGitHubにおいておきます。
よかったら、自由に持ってきて、利用・改造してください。
https://github.com/hiroto3432/GotiusaStyle
それでは。
Atcoder Grand Contest021のC問題
お久しぶりです。かささぎです。
テストとかテストとかテストが忙しく、なかなか書けませんでした。
最近Atcoderで競プロの練習をしています。といってもガチ勢ではないので、アルゴリズムのあれこれは分かりません。bitDPもビームサーチもできません。
こういう経緯もあり、500点以上の問題を解けた試がありません。そこで普段は300点までの問題をサッと解いた後、解けそうor面白そうな問題に挑戦しています。
今回開催されたAtcoder Grand Contest021は、A問題が300点、B問題が600点...となっており、A問題を解いた後、問題を眺めて、C問題を解くことにしました。私は浮動小数演算弱者なので、B問題は諦めました。
問題はこちら。
敷き詰め系の問題ですね。
要はN*Mのタイルに、1*2のタイルA枚と2*1のタイルB枚を全て張り付けられるかって問題です。
この問題では、2*2のマスに縦長のタイル2枚または、横長のタイル2枚を置けることを軸として考えました。NやMが偶数であれば2*2の範囲を複数で埋められます(たとえば4*6のマスなら2*2が2つ*3つの集まりであると考えられます)。
NやMが奇数なら、一番端の列に敷き詰められるだけ敷き詰めて、N-1やM-1の偶数にして、敷き詰めていく方法をとりました。
Gistにコードを乗っけときます。コメントも何もないうえ、殴り書きなので、あまり参考にはならないと思います。
Atcoder Regular Contest 021 C
実行結果は安定のWAでした。
テストケース105個中、
AC:85
WA:16
TLE:4
でした。
コードを見ると、端に敷き詰めていく際に、タイルがなくなっても敷き詰めていたので、条件を追加。
また、メインの処理はO(nm)で、n,m<=1000より、2secの制限時間を超えるのはおかしいと思っていたのですが、この原因はchar型の二次元配列のデータを、1つづつprintしていたからでした。System.out.print関数は普通の処理に比べやや重いです。プロコンで学んだ数少ない教訓の一つです。
そこでアウトプットするコードをこのように変更。gistでは99行目からです。
for(int n=0;n<N;n++){ String st=""; for(int m=0;m<M;m++){ st += map[n][m]; } System.out.println(st); }
この二つの変更を加え、再提出。結果、
もう見た。
テストケース105個中、
AC:94
WA:6
MLE:5
でした。
なぜMLE?は?ってなったんですが、先ほどのアウトプット処理のString型の変数にどんどんchar型の文字を追加したからでしょうね。
ということで、TLEとMLEどちらも起こらないように、バランスをとってこんな感じに変更。
for(int n=0;n<N;n++){ String st=""; for(int m=0;m<M;m++){ st += map[n][m]; if(m%64 == 63){ System.out.print(st); st = ""; } } System.out.print(st); System.out.println(); }
stの文字数を64文字までに制限しました。
すると、MLEのところはACとなりました。
まとめるとこんな感じ。
実行時間とメモリ容量のトレードオフは大切だと実感しました(こなみ)。
いい感じになりましたが、WAは6個残っているままタイムアップ。6/105なんで、たぶんなにか一つアルゴリズム的に間違っているんでしょう。
やっぱり900点問題は難しかったよ。
Atcoderの高得点問題を解けるようになるため、これからも続けていきます。
それでは。
P.S.
来月東京行くので、おいしい店教えてください。
別アクティビティからgetAssets()の呼び出し
こんばんは、かささぎです。誰か風と雪止めて。
最近Atcoderを始めたのですが、特筆するような結果を残せなかったので、今回のネタはAndroidです。悲しい。
getAssets関数を別クラスから呼び出した際にエラーが起こり、解決できたためメモしときます。
参考文献です。http://individualmemo.blog104.fc2.com/blog-entry-41.html
getAssets関数はassetsフォルダ内のファイルを利用するために用いられる関数ですが、普段はこのように利用することが多いと思います。
AssetManeger mngr = this.getAssets(); InputStream is = mngr.open("fileName");
これを別クラスから実行した場合、mngr,isがnullになります(普通はtry内に書きますので、catchブロックでスタックトレース(ぬるぽ)が得られます)。
原因はthisがnullになっていることです。
このthisとは、Activity contextという、アプリケーションの情報をグローバルで受け渡すためのインターフェースが入るのですが、実行中のactivity以外ではnullになるという仕様があります。
つまり別クラスから呼び出す際には、実行中のアクティビティのcontextを渡してやる必要があります。
こんな感じです。
<呼び出し元>
Hoge hoge = new Hoge(); hoge.readFile(this);
<呼び出し先>
public static void Hoge(Context context){ InputStream is = null; try{ AssetManager mngr = context.getAssets(); is = mngr.open("fileName"); }... }
ちなみに、contextにはApplication ContextとActivity Contextがあります。Applicationの方はアプリケーションと連動して、同じContextオブジェクトである(静的な)もので、Activityの方はActivityの変更で動的に変化するものといった違いがあります。情報として持っているものに違いはないので、どちらでもいいそうです。
Activity Contextを渡すためには、引数をgetApplicationContext()にします。
Hoge hoge = new Hoge();
hoge.readFile(getApplicationContext());
今回はgetAssetes()関数の呼び出しでしたが、アクティビティ外でContextを利用する機会はいろいろありそうなので、覚えておきます。
それでは。
就活セミナーに参加してきた
しばらくぶりです。かささぎです。マカフィー滅んでくれねえかな。
最近いろいろ忙しくあまり書けてませんでした。今回はその原因の一つだったセミナーについて書きなぐっていきます。まあまだタスクが山積みなので、短めで。
参加したのは、1/13,14日に名古屋で開催された、高専生向けの2つのセミナーです。13日は学研アソシエさんの、高専生のための業界研究セミナー、14日はメディア総研さんの、高専生のための合同会社説明会です。
なんかコミケの戦利品みたいですが、気にしないでください。
どちらも様々な業界の企業さんが来ておられました。二日目のほうが規模が大きい感じでした。また二日とも、名古屋近辺の企業さんが多い印象を受けました。やはり某T社の影響は大きい。
感想ですが、やはり自分のしたい仕事の業界について様々な企業から情報を吸収でき、参考になります。似ている点も多いですが、開発についての考え方が全く違っていたりなど、異なっている点を探しながら聞いていると面白いです。
また、知らなかった企業について知ることができるのも大きなプラスだと思いました。特に、高専卒生が多く活躍している企業さんから得られるものは大きかったです。
それではレポートを書く作業に戻ります。さようなら。
P.S
岐阜駅近くで食べたらめーん。
https://tabelog.com/gifu/A2101/A210101/21014499/
鳥白湯に目覚めた。
翌朝に行ったモーニング。https://tabelog.com/gifu/A2101/A210101/21001841/
小倉トーストしゅき。あと自家製ヨーグルトがめちゃうまだった。おすすめ。
それでは。