ラベル アルゴリズム の投稿を表示しています。 すべての投稿を表示
ラベル アルゴリズム の投稿を表示しています。 すべての投稿を表示

2013年9月19日木曜日

Hello world maniacs !

CodeIQ の @cielavenir さんからの言語指定の問題「Restricted Words - 出力したい値をどうやって手に入れるか?」に挑戦しました。

問題は
標準出力に
Hello World
と出力するプログラムを作成して下さい。

ただし、数値、文字及び文字列リテラルを解答に含めることはできません。
Perlのqqやqw、Rubyの%Q、%q、%wなども避けたほうが評価が高くなります。
言語仕様をフル活用して下さい!
というものです。
ここで注目したいのが!!
プログラミング言語は
AppleScript(osascript)/C/C++/C#/Clojure/D/Erlang/Fortran/Go/Groovy/Haskell/
Hello Algorithm/HSP/Java/JavaScript(Node.js)/Kuin/Lisp/Lua/OCaml/Pascal/
Perl/PHP/Pike/Python/R/Ruby/Scala/Scheme/Smalltalk/VB.Net
のいずれかを使用して下さい。指定された言語以外での解答は評価されません。

Pascal キター!!!!!
いままで CodeIQ では Pascal / Delphi は全く相手にされてなかったのに!!
ということで、軽く挑戦してみることにしました。

すぐに思いついた方法は3通り

  1. brainf*ck のようなチューリングマシン的な方法
  2. リフレクションを使ってクラスやメソッド名から抽出する方法
  3. 文字コードを列挙型で表す方法

このうち、1の方法は簡単すぎてつまらないので却下。
2の方法もあくまで言語指定が Pascal なので却下。
ということで、3の方法で行くことにしました。

ここで面倒だったのが言語指定が Pascal であること。
Pure Pascal では type とか var といったブロックは位置が決まっているし、Program には input / output を付けないといけないとか、const 指定子が使えないとか色々あります。
ということで、1回 Delphi でコンパイルした後 gpc 使ってコンパイルを試しました……ところが!!
gpc は最早 Pure Pascal ではなく Borland 拡張……つまり Delphi の文法が多分に採り入れられていたのです!!
すごいすんなりコンパイルされちゃう!! \(^o^)/
といっても、これが ISO 標準であり、これこそが Pure Pascal なのかなあなんて思ったりもしたのですが、 Pure Pascal は初版の物だろう!!ファースト以外は認めない!!……ということで、僕の頭の中にある Pure Pascal 文法で書きました。
それが以下になります。

program HelloWorld(Output);
 
type
THex = (
hx00, hx01, hx02, hx03, hx04, hx05, hx06, hx07,
hx08, hx09, hx0a, hx0b, hx0c, hx0d, hx0e, hx0f,
hxCount
);
 
procedure WriteChar(h, l: THex);
begin
Write(Chr(Ord(h) * Ord(hxCount) + Ord(l)));
end;
 
begin
WriteChar(hx04, hx08); // H
WriteChar(hx06, hx05); // e
WriteChar(hx06, hx0c); // l
WriteChar(hx06, hx0c); // l
WriteChar(hx06, hx0f); // o
WriteChar(hx02, hx00); //
WriteChar(hx05, hx07); // W
WriteChar(hx06, hx0f); // o
WriteChar(hx07, hx02); // r
WriteChar(hx06, hx0c); // l
WriteChar(hx06, hx04); // d
end.

まあ、どっちにせよ、あんまり面白くはならなかったのですが……。
特筆すべき事は何もないです。
ちょっと悔やまれるのは、hxCount なんてのをつくったところ。
そうじゃなくて、shl Ord(hx04) ってすれば良かった。

出題者の @cielavenir さんのコードはこちら。
その他の言語は一個上に。

1 を取得してから、それを使って他の数を取得、という方法のようですね。
それで、ちょっと疑問なのが、Delphi や gpc ではコンパイル通るんだけど、Integer を Char にキャストして値を得ていること。
Pure Pascal だと、これできないんじゃなかったかなあ……そのための Chr 関数な訳だし……。
C 系で言うところの 1byte を表す型は Byte っていう型があるんですよ-。
2013/09/19 13:10 追記
cielavenir さんに修正していただきました!

後日、解説記事がアップされるそうなので、解説記事がアップされたら、ここに追記します!

2012年9月26日水曜日

FMX.Types.TCanvas.DrawLine のアンチエイリアスと DDA




twitter で LandscapeSketch (WorkToolSmith) さんから、アンチエイリアスの付いていない線を引きたい!というお話しがありました。

FireMonkey で普通に線を描こうと思ったら、↓こんなコードになります。

// FBmp は TBitmap のインスタンス
FBmp.Canvas.BeginScene;
try
  FBmp.Canvas.DrawLine(
    TPointF.Create(0, 0),
    TPointF.Create(FBmp.Width, FBmp.Height),
    1);
finally
  FBmp.Canvas.EndScene;
end;

そうすると描ける線は↓こんな風にアンチエイリアスがかかって非常にキレイな線になります。



↓線を拡大したところ


大体のケースでは線がキレイになるし問題ないんだろうと思いますが、アンチエイリアスが不必要な場合もあります。
たとえば、領域指定でアンチエイリアスの扱いをどうするのかとか……?

では、アンチエイリアスを外そう!と思っても、一筋縄ではいきません。
というのも、FireMonkey の TCanvas は複数の実装があり、TCanvas は、それらの Abstract クラスでしかないからです。

Direct2D が使える環境では TCanvasD2D が実装で、それ以外の GDI+ が使える環境では TCanvasGdiPlus が実装です。
もちろん Mac であれば、また違って TCanvasQuartz が実装になっています。

そうなると、アンチエイリアスを外そうにも実装によって異なっているため、非常に面倒な作業になります。
そもそも、アンチエイリアスを解除できない場合も想定されます。

TCanvasGdiPlus  だけアンチエイリアスを外そうと思えば外せそうです。
それは GDI+に SetSmoothingMode という API があるからです。
ただし、TCanvasGdiPlus が持っている TGPGraphics のインスタンスは private 変数& pirvate メソッドでしか取得できないため、リフレクションを使うなど手荒な技が必要になります。

なお、実際にアンチエイリアスをかける実体は TStrokeBrush のインスタンスである TCanvas.Stroke です。

そういったわけで、FireMonkey でアンチエイリアスを外すくらいなら、自分で描いちゃおうぜっていうお話しです。

自分で線を描くためには DDA を使います。
DDA は Digital Differential Analyzer の略で日本語では「デジタル微分解析器」とかそんな意味です。

線を描くに当たって問題なのは「ディスプレイは画素の集まり」であって連続的な線を表せるわけでは無い事です。
そのため、始点から終点まで、どの画素を通るのかということを知る必要があります。
その計算をするのが(ここでの)DDA です。
その名の通り微分するので傾きが求まり、それをデジタル値として返します。

また、DDA が優れているのは、加算・減算・論理演算、のみで実装可能なことです。乗算や除算、小数演算などが必要無いため、非常に高速に実行できます。
ここでは Delphi 言語で実装していますが、機械語での実装も容易でしょう(有効かどうかはともあれ……)。

ということで、DDA で線の描画を実装したサンプルが↓です。


サンプルソースはここから(GitHub)


詳しい説明は、昔のデブキャンの資料(PDF)をどうぞ……と思ったら、そんなに詳しく書いていなかった件……。

大まかに説明すると、4つに場合分けします。

  • 幅の方が大きい場合
    • 始点の方が小さい
    • 終点の方が小さい
  • 高さの方が大きい場合
    • 始点の方が小さい
    • 終点の方が小さい

つまり、必ず小さい方から大きい方に動くように正規化してやります。
コードでは↓こんな感じです。

procedure DDA(
  iX1, iY1, iX2, iY2: Integer;
  const iOnDDAEvent: TDDAEvent;
  const iData: Pointer);
var
  (略)
begin
  XSize := abs(iX2 - iX1);
  YSize := abs(iY2 - iY1);

  if (XSize > YSize) then begin
    // 水平方向の方が広い場合
    Flag := XSize shr 1;

    if (iX1 < iX2) then begin
      // 左の方が小さい場合
      (略)
    end
    else begin
      // 右の方が小さい場合
      (略)
    end;
  end
  else begin
    // 垂直方向の方が広い場合
    Flag := YSize shr 1;

    if (iY1 < iY2) then begin
      // 上の方が小さい場合
       (略)
    end
    else begin
      // 下の方が小さい場合
      (略)
    end;
  end;
end;

それぞれの場合において、地道に X(n) に +1 をしていって、X(n+1) の値になったら Y(n)→Y(n+1) とするだけです。

全文ソースは GitHub に上がっているので、ご覧ください。

そんなこんなで DDA を使って書いた線がこちら。
赤がDDA で、青が通常の方法です。





赤の線はガックガクなのが判りますね