JavaScript三十六景Tips集
[背景]
このようなまとめが欲しかった。
LeetCodeを解きながら長い間書き留めてきたJavaScript Tips集です。
チーム開発ではもちろん可読性が重視されますので、その点もご留意ください。
elseブロックを段落で表現する
if(!l1) return l2
else if(!l2) return l1
else if (l1.val <= l2.val) // ...some
else return l2
いっぺんに代入する
// let left = 0;
// let current = 0;
// let answer = 0;
let left = current = answer = 0
[i][0]のような値にのみアクセスしたい場合
for(let i = 0; i < queries.length; i++){
res.push(limit > prefix[queries[i][1]] - prefix[queries[i][0]])
}
to
for(let query of queries){
res.push(limit > prefix[query] - prefix[query])
}
i(index)を単独で使っていないのであればfor of
to
var reverseWords = function(s) {
// some
for (let c of s) {
if (c === ' ') {
res += word + c;
word = '';
} else {
word = c + word;
}
}
// some
};
includesしないで存在チェックをしないでSetを使う
let numsSet = new Set(nums);
for (const num of nums) {
if (!numsSet.has(num + 1) && !numsSet.has(num - 1)) {
ans.push(num);
}
}
n, kどちらかが0の場合0を返す
if(n === 0 || k === 0) return 0
to
if(n * k === 0) return 0
条件に合った時だけ動くfor文
let nextInsertIndex = 3
let k = 1
for(let r = 0; nextInsertIndex < k; r++){
console.log("never console")
}
setする時端的に書く
let nums = [1, 2, 1]
let map = new Map()
for(let n of nums){
if(map.has(n)){
map.set(n, map.get(n) + 1)
continue
}
map.set(n, 1)
}
console.log(map)
to
let nums = [1, 2, 1]
let map = new Map()
for(let n of nums){
map.set(n, (map.get(n) || 0) + 1)
}
console.log(map)
iteratorを使う
let m = new Map([[1,1], [2,1], [3,1], [4,2]])
let arr = m.keys()
for(let e of arr){
if(m.get(e) > 1){
console.log(e)
}
}
to
let m = new Map([[1,1], [2,1], [3,1], [4,2]])
let arr = m.keys()
let current = arr.next().value
while(current >= 0){
if(m.get(current) > 1){
console.log(current)
}
current = arr.next().value
}
whileを使わないforの書き方
let start = 0, end = numbers.length-1
while(start < end){
...some
}
to
for(let start = 0, end = numbers.length-1; start < end;){
...some
}
cacheする
// cacheする
for (let i = 0, len = arry.length; i < len; i++) {
console.log(ary[i])
}
短く書く
const a = {}
if(a["a"] === undefined) a["a"] = []
a["a"].push(1)
to
const a = {}
a["a"] ||= []
a["a"].push(1)
二次元配列の作り方
let dp = Array.from({length: word1.length + 1}).map(e => Array.from({length: word2.length +1}).fill(0))
to
let dp = Array(word1.length + 1).fill(0).map(() => Array(word2.length + 1).fill(0));
or
let dp = Array.from({ length: word1.length + 1 }, () => Array(word2.length + 1).fill(0));
よりシンプルになる。
maxを得たい時、spread構文を使う
12
let max = 0;
for (const val of candies) {
val > max && (max = val);
}
to
const max = Math.max(...candies);
引数を得て何か再定義したい場合、ボディで定義する必要がないかもしれない
function some(candies, extraCandies, max = Math.max(...candies)){...}
overflowを考慮したmid値を得る
let mid = Math.floor((right + left) / 2)
to
let mid = Math.floor(left + (right - left) / 2)) // lower mid
// let mid = Math.floor(left + (right - left + 1) / 2)) // uper mid
object内のundefinedだけignoreする
to
// objはコピー後
Object.keys(obj).forEach(e => {
if(obj[e] === undefined) delete obj[e]
})
奇数と偶数
from
if(nums[i] % 2 === 1 && nums[j] % 2 === 0){
...some
}
to
// 奇数の場合true
if(nums[i] % 2 > nums[j] % 2){
...some
}
Mapのvalue値をsortしたMapを返す
Mapを値でソートする場合は、entries()で[key, value]の配列を取り出し、それを値でソートしてからMapに戻す必要がある
let map = new Map([
['b', 2],
['a', 1],
['c', 3]
]);
// 値でソート
let sortedByValue = new Map([...map.entries()].sort(([, v1], [, v2]) => v1 - v2));
console.log([...sortedByValue]);
// [['a', 1], ['b', 2], ['c', 3]]
keyでsort
let map = new Map([
['b', 2],
['a', 1],
['c', 3]
]);
// キーでソート
let sortedByKey = new Map([...map.entries()].sort(([k1], [k2]) => k1.localeCompare(k2)));
console.log([...sortedByKey]);
// [['a', 1], ['b', 2], ['c', 3]]
returnすればメモリがO(n)じゃなくなる?
WIP
forの終え方
(breakでもいいけど)
for(let i = 2; i <= n; i++){
if(n % i === 0){
count++;
if(count === k){
res = i;
i = n+1; // ここで条件以上にしてループを終える
}
}
}
return res
headがnullになるまで回す
for(let i = 0; head; head = head.next){
hs[i++] = head
}
One line Fizz Buzz
const fizzBuzz = function(n) {
let res = []
for(let i = 1; i <= n; i++){
const isDivisibleFive = (i % 5) === 0
const isDivisibleThree = (i % 3) === 0
if(isDivisibleFive && isDivisibleThree){
res.push("FizzBuzz")
} else if(isDivisibleThree){
res.push("Fizz")
} else if(isDivisibleFive){
res.push("Buzz")
} else {
res.push(i.toString())
}
}
return res
};
to
const fizzBuzz = function(n) {
return new Array(n).fill(0).map((a, i) => (++i % 3 ? '' : 'Fizz') + (i % 5 ? '' : 'Buzz') || '' + i);
};
// 常に++iをしていくと1から始まり0でFizzが付くことがない
簡単にdictionalyを作成する
const dict = "abcdefghijklmnopqrstuvwxyz";
const charMap = new Map();
for(let i = 0; i < dict.length; i++){
if(!charMap.get(dict[i])) charMap.set(dict[i], i)
}
to
const dict = "abcdefghijklmnopqrstuvwxyz";
const charMap = new Map([...dict].map((v, i) => [v, i]));
ビットシフトを検討する
let right1 = 12345;
let right2 = 12345;
// 乗算
console.time('Multiplication');
for (let i = 0; i < 100000000; i++) {
right1 *= 2;
}
console.timeEnd('Multiplication');
// ビットシフト
console.time('Bitshift');
for (let i = 0; i < 100000000; i++) {
right2 <<= 1;
}
console.timeEnd('Bitshift');
// Multiplication: 588.912109375 ms
// Bitshift: 165.72705078125 ms
数字の桁数を数えるとき
let n = 100;
n.toString().length // 3
to
Math.log10(100) + 1 // 3
桁の長さが事前にわからない場合、toStringはO(n)の空間複雑度になる。一方Math.log10はO(1)
Setは果たしてコスト減になっているか
このようなintersectionを求める関数の例
重複を気にしないようにSet.addで積んでいって最後に配列に戻していますが、最初から配列で書くことで不要な変換を避けることができる
const intersection = function(nums1, nums2) {
nums1.sort((a, b) => a - b)
nums2.sort((a, b) => a - b);
let p1 = 0;
let p2 = 0;
let set = new Set()
while(p1 < nums1.length && p2 < nums2.length){
if(nums1[p1] === nums2[p2]){
set.add(nums1[p1])
p1++
p2++
} else if(nums1[p1] < nums2[p2]){
p1++
} else {
p2++
}
}
return Array.from(set)
};
to
const intersection = function(nums1, nums2) {
nums1.sort((a, b) => a - b);
nums2.sort((a, b) => a - b);
let p1 = 0;
let p2 = 0;
const result = [];
while (p1 < nums1.length && p2 < nums2.length) {
if (nums1[p1] === nums2[p2]) {
// 重複を避けるために、最後に追加された値と比較
if (result.length === 0 || result.at(-1) !== nums1[p1]) {
result.push(nums1[p1]);
}
p1++;
p2++;
} else if (nums1[p1] < nums2[p2]) {
p1++;
} else {
p2++;
}
}
return result;
};
そもそもtwo pointerを使わないでも書けますが上記は説明のため
other
const intersection = function(nums1, nums2) {
const set1 = new Set(nums1); // nums1 の要素をハッシュセットに追加
const result = new Set(); // 結果を格納するためのセット
for (const num of nums2) {
if (set1.has(num)) {
result.add(num); // nums2の要素がset1に含まれていれば結果に追加
}
}
return Array.from(result); // 結果を配列に変換して返す
};
時間効率: ソートする必要がないため、計算量は O(n + m) で済む。(nとmはそれぞれnums1とnums2の長さ)。ソートを使用したアプローチ(O(n log n + m log m))よりも高速。
結論:多くの場合 Set が最適
循環する配列の中で最後に値を加える
Circular Queue
で活躍する
let a = [undefined, undefined, 1, 2, 3]
const count = 3
const length = a.length;
const headIndex = 2;
const b = a[(headIndex + count) % length] = 4
b // [4, undefined, 1, 2, 3]
more
dequeueの際のheadIndexの移動
this.headIndex = (this.headIndex + 1) % this.capacity;
文字が持つ数字をindex位置にして重複チェックに利用する
let dis = Array(26).fill(false);
const index = s[right].charCodeAt() - 97; // 97 は文字 'a' の ASCII コード値
dis[index] = true
ラベル付きループで外側のループを抜ける
outerLoop: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) {
break outerLoop; // 外側のループを抜ける
}
console.log(`i = ${i}, j = ${j}`);
}
}
ビット演算子で整数に変換
const num = 5.75;
console.log(num | 0) // 5
// ビット単位のOR演算。|が内部的に符号付き整数に変換するため、5|0ということが起きている
この方法は、通常の Math.floor
よりも高速であることが多い。32ビット整数の範囲(約-2^31から2^31-1)の数値にしか適用できないので注意
シンボルを使った隠しプロパティ
const hidden = Symbol('hiddenProperty');
const obj = {
[hidden]: 'This is hidden',
visible: 'This is visible'
};
console.log(obj.visible); // "This is visible"
console.log(obj[hidden]); // "This is hidden"
console.log(Object.keys(obj)); // ["visible"]
Intlオブジェクトで文字列のフォーマッティング
const number = 1234567.89;
const formatted = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(number);
console.log(formatted); // "$1,234,567.89"
// これを使うと、数値のフォーマットや通貨の表示などを簡単に行える
XOR の性質を利用して重複排除
// 配列の中で一つだけ出現する数字を求める
let nums = [2, 3, 2, 4, 3];
let res = 0;
for (let n of nums) res ^= n;
console.log(res); // 4
- a ^ a = 0, a ^ 0 = a の性質で重複を消せる
- Set を使わず O(1) メモリで済む
~~ で高速に整数化
console.log(~~5.75); // 5
- Math.floor の省略形としてよく使われる
- ただし32bit 整数範囲に限定される
注意
数値をsortする場合はカスタム比較関数を渡すこと
sort()メソッドはデフォルトで文字列としてのソートを行う。各要素を文字列として扱い、Unicodeのコードポイントに基づいてソートするため数値が正しい順序でソートされない
[2, 34, 4, 100,4343, 2, 244, 34,400, 41].sort()
// [100, 2, 2, 244, 34, 34, 4, 400, 41, 4343]
Discussion
あと十点追加しても大丈夫w
競技プログラミングならありだけど、プログラミング教育なら動作の違いも把握しておくのがいいですね。
これ、left だけが let(ローカル変数) で current と answer はグローバル変数になります。
これ、厳密比較ではなくなります。