LoginSignup
1
1

More than 5 years have passed since last update.

「響け!ユーフォニアム」でcodegolfする

Last updated at Posted at 2018-08-11

たまたまJavascriptを1行で書く - Qiitaという記事を見つけて、そういえばcodegolfなんてものがあったなと思い出して挑戦してみました。
【ファン迷惑】「響け!ユーフォニアム」という文字列だけで遊ぶシェル芸人達 - Togetter

この記事ではFoo.protoype.barFoo::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");
}

正規表現でいくパターンです。
正直短くならないと思います。
一番直感的な気はしますが。

読める程度に短くする

読める程度にvar1や改行や不要なコロンを取り除いていきます。
また、String::split[...]で書き直していきます。
同時にJavascriptでは複数のループ方法があるので、それぞれに書き直してみます。
Array::forEachArray::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]$1sとして利用できるので少し短くなりました。
もしかしたらと思って、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 iniを使っていなかったりfor ofsを使っていなかったり)

変数代入やインクリメントの移動

例として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


  1. varなしで宣言した変数はグローバル変数になる。ただしstrictモードでは不可。 

  2. ifalseのときにfor抜ける->i0(変換するとfalse)のときに抜ける 

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1