Processingで正三角形を描画する

Processingで正三角形を描画しようと思い、コードを書き始めたけど、すぐに手が止まってしまった。triangle()を使って、3点の座標位置を指定すれば三角形を描画できる。だけど、3点の座標位置はどう決めたら良いのだろうか?

そもそも正三角形とはどういう形をしているのか。Wikipediaには次のように書いてある。

正三角形(せいさんかくけい、英: equilateral triangle)は、正多角形である三角形である。つまり、3本の辺の長さが全て等しい三角形である。3つの内角の大きさが全て等しい三角形と定義してもよい。1つの内角は 60°(π/3 rad)である。また一つの内角が60°である二等辺三角形は正三角形となる。

正三角形 - Wikipedia

f:id:noriok:20120917173025p:plain

3本の辺の長さが等しい、3つの内角の大きさが全て等しい、1つの内角は60°ということは理解していた。では、そこから正三角形の3点の座標位置はどうなると言えるのか。上のページを読み進めると、正三角形の高さは、辺の長さをaとすると、a*sqrt(3)/2 となると書かれてある。つまり、高さは整数にはならない。

さらにページを読み進めると、正三角形の3点の座標位置の求め方も書いてある。これを使えば3点の位置も求めることが出来るけれど、なぜそのような値になるのか考えてみた。正三角形に何本かの補助線を引いてみると、意外に簡単に計算できた。

f:id:noriok:20120917173004p:plain

上図の正三角形の内部にみえる小さな三角形は全て、それぞれ内角が30°、60°、90°の直角三角形になっている。b, c, dの値はそれぞれ次のようになる。

b = a/2 * sin60°
c = a/2 * cos60°
d = c * tan30°

点Aの座標は、(x, y-(b+d))となる(y座標は下向きを正としている)。点Bと点Cの座標は、点の回転公式より求めた。

(x, y)を原点のまわりに角aだけ回転して(x', y')に移されるとき、
x' = x*cos(a) - y*sin(a)
y' = x*sin(a) + y*cos(a)

Processingプログラム:

// -*- mode: java -*-

class EqTriangle { // equilateral triangle
    float x, y, n;
    public EqTriangle(int x, int y, int n) {
        this.x = x;
        this.y = y;
        this.n = n;
    }
    public void draw(int angle) {
        float b = this.n * sqrt(3) / 4.0;
        float c = this.n / 4.0;
        float d = c * tan(radians(30));

        float x1 = 0;
        float y1 = -(b + d);

        float r = radians(120);
        float x2 = x1*cos(r) - y1*sin(r);
        float y2 = x1*sin(r) + y1*cos(r);

        float x3 = x1*cos(r*2) - y1*sin(r*2);
        float y3 = x1*sin(r*2) + y1*cos(r*2);

        pushMatrix();
        translate(this.x, this.y);
        rotate(radians(angle));
        triangle(x1, y1, x2, y2, x3, y3);
        popMatrix();
    }
}
  
EqTriangle t1, t2, t3;

void setup() {
    size(400, 400);

    t1 = new EqTriangle(width/2, height/2, 60);
    t2 = new EqTriangle(width/2, height/2, 100);
    t3 = new EqTriangle(width/2, height/2, 140);
}

void draw() {
    background(255);

    noFill();
    t1.draw(frameCount);
    t2.draw(-frameCount);
    t3.draw(frameCount);
}

実行すると、以下のように正三角形がくるくると回転します。

f:id:noriok:20120917172939p:plain

primesパッケージをインストール

primesパッケージをインストールします。

cabal install primes
Prelude Data.Numbers.Primes> take 30 primes
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113]
Prelude Data.Numbers.Primes> primes !! 0
2
Prelude Data.Numbers.Primes> primes !! 9999
104729
Prelude Data.Numbers.Primes> primeFactors 1234567
[127,9721]
Prelude Data.Numbers.Primes> primeFactors 12345
[3,5,823]
Prelude Data.Numbers.Primes> filter isPrime [1..100]
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]

Project Eulerの問題を解くときに使ってみよう。

参考:

Codeforces 37A - A. Towers

37A - A. Towers

import Data.List

calc :: [Int] -> (Int, Int)
calc xs = (maximum [count x xs | x <- xs], length $ nub xs)
  where
    count a xs = sum [1 | x <- xs, x == a]

main = do s <- getLine
          t <- getLine
          let (x, y) = calc $ map read $ words t
          putStrLn (show x ++ " " ++ show y)

個数を求めるのに、1 からなるリストを sum するのはどうも好きになれない。他に良い方法はないだろうか。

Codeforces 122A - A. Lucky Division

122A - A. Lucky Division

isLucky :: Int -> Bool
isLucky n = show n == filter (\c -> c == '4' || c == '7') (show n)

calc :: Int -> String
calc n = if null [i | i <- [1..n], isLucky i, n `mod` i == 0]
         then "NO"
         else "YES"

main = do s <- getLine
          putStrLn $ calc $ read s

第4回 スタートHaskell2に参加してきました

第4回 スタートHaskell2 - [PARTAKE] に参加してきました。

  • 第8章「入出力」@igrep さん
    • IO action
    • putStrLn は文字列を引数にとり、()(空のタプル。unit型ともいう)を結果とする I/O アクションを返す
    • do記法。IO actionをつなぎ合わせる。
    • >>= 関数の構文糖らしい main = getLine >>= print
    • when b f = bがTrueの時だけfを実行
    • forever
  • 第9章「もっと入力、もっと出力」 @mizu_tama さん
    • ファイル操作。自由度の高い方から openFile, withFile, (readFile, writeFile, appendFile)
    • System.Randomモジュール
    • getStdGen
    • newStdGen
    • bytestring, Stringより効率よく文字列を扱う

練習問題

練習問題は、エコーと九九の表だけやりました。

練習問題: エコー

ユーザーの入力をそのまま表示するプログラムを作ろう。

ただし “quit” と入力されたときは “Do you really want to quit? (yes/no)” と表示して、次の入力が “yes” だったらプログラムを終了する。

文字出力の際、バッファリングの関係ですぐに表示されないことがある。そのときは hFlushを使うとよい。

import Control.Monad
import System.IO (hFlush, stdout)

main = do
    x <- getLine
    if x == "quit"
    then do
        putStrLn "Do you really want to quit? (yes/no)"
        hFlush stdout
        y <- getLine
        when (y /= "yes") $ do
            main
    else do
        putStrLn x
        hFlush stdout
        main

練習問題: 九九の表

こんな感じの九九の表を表示してみよう。

 1  2  3  4  5  6  7  8  9
 2  4  6  8 10 12 14 16 18
 3  6  9 12 15 18 21 24 27
 4  8 12 16 20 24 28 32 36
 5 10 15 20 25 30 35 40 45
 6 12 18 24 30 36 42 48 54
 7 14 21 28 35 42 49 56 63
 8 16 24 32 40 48 56 64 72
 9 18 27 36 45 54 63 72 81

Text.Printfモジュールを使うと、いわゆるprintf関数がつかえるようです。printfは IO として扱われると、文字列を出力し、Stringとして扱われるとStringを返すようです。素晴らしいですね! 参考: Text.Printf.printf を使ってみる - sirocco の書いてもすぐに忘れるメモ

import Text.Printf

qq :: [[Int]]
qq = [map (*i) [1..9] | i <- [1..9]]

format :: Int -> String
format n = printf "%3d" n

formatList :: [Int] -> String
formatList xs = unwords $ map format xs

main :: IO ()
main = mapM_ putStrLn $ map formatList qq