Humanity

Edit the world by your favorite way

天下一プログラマーコンテスト

賞金・・・?100万・・・え??なにそれ?
というわけで参加するかどうかはともかく興味魅かれたのでやってみる。
というか朝見つけてやっと昼ごろにできあがるというダメダメな感じ。
packとかunpackとかhexとかその辺の関数(演算子?)の使い方をすっかり忘れてたのが原因だけどね!!1


うん、で、とりあえずこのバイト列がなんて文字列なのか調べるためにこんなコード書く。

#!/usr/bin/env perl
use strict;
use warnings;


my $bytes_str = 
          "e4bba5e4b88be381aee69687e5ad97e58897e381af5554462d38e38292e69687e5ad97"
        . "e382a8e383b3e382b3e383bce38387e382a3e383b3e382b0e5bda2e5bc8fe381a8e381"
        . "99e3828b3136e980b2e695b0e381aee38390e382a4e38388e58897e381a7e38182e3828be38082";
my $bytes = "";
while ($bytes_str =~ s/(..)//) {
    $bytes .= pack('C', hex($1) & 0xff);
}

{
    local $\ = "\n";
    print $bytes;
}

出力

以下の文字列はUTF-8を文字エンコーディング形式とする16進数のバイト列である。

packとかhexとかの使い方を忘れてたので初めてのPerlやらネットなどでカンニング
読んだ後に「チートじゃん」と気づく\(^o^)/

※補足
なお、UTF-8では1バイト目の上位ビットを見ることで、その文字が何バイトの文字かがわかる。
2進数表記した場合以下のようになる。
  • 1バイト目の上位ビットが「0」 1バイトの文字
  • 1バイト目の上位ビットが「110」 2バイトの文字
  • 1バイト目の上位ビットが「1110」 3バイトの文字

この補足なかったらperldoc utf8とかしてまた延々と時間食ってただろうな。
というかUnicodeとか知らn(ry


次。
すごーーーーーく忠実にやってみた版。
ようするにutf8に直してサイズ計算とかじゃなく補足に書いてあった通りに上位4ビット辺り調べて文字数をカウントしていくという方法。

#!/usr/bin/env perl
use strict;
use warnings;
use Carp;

sub assert {
    croak unless $_[0];
}

sub bytes_str_to_array_bits {
    # 0xffは念のため
    split //, unpack 'B*', pack 'C', hex($_[0]) & 0xff;
}

my $bytes_str = 
          "e4bba5e4b88be381aee69687e5ad97e58897e381af5554462d38e38292e69687e5ad97"
        . "e382a8e383b3e382b3e383bce38387e382a3e383b3e382b0e5bda2e5bc8fe381a8e381"
        . "99e3828b3136e980b2e695b0e381aee38390e382a4e38388e58897e381a7e38182e3828be38082";
my $count = 0;
assert(length($bytes_str) % 2 == 0);

while ($bytes_str =~ s/(..)//) {
    my @bits = bytes_str_to_array_bits($1);

    if (! $bits[0]) {
        # -1バイト目の上位ビットが「0」 1バイトの文字
        $count++;
    } elsif ($bits[0] && $bits[1] && ! $bits[2]) {
        # -1バイト目の上位ビットが「110」 2バイトの文字
        $count++;
        $bytes_str =~ s/(..)//;
        @bits = bytes_str_to_array_bits($1);
        assert($bits[0] && ! $bits[1]);
    } elsif ($bits[0] && $bits[1] && $bits[2] && ! $bits[3]) {
        # -1バイト目の上位ビットが「1110」 3バイトの文字
        $count++;
        $bytes_str =~ s/(..)(..)//;
        @bits = bytes_str_to_array_bits($1);
        assert($bits[0] && ! $bits[1]);
        @bits = bytes_str_to_array_bits($2);
        assert($bits[0] && ! $bits[1]);
    } else {
        die "not utf8 string.";
    }
}

print $count, "\n";
41


で、次にutf8にしてからlengthで文字数確認しようと思って次のコード書いたんだけど動かない。

#!/usr/bin/env perl
use strict;
use warnings;
use Carp;

sub assert {
    croak unless $_[0];
}


use utf8;

sub bytes_str_to_utf8 {
    my $bytes = shift;
    $bytes =~ s{(..)}{
        pack 'C', hex($1) & 0xff
    }exg;
    utf8::upgrade($bytes);
    return $bytes;
}

my $bytes_str = 
          "e4bba5e4b88be381aee69687e5ad97e58897e381af5554462d38e38292e69687e5ad97"
        . "e382a8e383b3e382b3e383bce38387e382a3e383b3e382b0e5bda2e5bc8fe381a8e381"
        . "99e3828b3136e980b2e695b0e381aee38390e382a4e38388e58897e381a7e38182e3828be38082";
assert(length($bytes_str) % 2 == 0);

$\ = "\n";
my $utf8_str = bytes_str_to_utf8($bytes_str);
print do { use utf8; length $utf8_str };
print do { use bytes; length $utf8_str };
print "is utf8?:" . utf8::is_utf8($utf8_str);


出力結果は

109
109
is utf8?:1

間違ってた。
lengthはdo文の中で呼び出さないとutf8とかbytesプラグマの意味ないじゃん。
というわけで次の箇所を修正した。

- print length do { use utf8; $utf8_str };
- print length do { use bytes; $utf8_str };
+ print do { use utf8; length $utf8_str };
+ print do { use bytes; length $utf8_str };

新しいコードの出力は次の通り。

109
211
is utf8?:1

なぜこうなるかはこのエントリ参照。



utf8 flagは付いてるのにバイト列と同じ文字数。
Perlのバージョンは5.8.8。ちょっと古いのが悪いんだろうか。最新版にしてみるか・・・


追記:Perl 5.10.0でも同じだった。なんでだろう・・・

追記2:参加賞が仮想サーバの提供とか!!(今さら気づいた
すげー。

追記3:
pack("H*", 文字列)すればいいらしい。

print pack "H*", "e4bb...."  # => 以下の文字列はUTF-8を文字エンコーディング形式とする16進数のバイト列である。

でもlengthが109なのは変わらず。。。


追記4:
できた。
それと一応文字数縮めたやつも貼っておく。

my $bytes_str = 
          "e4bba5e4b88be381aee69687e5ad97e58897e381af5554462d38e38292e69687e5ad97"
        . "e382a8e383b3e382b3e383bce38387e382a3e383b3e382b0e5bda2e5bc8fe381a8e381"
        . "99e3828b3136e980b2e695b0e381aee38390e382a4e38388e58897e381a7e38182e3828be38082";

print length ${[map { utf8::decode($_); $_ } pack "H*", $bytes_str]}[0];