第26回オフラインリアルタイムどう書く「三種類の境界線」の ruby と C++ による実装


問題 http://nabetani.sakura.ne.jp/hena/ord26tribo/
解答リンク集 http://qiita.com/Nabetani/items/8a973a47633558f54ba8
イベント http://yhpg.doorkeeper.jp/events/15316

当日お見せした実装。

まずは ruby から。

def solve(s)
  positions = s.chars.map { |c| c.ord - 'a'.ord }.sort
  bo = [positions.size] * 3
  positions.each do |po|
    h = Math.sqrt(po).floor
    next if  (po - h**2).even? # 上が尖った三角は無視
    [-1, +1, -h * 2].each.with_index do |delta, ix|
      bo[ix] -= 2 if positions.include?(po + delta)
    end
  end
  bo.join(',')
end

DATA.map do |line|
  id, src, expected = line.split(/\s+/)
  actual = solve(src)
  (actual == expected).tap do |ok|
    puts '***NG*** : %s %s->%s ( %s )' % [id, src, actual, expected] unless ok
  end
end.all?.tap { |r| puts(r ? 'ok' : 'NG') }

__END__
0 bdelmnouy 5,7,9
1 a 1,1,1
2 q 1,1,1
45  abcdefghijklmnoqrstuwxy 5,7,7
46  abcdeghijklmnopqrstuvwxy  6,6,6

いつも通り、テストデータの大半は省略。

実装戦略は。

  • 線を数えていく。
  • 隣にあるから線がないとかいうことを度外視して全部数えてから、ここは線がないよね、と、減らしていく。

の二通りがある。

あと、セルとセルの関係をどこまで算術的にやるのか、どこまでデータを持つのかという部分もいろいろある。

この実装の場合は、減らしていく方向で、全部算術的に求めている。
減らしていく境界線なのかどうかを判断する際、下向き三角だけを見るようにしている。
そうすると「bの左にはセルがない」という状況に対処する必要がなくなり、ちょっと楽になる。

で。
当日、C++ に移植した。
アルゴリズムはまったく同じ。

C++
// clang++ -std=c++11 -Wall
#include <iostream>
#include <string>
#include <vector>
#include <cmath>
#include <algorithm>
#include <sstream>

using namespace std;

vector<int> get_positions( string const & src )
{
  vector<int> positions;
  for( auto ch : src ){
    positions.push_back( ch - 'a' );
  }
  return positions;
}

string solve( string const & src )
{
  vector<int> positions = get_positions( src );
  int s = positions.size();
  int borders[3]={s,s,s};
  for( auto po : positions ){
    int h = static_cast<int>( floor( sqrt( po ) ) );
    if ( (po-h*h)%2==0 ){
      continue;
    }
    for( int ix=0 ; ix<3 ; ++ix ){
      int delta = (vector<int>{-1,1,-h*2})[ix];
      if ( positions.end() != find( positions.begin(), positions.end(), po+delta ) ){
        borders[ix] -= 2;
      }
    }
  }
  return (stringstream()<<borders[0]<<","<<borders[1]<<","<<borders[2] ).str();
}

void test( string const & src, string const & expected )
{
  string actual = solve( src );
  bool ok = actual==expected;
  cout
    <<( ok ? "ok " : "***NG*** " )<< src << " => " << actual;
  if ( ! ok ){
    cout << " != " <<  expected;
  }
  cout  << endl;
}

void test_all()
{
/*0*/ test( "bdelmnouy", "5,7,9" );  
/* 略 */
/*46*/ test( "abcdeghijklmnopqrstuvwxy", "6,6,6" );
}
int main()
{
  test_all();
}

get_positions が、STL をちゃんと使えばもっとシンプルになると思うんだけど、STL力が足りず、こんなところで。
行数では、ruby の 3倍ぐらいの感じ。そんなもんかな。