Subscribed unsubscribe Subscribe Subscribe

64通りのkamipoを出力する

お題

入力として与えられたアルファベット文字列の、小文字・大文字での全組み合わせを出力する

入力がkamipoなら、

kamipo
kamipO
kamiPo
kamiPO
kamIpo
kamIpO
kamIPo
kamIPO
kaMipo
kaMipO
kaMiPo
kaMiPO
kaMIpo
kaMIpO
kaMIPo
kaMIPO
kAmipo
kAmipO
kAmiPo
kAmiPO
kAmIpo
kAmIpO
kAmIPo
kAmIPO
kAMipo
kAMipO
kAMiPo
kAMiPO
kAMIpo
kAMIpO
kAMIPo
kAMIPO
Kamipo
KamipO
KamiPo
KamiPO
KamIpo
KamIpO
KamIPo
KamIPO
KaMipo
KaMipO
KaMiPo
KaMiPO
KaMIpo
KaMIpO
KaMIPo
KaMIPO
KAmipo
KAmipO
KAmiPo
KAmiPO
KAmIpo
KAmIpO
KAmIPo
KAmIPO
KAMipo
KAMipO
KAMiPo
KAMiPO
KAMIpo
KAMIpO
KAMIPo
KAMIPO

と64通りの出力を。

回答

80byte。Deparseするとこんなかんじ。

use feature 'say';
@a = split(//u, pop @ARGV, 0);
foreach $i (1 .. 2 ** @a) {
    say join('', map({$i >> $_ & 1 ? uc $a[$_] : lc $a[$_];} 0 .. $#a));
}

基本戦略として、0から2の[入力文字数]乗までの数字を順に挙げていき、n桁目のビットがtrueであればn文字目は大文字、falseであればn文字目は小文字、というマッピングをすることで全通りを得る、というかんじで。
splitした後にfor文まわしてmapで各文字を操作しているので、とても単純。


72byte。joinする必要なかったんや。mapの"{}"も省ける。


70byte。Deparseすると

use feature 'say';
foreach $i (1 .. 2 ** (@a = split(//u, pop @ARGV, 0))) {
    say map(($a[$_] & ~chr($i >> $_ & 1 and 32)), 0 .. $#a);
}

引数のsplitは最初のfor文の式中に入れてしまえば1byte短くなる。あと入力が小文字なら、"\xDF"でマスクかけてやればuc関数を使う必要ないな、ということでchr(32)もしくはchr(0)の反転との論理積で大文字化と小文字化をしてみた。


68byte。Deparseすると

use feature 'say';
foreach $i (1 .. 2 ** (@a = split(//u, pop @ARGV, 0))) {
    say map(($a[$_] ^ ($i >> $_ & 1 ? $" : '')), 0 .. $#a);
}

つまりは空文字もしくは"\x20"との排他的論理和を取れば大文字・小文字の変換ができるじゃないか、ということで。"\x20"すなわちスペース(" ")の変わりに特殊変数$"を使うことで少しでも短く。


もっと別のアプローチでもっと短くできそうにも思えるのだけど思いつかない…。

反則技

ビット演算とか無視でとりあえず1/2の確率で大文字-小文字変換させて出力する、のを10000回くらい繰り返してからsortしてuniqすれば最終的に全通り出るよね、的な。

追記

@さんから。ありがとうございます。

60byte。はるかに短い…!
正規表現を使って各文字にマッチさせつつpos関数を使ってその文字の位置を取得して$&を変換、と…。なるほど〜、、posって知らなかった…