isyumi_netブログ

isyumi_netがプログラミングのこととかを書くブログ

もっと動的なPictureタグがほしい。

この仕様、1枚の画像をgulpで数種類のサイズ・フォーマットに変換してS3などに置く運用を想定していたと思う。

しかし、今では画像変換機能付きのCDNを通すのが当たり前になった。

つまり、Pictureタグの中で対応している画像サイズを明示する必要がまったくないのだ。また、画像フォーマットごとにURLを指定する意味もない。大体拡張子をみてそのとおりに変換するからだ。

よって、もっと動的な動きが出来るPictureタグが必要だ。オリジナル画像の縦横サイズと、CDNが対応している画像形式だけを指定すれば、動的にURLを変更してくれればいい。

 

タグ

<picture
 src="https://img.example.com/cat.jpg.$format?width=$width&height=$height"
    format="jpg,png,gif,webp,bmp"
    original-width="1200"
    original-height="800"
    size="50w" />

 

 

コレでよくね?

抽象度を意識するとプログラミングが上達する。

抽象度は上げて落とせ。

 

僕は正規表現を使わない。

僕はシステム開発の中で正規表現をほとんど使わない。

外部システムから取得したデータを取り込むときに渋々使うくらいだ。

文字列置換もあまりしない。

 

プログラミングが下手な人が作ったシステムは正規表現や文字列置換が多い気がする。そこで、なぜその人は正規表現を使わなければいけなくなってしまったのか考えることで上達のヒントとしたい。

下手な人の例

例えば、ECサイトを作っているとしよう。商品には値段がある。値札は¥マークと三桁ごとのコンマが振られてた文字列だ。

さて、商品の「カートに入れる」ボタンを押すと「合計金額」が加算される機能を作るとする。

下手な人は、画面の中にある三桁ごとのコンマが振られた文字列を何とかしてintにして変換して、それを足し算して、その結果に3桁ごとのコンマを振って、それを画面に表示しようとする。

それに対して上手な人はそもそも最初からint型で値段と現在の合計金額を引きまわしているので、そんなことをしなくていい。

 

これがシステムの中に出現する正規表現の数の差になる。そして、システム開発のうまさの差である。

抽象度に対する意識の差

プログラミングが上手な人と下手な人の間には、抽象度に対する意識の差がある。

上手い人は、極力抽象度を高く保ち続けようとする。画面に表示する最後の最後の瞬間に抽象度を落とすことでビューに変換する。

 

逆に下手な人は抽象度を意識できていないのだろう。そもそも、抽象であるデータ構造と具体である画面を分けて考えられないのかもしれない。

抽象度の高いデータとは

例を挙げよう。

Stringの"1,800"よりintの1800のほうが抽象度が高い。

JSTUTCならUTCのほうが抽象度が高い。

誕生日・現在時刻・年齢や定価・税率・税込価格のように3値の内2値が定まれば第3値が導出できるものは、どれか一つを省いた方が抽象度が高い。

不正値が混じっているかもしれないデータと不正値が混じっていないことを確認したデータなら確認したデータの方が抽象度が高い。

抽象度の3原理

抽象度の高低については、この三原理がいえる。

  • 抽象度の高いデータの方が扱いやすい
  • 抽象度の高いデータを抽象度の低い表現に落とすのは簡単
  • 抽象度の低い表現を抽象度の高いデータに変換するのは難しい

いいコードを書くための原則

ということは、プログラミングをするときはこうするべきだ。

処理の入口で最も抽象度の高いデータに変換する。

すべてのビジネスロジックは抽象度の高いデータを引数に、抽象度の高いデータを返す関数として書く。

処理の出口は、抽象度の高いデータのみを引数にして任意の出力をするように実装する。

 

これをシステム開発全体で意識することができたらプログラミングは上達すると思う。

尺取り法の最強ライブラリを作った。

僕は水色コーダーでありながら尺取り法がとても苦手である。
ミスりやすい。
そこでこの度、尺取り法の最強ライブラリを作った。

 

尺取り法の難しさ

実装上の注意として2つの点が挙げられる。

  1. どの尺にも入れない要素がいる
  2. LeftがRightを追い越す時の処理。

 

1.どの尺にも入れない要素がいる

例えば和が100以下の尺を数え上げる問題で、200の要素が混じっていたりすると、その前後の処理が複雑になる

2.LeftがRightを追い越す時の処理

LeftとRightを追い越す時に、Rightを追従させなければいけない。この時の処理を忘れがちである。

時には1と2が同時に起きることもある。非常に辛い

 

解決

尺が存在する区間を列挙

まず、単体で条件を満たす要素を列挙する。
そして、尺が存在する区間を列挙する。
たとえばこの数列から和が8以下の尺を列挙するとする

{ 1, 2, 8, 10, 3, 4, 6}

単体で存在可能な要素は{1, 2, 8, 3, 4, 6}なので、
尺が存在する区間は{1, 2, 8}と{3, 4, 6}である。
この2つの区間をそれぞれ処理すればいい。
この時点で、1のどの尺にも入れない要素がある問題はクリアできた。

尺が存在する区間の始まりをハンドリングする

次に、2の問題はライブラリが区間の開始イベントを送ってくれると解決する。
区間が始まるタイミングは3つだ

  • 配列の最初の要素
  • 自分の左がどの尺にも入れない要素
  • 左が右を追い越した時

プログラマは、区間の開始を知らされた時に尺を初期化すればいい。

 

実装

プログラマは以下の6個のイベントハンドラを処理すればいい。

  • この要素は単体で条件を満たしているか?
  • 左を固定して尺を初期化しろ
  • 右に1個伸ばせるか?
  • 右に1個伸びろ
  • 左を1個捨てろ
  • 区間が確定した

 

ライブラリ

void tow_pointer(int n,
function<bool(int left)> check_begin,
function<void(int left)> begin,
function<bool(int right)> check_right,
function<void(int right)> push_right,
function<void(int left)> pop_left,
function<void(int left, int right)> dead_end
) {

auto go = [&](P range) {
int right = -1;
for (int left = range.first; left <= range.second; left++) {
if (right < left) {
right = left;
begin(left);
}
while (right + 1 <= range.second && check_right(right + 1)) {
right++;
push_right(right);
}
dead_end(left, right);
pop_left(left);
}
};


auto get_ranges = [&] {
vector<bool> enable_items(n);
rep(i, n) enable_items[i] = check_begin(i);

auto start = [&](int i) {
if (i == 0) {
return enable_items[i] == true;
}
if (!enable_items[i - 1] && enable_items[i]) {
return true;
}
return false;
};

auto end = [&](int i) {

if (i == n - 1) {
return enable_items[i] == true;
}

if (enable_items[i] && !enable_items[i + 1]) {
return true;
}

return false;
};

vector<P> ans;
P p;
rep(i, n) {
if (start(i)) {
p = P(i, -1);
}
if (end(i)) {
p.second = i;
ans.push_back(p);
}
}
return ans;
};


vector<P> ranges = get_ranges();
for (P range: ranges) go(range);
}

使い方

例えばこんな感じで使える

https://atcoder.jp/contests/abc032/tasks/abc032_c

 

int main() {
int n;
ll k;
cin >> n >> k;

vector<ll> numbers(n);
rep(i, n) cin >> numbers[i];

if (find(numbers.begin(), numbers.end(), 0ll) != numbers.end()) {
cout << n << endl;
ret();
}
ll sum = 0;

// この要素は単体で条件を満たすか?
auto check_begin = [&](int left) {
return numbers[left] <= k;
};

// leftを左として尺を初期化しろ
auto begin = [&](int left) {
sum = numbers[left];
};

// 右に1個伸びれるか確認しろ
// 次の「1個右の要素は」right
auto check_right = [&](int right) {
return sum * numbers[right] <= k;
};

// 右に1このびろ
// 次の「1個右の要素は」right
auto push_right = [&](int right) {
sum *= numbers[right];
};

// 左を1個捨てろ
// 捨てる左の要素はleft
auto pop_left = [&](int left) {
assert(sum % numbers[left] == 0);
sum /= numbers[left];
};

ll ans = 0;

// 尺が伸びれるところまで伸びた
auto dead_end = [&](int left, int right) {
ll now = right - left + 1;
cmax(ans, now);
};


tow_pointer(
n,
check_begin,
begin,
check_right,
push_right,
pop_left,
dead_end
);
cout << ans << endl;

}

このあたりを見てほしい。

https://atcoder.jp/contests/abc032/submissions/11171624

https://atcoder.jp/contests/abc038/submissions/11171673

https://atcoder.jp/contests/arc022/submissions/11171696

https://atcoder.jp/contests/abc098/submissions/11171718

 

サンプル自動生成力があれば時々青問題が解ける

昨日のAtCoder Grand Contest 043でB問題を解いて2完できた。うれしい。
水色コーダーには望外で僥倖だ。

https://atcoder.jp/contests/agc043/tasks/agc043_b

僕は稀にこの手の青難易度問題を解いてしまうことがある。そのテクニックを紹介しよう。

やること

  • 愚直解を書く
  • テストケースを自動生成するコードを書く
  • For文で小さいケースから順番に全部試す
  • プリントする

昨日のB問題は8桁くらいを一瞬でprintできる。

この手の問題は絶対に簡単な規則性があるので、ずーと眺めていたら何か見つかるかもしれない。

昨日の問題は、「2段目以降は、nが偶数ならトーナメントにして解いても同じ結果になる」ということに気づいた。愚直解なら1段につき1要素しか減らないのでo(n^2)になってTLEだが、トーナメントにして1段を半分にしていいならo(log n)になるのでACできる。ということは、あとはnが奇数の時だけ考えればいい。奇数の時も簡単だ。1段を愚直解すれば長さが偶数になる。

 

// 一段減らす
vector<int> sub(vector<int> &v) {
if (v.size() == 1) return v;
vector<int> v2(v.size() - 1);
rep(i, v.size() - 1) {
v2[i] = abs(v[i] - v[i + 1]);
}
return v2;
}

// 半分にする
vector<int> tournament(vector<int> &v) {
vector<int> next;
for (int i = 0; i < v.size(); i += 2) {
int a = abs(v[i] - v[i + 1]);
next.push_back(a);
}
return next;
}

// 偶数なら半分にする
// 奇数なら一段減らしてから半分にする
int solv(vector<int> v) {
while (v.size() != 1) {
if (v.size() % 2 == 0) {
v = tournament(v);
} else {
v = sub(v);
}
}
return v.front();
}

 

提出前にすること

「多分解けたな」と思ったら、せっかくなのでさっき作ったテストケース自動生成コードを使って確認してほしい。小さいサンプルで、愚直解と同じ結果になるか試すのだ。結構地味なコーナーケースを検出することがある。大体こういう問題はnが極端に小さい時に例外がある。昨日の問題なら2231の時だけこの方法ではうまくいかないことが検出できた。小細工をして通した。逆に言えば、nが小さいケースを全部通せたらだいたいACできる。

その他

愚直解も実装が複雑な問題であれば、TLEになることが分かっていても愚直解をジャッジに投げてほしい。その勇気も必要だ。ペナルティはたかが5分だ。間違った愚直解で時間を失うよりましだ。

 

おススメ

このブログに感化された緑・水色コーダーの方は、ぜひこのやり方でこの問題を解いてみてほしい。
頭で考えるとめちゃくちゃ難しい問題だが、小さいケースからprintしていけば超簡単だ。
https://atcoder.jp/contests/abc059/tasks/arc072_b

 

Web広告系のトラッキングタグは何でこんなに遅いんだ。

お仕事でECサイトを作った。
僕はWebページの高速化が得意だ。
広告系のトラッカーを入れるまではPageSpeed Insightsが97点だった。

 

それから言われたサードパーティの広告系のタグを入れた。
FacebookのトラッキングタグにYahoo広告のトラッキングタグに......その他いろいろ。

 

それらを入れただけでPageSpeed Insightsが80点を切った。
ため息が出る。
もう少し気を使ってくれ。
Google Analyticsくらいは良い奴だと思っていたのに。

 

こういうのは全部WebWorkerに持っていく流れにならないかな。

 

もしくは、どうせサードパーティCookieは消されていくんだし、サーバーサイドからログを送信する方向になってほしい。サーバーからIPとUAとURLを送れば大体事足りるでしょう。

AtCoderの精進管理ツールを作った

AtCoder水色になりました。
丁度ABCを全埋めしたタイミングでした。
ABCを全埋めしたら水色くらいなれるという言説があるようですが、ある程度確からしと思います。

さて、僕はAtCoderで二周目に入ろうと思いました。
AtCoder Problemsは緑色に染まってしまったので、二周目以降の進捗を管理できるツールを作りました。

atcoder-chart.net

 

問題と難易度のデータはAtCoder ProblemsさんのAPIを使わせていただきました。

解けた問題は○をつけ、少しでも詰まった問題は△を付けます。
次回以降は△を付けておいた問題だけを解いていきます。

なぜこんなものを作ろうと思ったのか。
僕はチャート式を使って数学の勉強をする感覚でAtCoderを使うのがいいと思っています。
頑張って難しい問題に挑むより、解答方法丸暗記でもいいから低難易度の問題は必ず全部解けるようにしたほうが成長が速いのではないかと考えています。

 

僕は水色コーダーです。
自分は今まで「茶色問題までなら瞬殺できる。緑色問題もちょっと考えたら解ける」と思っていました。
しかし、このツールで記録を付けながら二周目をした所、驚くことが分かりました。
実は茶色問題でも1/3くらいは悩んだり間違えたりしていました。緑問題の中にも解説を見ないと解けない問題がそこそこありました。
二周目なのに!
一周目の僕は緑色問題が解けたら「緑色余裕!」と思い、解けなかったら「今のはたまたま運が悪かった」と思っていました。実は「運が悪かった」が1/3以上を占めていたのです。運ではなく実力不足です。

あなたも、○○色くらいなら瞬殺出来ると思っているはずです。多分大して解けてないと思います。

僕と同じように、下位問題を全部潰しておきたい人はぜひ使ってください。

CSSの競技プログラミングを作りたい

最近競技プログラミングというものを始めた。

これは大変素晴らしい。
自分の練習のための教材になるし、他人のスキル感を大雑把に掴むことが出来る。
あらゆる技能についてこういう仕組みが備わっていくべきだ。

ところで、最近マークアップエンジニアの募集をせねばならなくなった。HTMLとCSSを書くお仕事の募集だ。

マークアップエンジニアも難しい仕事だ。僕はHTMLのセマンティクスを重視したHTMLを書いてほしいと思っている。そして変更に強いCSSを書いてほしい。様々な画面サイズで意図したとおりに表示してほしい。パフォーマンスについても最低限の理解をしてほしい。Pictureタグとか。deferとか。古いブラウザのことは考えなくていいと思っているが、モダンブラウザ間の差異の吸収の仕方は知っておいてほしい。

専門学校に募集をかけようという話になったことがある。その場にいた専門学校卒の人がこういった。「専門学校には一つのクラスに2、3人だけ凄い人がいて、それ以外は本当に何もできない」。そんな人を引き当ててしまったら困る。まともな人を見分けたい。

マークアップエンジニアのスキルを測るのは難しい。一般的に作品を見せてもらうということにしているらしい。しかし、この方法は問題が多い。まず、どれくらい時間をかけて作ったのかわからないということだ。そして、網羅的な知識を持っているか判断しづらい。
応募者側も、何を作ったら認めてもらえるのかわからないから困っちゃうだろう。つまり、こちらは、基礎技術が有るか無いかを見たいのに、応募者側はネタを見せたくなっちゃうというギャップがある。

だったら、明確なお題がある方が混乱させないだろう。

そこでこういうシステムを考えた。

AtCoderみたいに時間を決めてコンテストを開催する。
問題はワイヤーフレーム形式で出題される。
色や影や角丸の指定がある場合は、画像だとわかりにくいので文章で伝えたほうがいいだろう。
提出されたHTMLとCSSサーバーサイドでレンダリングする。Headless Chromeなどを使えばいいだろう。この時、様々な画面サイズを指定してレンダリングする。
そして、何かしらの判定技術で正誤を付ける。画像処理的に採点するという方法があるし、各DOM要素のclientHogeをチェックしていく方法でもいいだろう。
最後に、正解にかかった時間を元にレーティングを付ける。

こういうアイディアも考えた。
1問提出するごとに、変更依頼が発生するといいのではないか。
AtCoderは1問目と2問目は全く関係ない問題になっていることが多いが、あえて1問目と2問目に繋がりを持たせる。すると、変更に強いマークアップが出来るほど有利になる。

これは採用に困ってる企業からお金を貰えそうだ。