たまたまJavascriptを1行で書く - Qiitaという記事を見つけて、そういえばcodegolfなんてものがあったなと思い出して挑戦してみました。
【ファン迷惑】「響け!ユーフォニアム」という文字列だけで遊ぶシェル芸人達 - Togetter
この記事ではFoo.protoype.bar
はFoo::bar
と表記します (例: Array.prototype.join
-> Array::join
)
とりあえず大本のコード何個か考える
A
var a = "響け!ユーフォニアム".split("");
for(var i = 0; i < a.length; i++){
console.log(a.join(""));
a.unshift(a.pop());
}
Togetterのほうにあるコードですね。
a.unshift(a.pop())
がなるほどって感じでした。
B
var b = "響け!ユーフォニアム";
b.forEach( (_, i) => {
console.log(b.slice(i) + b.slice(0, i));
});
Togetterのほうにあるコードの二つ目です。
C
var b = "響け!ユーフォニアム";
[b].concat(b.split("")).reduce( (s, e) => {
console.log(s);
return s.slice(1)+e;
});
なんとなくArray::reduce
使いたいなって思って書きました。
D
var a = "響け!ユーフォニアム";
for (var i in a) {
console.log(a);
a = a.replace(/(.)(.*)/,"$2$1");
}
正規表現でいくパターンです。
正直短くならないと思います。
一番直感的な気はしますが。
読める程度に短くする
読める程度にvar
1や改行や不要なコロンを取り除いていきます。
また、String::split
を[...]
で書き直していきます。
同時にJavascriptでは複数のループ方法があるので、それぞれに書き直してみます。
Array::forEach
はArray::map
で返り値を捨てれば同じことになるのでそっちで統一します。
/* ----- A ----- */
a=[..."響け!ユーフォニアム"];
// for
for(i = 0; i < a.length; i++){console.log(a.join(""));a.unshift(a.pop())}
// for in
for(i in a){console.log(a.join(""));a.unshift(a.pop())}
// map
a.map( _ => {console.log(a.join(""));a.unshift(a.pop())})
/* ----- B ----- */
b="響け!ユーフォニアム";
// for
for(i = 0; i < b.length; i++){console.log(b.slice(i) + b.slice(0, i))}
// for in
for(i in b){console.log(b.slice(i) + b.slice(0, i))}
// map
b.map( (_, i) => {console.log(b.slice(i) + b.slice(0, i))})
/* ----- C ----- */
b="響け!ユーフォニアム";
[b, ...b].reduce( (s, e) => {console.log(s);return s.slice(1)+e})
/* ----- D ----- */
b="響け!ユーフォニアム";
// for
for(i = 0; i < b.length; i++){console.log(b);b=b.replace(/(.)(.*)/,"$2$1")}
// for in
for (i in b) {console.log(b);b=b.replace(/(.)(.*)/,"$2$1")}
なんとなくcodegolf感が出てきました
関数を工夫する
同じようなことをするにも別の関数を使ったり、別の方法で使ったりすることで短くなることもあります。
少し書き換えてみます。
for of
が先ほどないなって思った方もいらっしゃると思いますが、ここで書き直します。
A
a=[..."響け!ユーフォニアム"];
// for
for(i = 0; i < a.length; i++){console.log(a.join(""));a.push(a.shift())}
// for in
for(i in a){console.log(a.join(""));a.push(a.shift())}
// map
a.map( _ => {console.log(a.join(""));a.push(a.shift())})
回転方向は関係ない(はず)なので、a.unshift(a.pop())
をa.push(a.shift())
に書き換えます。
これで1文字減ります。
D
b="響け!ユーフォニアム";
// for
for(i = 0; i < b.length; i++){console.log(b);b=b.replace(/(.)(.*)/,"$2$1")}
// for in
for (i in b) {console.log(b);b=b.replace(/(.)(.*)/,"$2$1")}
// for of
for(s of b) {console.log(b);b=b.replace(/.(.*)/,"$1"+s)}
// for of 2
for(s of b) {console.log(b);b=b.replace(s,"")+s}
// for of 3
for(s of b) {console.log(b);b=b.slice(1)+s}
// for in 4
for(i in b) {console.log(b);/./.exec(b);b=RegExp["$'"]+RegExp["$&"]}
// for of 4
for(s of b) {console.log(b);/./.exec(b);b=RegExp["$'"]+s}
for of
を使うことでb[i]
や$1
をs
として利用できるので少し短くなりました。
もしかしたらと思って、RegExp["$'"]
などの非推奨のものでも書いてみました。
正規表現が/./
の短さになるのはメリットかなと。
b="響け!ユーフォニアム";
for(i in b)console.log(b.replace(eval(`/(.{${i}})(.*)/`),"$2$1"));
というのも書いてみたんですが、長すぎますね…
forやmapの中身の一行化
forの中身を一行にすることで{}
を省略できます。
mapなどは内側の即時関数を()=>{return~}
から_=>~
にできます。
一行の場合は最後が自動でreturn
されるのが大きいですね。
そこで一行への仕方ですが、何個か方法があります。
- カンマ演算子(
,
)の利用- 関数の引数には使えません
- 例:
func(1, 2)
->引数に1と2を渡すことになるからNG、func((1, 2))
ならOK
- 論理和/差演算子(
||
/&&
)の利用- 先に来るものが必ず
true
もしくはfalse
に変換できる必要があります - 例:
console.log()||a+b
,a-b&&console.log()
(a-bは0にはならない場合)
- 先に来るものが必ず
- ビット和/差演算子(
|
/&
)の利用- 先に来るものが必ず数値に変換でき
0
もしくはそれ以外に変換できる必要があります - また、返り値が必要な場合は使えません(数値変換されるので)
- 例:
console.log()|a+b
- 先に来るものが必ず数値に変換でき
カンマ演算子が一番考えることが少ないので(たぶん)、優先したいですね。
/* ----- A ----- */
a=[..."響け!ユーフォニアム"];
// for
for(i = 0; i < a.length; i++)console.log(a.join("")),a.push(a.shift())
// for in
for(i in a)console.log(a.join("")),a.push(a.shift())
// map
a.map( _ => console.log(a.join(""))|a.push(a.shift()))
/* ----- B ----- */
b = "響け!ユーフォニアム";
// for
for(i = 0; i < b.length; i++)console.log(b.slice(i) + b.slice(0, i))
// for in
for(i in b)console.log(b.slice(i) + b.slice(0, i))
// map
b.map( (_, i) => console.log(b.slice(i) + b.slice(0, i)))
/* ----- C ----- */
b = "響け!ユーフォニアム";
[b, ...b].reduce( (s, e) => console.log(s)||s.slice(1)+e)
/* ----- D ----- */
b = "響け!ユーフォニアム";
// for
for(i = 0; i < b.length; i++)console.log(b),b=b.replace(/(.)(.*)/,"$2$1")
// for in
for (i in b)console.log(b),b=b.replace(/(.)(.*)/,"$2$1")
// for of
for(s of b)console.log(b),b=b.replace(/.(.*)/,"$1"+s)
// for of 2
for(s of b)console.log(b),b=b.replace(s,"")+s
// for of 3
for(s of b)console.log(b),b=b.slice(1)+s
// for in 4
for(i in b)console.log(b),/./.exec(b),b=RegExp["$'"]+RegExp["$&"]
// for of 4
for(s of b)console.log(b),/./.exec(b),b=RegExp["$'"]+s
だいぶ短くなってきましたね。
()などの除去
テンプレートレテラルを用いてfunc("文字列")
はfunc`文字列`
に書き換えます。
空白の完全除去も同時に行います。
/* ----- A ----- */
a=[..."響け!ユーフォニアム"];
// for
for(i=0;i<a.length;i++)console.log(a.join``),a.push(a.shift())
// for in
for(i in a)console.log(a.join``),a.push(a.shift())
// map
a.map(_=>console.log(a.join``)|a.push(a.shift()))
/* ----- B ----- */
b="響け!ユーフォニアム";
// for
for(i=0;i<b.length;i++)console.log(b.slice(i)+b.slice(0,i))
// for in
for(i in b)console.log(b.slice(i)+b.slice(0,i))
/* ----- C ----- */
b="響け!ユーフォニアム";
[b,...b].reduce((s,e)=>console.log(s)||s.slice(1)+e)
/* ----- D ----- */
b="響け!ユーフォニアム";
// for of
for(s of b)console.log(b),b=b.replace(/.(.*)/,"$1"+s)
// for of 2
for(s of b)console.log(b),b=b.replace(s,"")+s
// for of 3
for(s of b)console.log(b),b=b.slice(1)+s
// for of 4
for(s of b)console.log(b),/./.exec(b),b=RegExp["$'"]+s
いくつか明らかに無理そうなのは削除しました。(for in
でi
を使っていなかったりfor of
でs
を使っていなかったり)
変数代入やインクリメントの移動
例としてAのfor
のバージョンを使います。
b="響け!ユーフォニアム";
for(i=0;i<b.length;i++)console.log(b.slice(i)+b.slice(0,i))
i
を上へa.length
に向かってインクリメントしてますが、a.length
から始めてデクリメントするようにします。
b="響け!ユーフォニアム";
for(i=b.length;i;i--)console.log(b.slice(i)+b.slice(0,i))
こうすると、終了条件がi
になって2文字減ります。2
b="響け!ユーフォニアム";
for(i=b.length;i;)console.log(b.slice(i)+b.slice(0,i--))
インクリメントもfor文内に突っ込むとさらに1文字減ります。
ほかの文にも同じようにしていきます。
/* ----- A ----- */
a=[..."響け!ユーフォニアム"];
// for
for(i=a.length;i;i--)console.log(a.join``),a.push(a.shift())
// for in
for(i in a)console.log(a.join``),a.push(a.shift())
// map
a.map(_=>console.log(a.join``)|a.push(a.shift()))
/* ----- B ----- */
b="響け!ユーフォニアム";
// for
for(i=b.length;i;)console.log(b.slice(i)+b.slice(0,i--))
// for in
for(i in b)console.log(b.slice(i)+b.slice(0,i))
/* ----- C ----- */
[b="響け!ユーフォニアム",...b].reduce((s,e)=>console.log(s)||s.slice(1)+e)
/* ----- D ----- */
b="ム響け!ユーフォニア";
// for
for(i=b.length;i;i--)console.log(b=b.replace(/(.)(.*)/,"$2$1"))
// for of
for(s of b)console.log(b=b.replace(/.(.*)/,"$1"+s))
// for of 2
for(s of b)console.log(b=b.replace(s,"")+s)
// for of 3
for(s of b)console.log(b=b.slice(1)+s)
// for of 4
for(s of b)/./.exec(b),console.log(b=RegExp["$'"]+s)
"ム響け!ユーフォニア"
というのはせこい気もしますが…
結果
/* ----- A ----- */
a=[..."響け!ユーフォニアム"];a.map(_=>console.log(a.join``)|a.push(a.shift()))
/* ----- B ----- */
for(i in b="響け!ユーフォニアム")console.log(b.slice(i)+b.slice(0,i))
/* ----- C ----- */
[b="響け!ユーフォニアム",...b].reduce((s,e)=>console.log(s)||s.slice(1)+e)
/* ----- D ----- */
for(s of b="ム響け!ユーフォニア")console.log(b=b.slice(1)+s)
ということで自分の最短は
for(s of b="ム響け!ユーフォニア")console.log(b=b.slice(1)+s)
の51文字でした
ちなみにCoffeeScriptだと
console.log b=b[1..]+s for s in b="ム響け!ユーフォニア"
の46文字でした
参考
Tips for golfing in JavaScript - StackExchange
Tips for Golfing in ECMAScript 6 and above - StackExchange