Open21

【小ネタ集】コーディングサイト(Codewarsなど)で勉強になったと思う書き方(主に JavaScript, TypeScript,Python)

tac-tac-gotac-tac-go

PythonとJavaScriptで文字列を int型に変換するとき、エラーになる場合とその対処法

Pythonであれば 空の文字列を int を用いて int型 に変換しようとするときエラーになり、JavaScript であれば parseInt を使えば空の文字列は NaN と変換される。
そのときはそれぞれ or|| を用いればエラーを回避できる

Python

int("" or 0)
# orの後に出力したい数字を指定する

JavaScript

parseInt("" || 0)
// ||のあとに出力したい数字を指定する
tac-tac-gotac-tac-go

Pythonで zip_longest の引数 fillvalueについて

Pythonで異なる長さの配列で長い配列の方に合わせてloop を作成するときには zip ではなく zip_longest を使う。ただ、長さが違う時、短い配列がindexの最後まで到達してしまうとそのあとはNone が出力される。

['a', 'cat'] のように長さが異なる配列からそれぞれ文字を取り出し、index毎に文字を連結し、indexが末尾に到達した配列については空白文字を付与するような以下のタスクがある場合を考える。

"a c\n  a\n  t"

その時 zip_longest の引数として fillvalue が使える

itertools.zip_longest(*iterables, fillvalue=None)

今回の場合空白文字なので、値としては空白文字を与えると以下のような実装になる。

"\n".join(f"{a} {b}" for a, b in zip_longest(*arr, fillvalue=" "))
tac-tac-gotac-tac-go

JavaScriptで、pythonのようにnumpyのmean関数を用いて列方向の平均値を求める方法

Pythonでは

[ [1, 2, 3, 4], [5, 6, 7, 8] ]

のような配列があり、それらの列方向の合計を求める際にはnumpy配列に変換した上で
配列.mean(axis=0) を用いれば求められる

JavaScriptで特にライブラリなどを用いない場合、reduceを用いて実装することができる

const arr = [ [1, 2, 3, 4], [5, 6, 7, 8] ]
const meanArray = arr[0].map((_, colIndex) => 
        arr.reduce((sum, row) => sum + row[colIndex], 0) / arr.length
    );
    return meanArray; 

まず、arr[0] で列方向に繰り返すloopを作成する。その後、reduce にて 行ごとの値を取り出す。
row には arrの要素ごとに値を取り出すため、[1, 2, 3, 4][5, 6, 7, 8] のように一次元配列が格納された形になるが、row[colIndex] と記載することで列ごとに値を取り出す形となり、それらを足し合わせて、行の長さで割ることで各列ごとの平均を算出することが可能となる。

tac-tac-gotac-tac-go

JavaScriptにおいてprototypeを定義するときはアロー関数を使えない

当たり前のようにアロー関数を使っていると、prototype時にアロー関数を定義してしまうことがあるが、エラーになるので原因を調べてみました。

アロー関数が使えない理由は、アロー関数がthisのスコープを持たないから。
普通の関数ではthisが呼び出し元のコンテキストを指しますが、アロー関数では定義時のスコープをそのまま保持し、動的に変わらない。

なので、String.prototype.myNewMethod = () => { ... } とすると、thisundefined またはグローバルオブジェクトを指してしまい、意図したオブジェクトを指しません。

例えば:

String.prototype.myNewMethod = () => {
    console.log(this); // undefined or global object
    return this.toUpperCase(); // Error: this.toUpperCase is not a function
}

対して、通常の関数を使うと正しく this が現在のインスタンスを指します:

String.prototype.myNewMethod = function() {
    console.log(this); // the string instance
    return this.toUpperCase();
}
tac-tac-gotac-tac-go

TypeScriptにおけるfind関数の考え方

例えば
[ 1, 1, 1, 2, 1, 1 ] のような配列から値が唯一異なる数字(今回であれば2)を取り出したい時

arr.find((value,index) => arr.indexOf(value) === arr.lastIndexOf(value))

のように find 関数を使うことが便利なように思えるが、TypeScript のように型を定義して値を返却したい場合、記載 のように、値が見つからない場合は undefined を返すため undefined を型定義に含めないといけない。
なので、TypeScript においては filter 関数を定義するほうがよい。

arr.filter((el) => arr.indexOf(el) == arr.lastIndexOf(el))[0];
tac-tac-gotac-tac-go

Pythonで特定の型が来た時に文字列にして反転し、その後元の型に戻す方法

# 期待される出力
# 'Hello' -> 'olleH'
# 314159 -> 951413
def reverse_it(data):
    if type(data) in [int, str, float]:
        return type(data)(str(data)[::-1])
    return data

if-else で条件分岐させる必要がなく、最後に type(data) としてデータの型を取得しその値でキャストすることで余計な条件分岐が不要となる。また if の際にリストで条件一致することで少ないコード量で実装可能である。isinstance 関数での実装も可能ではあるが、こちらのほうがコード量は少なくすることができる。

tac-tac-gotac-tac-go

JavaScriptにおいてsortにて特定の値のみを一番後ろに配置する方法

0のみを後ろに配置したいとき

moveZeros([false,1,0,1,2,0,1,3,"a"])
// returns[false,1,1,2,1,3,"a",0,0]

実装

const moveZeros = (arr) => {
  return arr.sort((a, b) => b === 0 ? -1 : 0);
}

実装で言えばシンプルであるが、sort において 引数 bが0のときそれ以外の値を -1, 0を0に割り当てることで、負の値であるそれ以外の値が先に来て、0が後に来る。
もちろん、arr.sort((a, b) => b === 0 ? 0 : -1); のように実装すれば 0 を先に来させることができるし、b=== の部分を他の数字に変えれば それ以外の数値を一番前や後ろに固めることもできる。

tac-tac-gotac-tac-go

JavaScriptでreverseを使わずに配列を逆にしたい場合

1. 手動で要素を入れ替える方法

const reverseArray = (arr) => {
  for (let i = 0; i < Math.floor(arr.length / 2); i++) {
    let temp = arr[i];
    arr[i] = arr[arr.length - 1 - i];
    arr[arr.length - 1 - i] = temp;
  }
  return arr;
};

let array = [1, 2, 3, 4, 5];
console.log(reverseArray(array));  // [5, 4, 3, 2, 1]

2. 新しい配列に要素を追加する方法

const reverseArray = (arr) => {
  let newArray = [];
  for (let i = arr.length - 1; i >= 0; i--) {
    newArray.push(arr[i]);
  }
  return newArray;
};

let array = [1, 2, 3, 4, 5];
console.log(reverseArray(array));  // [5, 4, 3, 2, 1]

3. 再帰を使う方法

const reverseArray = (arr) => {
  if (arr.length === 0) return [];
  return [arr.pop()].concat(reverseArray(arr));
};

let array = [1, 2, 3, 4, 5];
console.log(reverseArray(array));  // [5, 4, 3, 2, 1]
tac-tac-gotac-tac-go

文末にある特定の記号のみを残しそれ以外の位置にある特定の記号は消す方法

!があるときを参考にする
入力例と期待する出力

"Hi!"     ---> "Hi!"
"Hi!!!"   ---> "Hi!!!"
"!Hi"     ---> "Hi"
"!Hi!"    ---> "Hi!"
"Hi! Hi!" ---> "Hi Hi!"
"Hi"      ---> "Hi"

自分がした実装

function remove (text) {
  const endExclamations = text.match(/!+$/);
  const cleanedText = text.replace(/!+/g, '');
  return endExclamations ? cleanedText + endExclamations[0] : cleanedText;
}

一度 ! を消してその後付加すれば良いと思ったので正規表現で!を検出しそれを!を削除した文章にくっつけた。

参考になった実装

function remove (string) {
  return string.replace(/!+(?!!*$)/g,"");
}

否定先読みを使う。!がありなおかつ文末には!がつかないようなものであればその!を削除するという実装にすれば文末の!は消さなくても済む。肯定先読みはある程度慣れていたが、今回の実装は否定先読みでうまく実装された例である。

tac-tac-gotac-tac-go

配列の中で最大頻度で出てくる数字、もしそれが重複する場合は大きい値にする。

[12, 10, 8, 12, 7, 6, 4, 10, 12]              -->  12
[12, 10, 8, 12, 7, 6, 4, 10, 12, 10]          -->  12
[12, 10, 8, 8, 3, 3, 3, 3, 2, 4, 10, 12, 10]  -->   3
function highestRank(arr){
  let getNum = num => arr.filter(n => n === num).length;
  return arr.sort((a,b) => getNum(b) - getNum(a) || b - a)[0];
}

Python であれば sortedの引数にkey=lambda で解けるが JavaScriptでは少し工夫が必要。
まず、出現頻度で並びかえ || を使って、もし同じ出現頻度なら値をもとにソートする実装が必要となる。

tac-tac-gotac-tac-go

ある数の次の素数を見つけ出すプログラム

入力例

5   =>  7
12  =>  13

実装

const nextPrime = (n) => isPrime(n + 1) ? n + 1 : nextPrime(n + 1);

const isPrime = (n) => {
  if (n < 2) {
    return false;
  }
  
  for (var i = 2; i <= Math.sqrt(n); i++) {
    if (n % i === 0) {
      return false;
    }
  }
  
  return true;
}

素数の計算で平方根を使う理由

素数の計算で平方根を使う理由は、効率的に素数を判定するため。

具体的には、素数の定義に基づき、素数 ( n ) とは ( 1 ) と自分自身以外の正の整数で割り切れない数。しかし、ある数 ( n ) が素数でない場合、必ずその数は ( 2 ) つの異なる正の整数の積で表される。このとき、片方の整数が平方根より小さく、もう片方の整数が平方根より大きくなる。

例えば、数 ( 49 ) が素数かどうかを調べる場合、平方根は ( 7 )。このため、( 49 ) が素数でないならば、必ず ( 1 ) 以上 ( 7 ) 以下の整数のいずれかで割り切れるはず。実際に ( 49 ) は ( 7 x 7 ) と表されるため、素数ではないと判断できる。

tac-tac-gotac-tac-go

JavaScriptで二次元配列のなかで唯一登場する値の座標位置をジェネレータによって見つける

出力例

Input: [
  ['x', 'o'],
  ['o', 'o']
]
Return [0,0] when x at top left => [0, 0]

ただしxが二つある場合は空の配列を返す

Input: [
  ['x', 'o'],
  ['o', 'x']
]

一番シンプルな実装で言えば二次元配列をforで回して配列に追加し配列の長さを調べて回答する以下のような実装。

const xMarksTheSpot = (input) => {
  let arr=[];
  for (let i=0; i<input.length; ++i)
    for (let j=0; j<input[i].length; ++j)
      if (input[i][j]=='x')
        arr.push([i,j]);
  return arr.length==1?arr[0]:[];
}

これでも悪くないが、処理効率を考えるとジャネレーターを使うのがよいだろう。
以下に実装を示す。

const xMarksTheSpot = (input) => {
  const gen = function*() {
    for (let i = 0, m = input.length; i !== m; ++i)
      for (let j = 0, n = input[i].length; j !== n; ++j)
        if (input[i][j] === "x") yield [i, j];
  }();
  const first = gen.next().value;
  const second = gen.next().value;
  return first && !second ? first : [];
}

つまり値を取り出す処理は二回でいい。全部のforループを回らなくても完遂できる可能性があるので参考になる実装であった。一つの使い方として配列の中を走査して調べたいが条件に当てはまるのがN回以上出たらダメとかそういう条件でジェネレータは有効に使えると思っている。

tac-tac-gotac-tac-go

JavaScriptで配列の長さを再帰処理を使って求める実装

配列の長さを求めるには .length を用いるが、lengthを用いずに再帰処理のみで配列の長さを求める方法を紹介する。
実装

function lenR([head,...tails]) {
  if(head===undefined)return 0
  return lenR(tails)+1
}

lenR の引数には配列が渡されるが、引数を受け取るときにスプレッド構文を使い、先頭に数字が存在しなければ 0を返すまで再帰処理をする。
注意点として

 if (!head) return 0;

というふうに書きたくなるかもしれないが、lenR([0,1]) => 0 先頭が0で始まる数字の場合、!headがtrueになってしまうのでundefinedの条件にしたほうがよい。

tac-tac-gotac-tac-go

変数を正規表現のなかに埋め込む方法

問題としてある変数が与えられたときにその変数の直後の文字を抽出したい。

例題

If letter = 'r':
comes_after("are you really learning Ruby?") # => "eenu"
comes_after("Katy Perry is on the radio!")   # => "rya"
comes_after("Pirates say arrrrrrrrr.")       # => "arrrrrrrr"
comes_after("r8 your friend")                # => "i"

実装

function comes_after(str,l) {
  const reg = new RegExp(`(?<=${l})[a-z]`,"ig")
  return (str.match(reg) || []).join("")
}

ある変数を使ってそれを埋め込みたい場合はRegExpを使い、テンプレートリテラルを用いる。今回のケースではある変数のあとに英語がマッチしたときなので肯定後読みを使う。

tac-tac-gotac-tac-go

連続する奇数、連続する偶数を見つけるとその間に記号を入れる問題

問題

454793 --> "4547-9-3"
1012356895 --> "10123-56*89-5"

一番シンプルな考え方だとfor文で前後一文字ずつ取り出し条件を満たすかどうかを調べていき、もし条件に一致していれば結果となる文字列に連結する。ただし、正規表現を使って解くこともできる。
肯定後読みを使って偶数が連続していたら先の文字に新しい文字を連結することで上記の問題も解決することができる。

const insertDashII = (num) => String(num).replace(/([13579])(?=[13579])/g,"$1-")
                                    .replace(/([2468])(?=[2468])/g,"$1*")
tac-tac-gotac-tac-go

Sortアルゴリズムを用いずに配列の中で一番大きい数とその次の大きい数を見つけ出し、その数同士のマイナスを計算

問題

[10, 5, 2] -> 10 - 5 = 2

largest が更新されるたびに、現在の largest の値を secondLargest に移す。
すでに largest より小さいが、secondLargest より大きい場合のみ secondLargest を更新する。

function diffBig2(arr) {
  let largest = -Infinity;
  let secondLargest = -Infinity;

  for (const num of arr) {
    if (num > largest) {
      secondLargest = largest;
      largest = num;
    } else if (num > secondLargest) {
      secondLargest = num;
    }
  }
  return largest-secondLargest
}
tac-tac-gotac-tac-go

二次元配列のなかで各配列に対して関数を適用

例題

const xss = [ [1,2,3]
            , [4,5,6]
            ];

gridMap( x => x+1 , xss )  =>  [[2,3,4],[5,6,7]]
gridMap( x => x**2 , xss )  =>  [[1,4,9],[16,25,36]]

自分の実装

const gridMap =(fn,arr1) =>arr1.map(arr2 => arr2.map(v => fn(v)))

参考になった実装

const gridMap = (fn,a) => a.map(v => v.map(fn))

シンプルに二次元配列から取り出した一次元配列に対してmapを適用しそのなかで関数を適用すれば値を取得できる。後者のほうがコード量も少なくわかりやすいので参考にしたい。

tac-tac-gotac-tac-go

与えられた3辺が正三角形なのか、二等辺三角形なのか、普通の三角形なのか、三角形として成り立たないのかを調査

let typeOfTriangle = (a, b, c) => {
  if ([a, b, c].some(x => typeof x != "number")) return "Not a valid triangle";
  [a, b, c] = [a, b, c].sort((x, y) => x-y);
  if (a + b <= c) return "Not a valid triangle";
  if (a == b && b == c) return "Equilateral";
  if (a == b || a == c || b == c) return "Isosceles";
  return "Scalene";
}

ここでの実装のコツとしては some を使った条件検索と、sortアルゴリズムを分割代入によって変数に再代入することである。
三角形は二辺の大きさが一辺の大きさを越えないといけないのでそこも調査する必要はある。

tac-tac-gotac-tac-go

非減少配列を生成する時のJavaScriptの実装

[2,1,2] -> [2,2,2]
[1,1,2,2,1,2,2,2,2] -> [1,1,2,2,2,2,2,2,2]

ある配列の要素が前の配列の要素よりも小さい場合、前の配列の要素にする。大きい場合はそのまま。
つまり非減少配列を作成したいときの実装。
シンプルに考えるのであれば、for文で前の要素と比較する方法が考えられる。
ただ、map を使って効率的に書く実装を紹介する。

function cleanedCounts(data) {
  let m = 0;
  return data.map(e => m=Math.max(m,e))
}

m=Math.max(m,e) これは、現在の要素か、前の要素が大きいかを調べて代入する処理だが、{} を省略していることで暗黙的なreturnがおこなわれている。
mに値を代入するだけではなく、mの値そのものが新しい配列の値となる。
つまり代入とreturnを一行でおこなっている効率の良いコードになっている。

tac-tac-gotac-tac-go

二つ同士の数を * をつかわないで実装する方法(30文字以内)

madMul=f=(a,b)=>b&&a+f(a,--b);

普段実装することはないが、参考になった実装のため記録。
b&& が再帰処理の終了条件で bが0になったときに終了する。具体的に数を入れたときの動作を説明する。

例: madMul(3, 4)
madMul(3, 4):
4 && 3 + f(3, 3) が評価される。

f(3, 3):
3 && 3 + f(3, 2) が評価される。

f(3, 2):
2 && 3 + f(3, 1) が評価される。

f(3, 1):
1 && 3 + f(3, 0) が評価される。

f(3, 0):
0 && ... の条件が偽になり、計算は停止する。

最終結果として、3 + 3 + 3 + 3 = 12 が返される。
tac-tac-gotac-tac-go

-の記号を使わないで二つの数字の引き算

const madSub=(a,b)=>a+~b+1;

ビット反転を使った実装。~bでその数字の マイナス1を引いたものが生成される。
+1 をすることでその数字の - の値を得ることができる。 最後にそれをaと足すと-の記号を使わなくても引き算がおこなえる。