普段は Python (PyPy) で競プロしていますが、AtCoder を TypeScript で解いていくなかで、Python ではビルトインや標準ライブラリで提供されている機能が欲しい場面が多くありました。
だんだん TypeScript のテンプレートも育ってきたので、Python の便利機能(関数やコンテナ)を模倣した TypeScript の自作関数をまとめて紹介します。
TypeScript (3.8)
(AtCoder の 2022/08/22 現在のバージョン)で動作することを確認済みです。
なお、Python の input()
を模倣したものは以下の記事に書きました。
max(), min()
const max = (data: number[]) => data.reduce((a, b) => Math.max(a, b));
const min = (data: number[]) => data.reduce((a, b) => Math.min(a, b));
配列の要素数が少なければ、スプレッド構文を利用して Math.max(...array)
でも良いです。
しかし以下のドキュメントの通り、JavaScript の関数に多すぎる引数(おおよそ数万個以上とのことです)を渡すのは危険です(実際にエラーで通らなかったことを経験しました)。
以下のドキュメントの通り、配列の最大値や最小値を求める場合は Array.reduce()
を使用した方法が安全です。
なお、console.log()
に配列のスプレッドを渡したときにも同様のエラーを経験しました。その場合は Array.join(" ")
で文字列に変換してから出力するのがシンプルで良いと思います。
sum(), statistics.mean()
const sum = (data: number[]) => data.reduce((a, b) => a + b, 0);
const mean = (data: number[]) => sum(data) / data.length;
zip() *配列2個まで
const zip = <T1, T2>(a: T1[], b: T2[]): [T1, T2][] =>
[...Array(Math.min(a.length, b.length))].map((_, i) => [a[i], b[i]]);
配列3個以上への対応や、longest = true
とすることで itertools.zip_longest
と同等の動きを実現するアイデアがあります。今後問題を解いていく中で必要になったら実装してみます。
str.startswith(), str.endswith()
const startsWith = (s: string, prefixArray: string[]) =>
prefixArray.some((prefix) => s.startsWith(prefix));
const endsWith = (s: string, suffixArray: string[]) =>
suffixArray.some((suffix) => s.endsWith(suffix));
bisect.bisect_right()
const bisectRight = (array: number[], x: number) => {
let lo = 0;
let hi = array.length;
while (lo < hi) {
const mid = Math.floor((lo + hi) / 2);
x < array[mid] ? (hi = mid) : (lo = mid + 1);
}
return lo;
};
二分探索の便利関数です。
以下の Python のソースコードを参考にしました。
実戦で必要になったら、lo
, hi
を引数として受け入れるようにしたり、bisect_left()
などの他の bisect
モジュールの関数も追加したりしようと考えています。
collections.Counter()
const Counter = <T>(arr: T[]) => {
const counter = new Map<T, number>();
for (const v of arr) counter.set(v, (counter.get(v) || 0) + 1);
return counter;
};
Map
を返す関数として実装しています。most_common
などのメソッドに相当するものはその場で書いてしまっています。
関数名を大文字で始めるのは好き嫌いがありそうですが、const counter = Counter(array);
とすることで、Python との頭の切り替えを少なくするためにこうしています。
itertools.accumulate()
const accumulate = (data: number[]) => {
let now = 0;
return data.map((v) => (now += v));
};
累積和の最初にゼロを入れるために、const acc = [0, ...accumulate(arr)];
として使用することが多いです。
itertools.pairwise()
const zip = <T1, T2>(a: T1[], b: T2[]): [T1, T2][] =>
[...Array(Math.min(a.length, b.length))].map((_, i) => [a[i], b[i]]);
const pairwise = <T>(array: T[]) => zip(array, array.slice(1));
pairwise()
を使うとシンプルに書ける場面が多いので実装してみました。
場合によっては、以下の点について注意が必要です。
- 配列限定であること
- メモリ効率・速度効率が、おそらくそんなに良くないこと
range()
const range = (start: number, stop: number, step = 1) =>
Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step);
以下の MDN ドキュメントの range()
関数に、型とデフォルト値を追加したものです。
注意点として、Python の range()
とは異なり stop
の値も終端として含まれます。
string.ascii_letters
const range = (start: number, stop: number, step = 1) =>
Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step);
const alphaArray = (start: string, stop: string) =>
range(start.charCodeAt(0), stop.charCodeAt(0)).map((x) =>
String.fromCharCode(x)
);
Python の string.ascii_letters
とはだいぶ趣が異なりますが、それなりに使いやすいのではないかと思います。
range()
同様、MDN の以下のドキュメントを参考にしました。
set() & set()
const intersection = <T>(setA: Set<T>, setB: Set<T>) => {
const _intersection = new Set();
for (const elem of setB) if (setA.has(elem)) _intersection.add(elem);
return _intersection;
};
MDN のドキュメントの関数に型をつけたものです。個人的によく使うので、テンプレートに入れています。
終わりに
テンプレートを更新したら、追記・更新します。