読者です 読者をやめる 読者になる 読者になる

platexインストールでエラーが出たので対処した.

sudo port install ptex +utf8 +motif

とコマンドを打つと,

Error: Unable to execute port: invalid command name "use_parallel_build"

と言われたので調べてみると
http://developer.mozilla.org/ja/docs/Mac_OS_X_Build_Prerequisites

port invalid command name "use_parallel_build": update MacPorts to version >= 1.600 with 'sudo port selfupdate'.

らしい.sudo port selfupdateをしてみるとうまくいった.簡易メモ迄.僕はまたも中間報告と院試で死にそうなのだ.

3のつく数字と3の倍数だけAhoと表示するプログラムを出来るだけ短く書け.

と、id:shawshank99にミッションを言いわたされたので,Rubyでやってみた.

gets.to_i.times{|i|p((i%3==0||i=~/3/)&&i>0 ? "Aho" : i)}

56文字也.なんかずるい気がするけどこれでいいや.
僕は研究室の中間報告会が近くて死にそうなのだ.

追記.

shawshank99>i>0 この条件を何とか取り除けそうな気がするんだが無理かなぁ

とのことなので,やってみました.

(1..gets.to_i).each{|i|p i%3==0||i=~/3/ ? :Aho: i}

51文字.i>0以外にも削れるところがあった.

rakeで undefined method `last' for {}:Hash rake db:migrateと言われた。

このページで答えを発見。
Ruby on Rails: Talk
Railsを2.0にしたら,rakeまでバージョンがあがっていたらしい.
つまり,Rails2.0入れた後,Rails1.2でつくったアプリにrake使おうとするとこうなる.
Rails1.2にはrake0.7.3が宜しいらしいので,

sudo gem install -v=0.7.3 rake 

と,rakeをバージョン指定してインストールします.
確認してみると,

$ gem list rake

*** LOCAL GEMS ***

rake (0.8.1, 0.7.3)

異なったバージョンのrakeがインストールされているのが分かります.
使い方は

rake _0.7.3_ #{command}

とすればよろしい.

参考URL

  1. Rubyist Magazine - RubyGems (1)
  2. Rubyist Magazine - RubyGems (2)
  3. gemでバージョン指定してパッケージをインストールする

与えられた文字列を、一文字づつ分解して「る」をつけたし出力。

#!/usr/local/bin/ruby -Ku
gets.split(//).each do |char|
  print char <<
  if (char == " " or char == " " or char == "\n") 
    ""
  else
    ""
  end
end

実行結果

$ ruby ru.rb 
ぶんぶんぶん はちがとぶ

ぶるんるぶるんるぶるんる はるちるがるとるぶる

なんの意味もなし。ifの戻り値を文字に付加しています。
実行時には環境の文字コードに注意です。

IndexOfで特定条件下の2重ループをやや高速に。

この方法は、たとえば試験を受けた人のIdと名前だけが格納された配列と合格基準に逹した人のIdだけが格納された配列があり、そのデータから合格基準に達した人の名前を表示したい場合に有効です。非常にかぎられた条件なので使うことはないかも。
普通ならこのように書きます。studentsはidとnameをもつstudentクラスのインスタンスが格納された配列とし、passersは合格者idの格納された配列とします。

var studentsCnt:uint  = students.length();
var passersCnt:uint   = passers.length();

for(var i:uint=0; i<studentsCnt; i++){
  for(var j:uint=0; j<passersCnt; j++){
    if(students[i].id == passers[j]){
      trace(student[i].name);
    }
  }
}

速度的に嫌なニオイのするコードです。これを次のように書いてみます。

var studentsCnt:uint  = students.length();
var passersCnt:uint   = passers.length();
var passersId:String;

for(var i:uint=0; i<passersCnt; i++){
  passersId += passers[i] + ","; /* 区切り文字を付加 */
}
for(var i:uint=0; i<studentsCnt; i++){
  if(passersId.indexOf(students[i].id) > 0){
    trace(student[i].name);
  }
}

Idをひとつづつ区切って文字列とし、indexOfで判定するわけです。これで意外と高速化できます。ちょっと小賢しいかな。

RailsのpaginationでFlash(AS3)にデータを渡す。

webアプリケーションでは、大量のデータをViewに表示する際に、画面が長くなりすぎないよう、またレスポンス速度向上のため、表示を小分けする事があります。googleの検索結果のような、n件〜x件までを表示し「次へ」「前へ」リンクがある画面がそれです。Railsにはpaginationという機能が実装されていて*1、この小分けが非常に簡単に行えます。データを表示したいとき、railsで既に検索ロジックが実装されている場合、これを利用して検索結果をかっこ良く表示できたら楽しいですね。今回はとりあえず読み込みまでを作ってみる事にします。
データベースにはbooksテーブルが作られていて、id,名前,著者(訳者)の名前がレコードとして記録されているとします。こんな感じです。




このbooksは僕のbooklogからリストをコピペして、ActiveRecordでまるまるレコードを作成したものです。一応コードは以下のような感じ。コピペしたリストはbook_dataという名前で保存してあり、それを読み込んでいます。

#ruby 1.8.6 (2007-09-23 patchlevel 110) [i686-darwin8.11.1]
require "rubygems"
require 'active_record'

ActiveRecord::Base.establish_connection(
  :adapter    =>      'mysql',
  :host       =>      'localhost',
  :username   =>      'root',
  :password   =>      '',
  :database   =>      'test',
  :socket     =>      '/tmp/mysql.sock',
  :encoding   =>      'utf8'
)
 
class Book < ActiveRecord::Base
end

open("book_data") {|file|
  while l = file.gets
    line = l.split(/\//)
    book = Book.new(:name => line[0],:author => line[1])
    redo unless book.save
  end
}

つぎに、デ ータベースからbooksを読み込んでpaginateするrailsのコントローラですが、データをAS3で読み込むにはXMLで出力するべきでしょう。AS3側ではE4Xが使えるからです。paginateは:per_pageで指定された個数分レコードを取得しますが、実はクエリのpage=nを見て、page[n]を呼び出しています、paginateで取得したpageはArrayぽく振る舞うようです。80件のレコードを持つテーブルから10件ずつ取得している場合、8ページになりますが、試しに9ページ目を取得するようにクエリを投げると1ページ目が取得されます。
この例では

http://localhost:3000/book/list?page=n

によって指定されたページのレスポンスが返ります。ここでは、AS3側でこのurlにnを増やしつつリクエストを投げます。
しかし、nがいくつになるまで、すなわち何ページまで取得すればいいのかが分かりません。
仕方ないのでxmlファイルに最大ページがいくつであるかを書いておく事にします。

class BookController < ApplicationController
  def list
    page, books = paginate(:books, :per_page => 10)
    #xmlのヘッダ
    xmlhead = %(<?xml version="1.0" encoding="UTF-8"?>)
    (xml ||=[]) << xmlhead
    
    xml << "<root>"
      #総ページ数の情報
      xml << "<info>"
      xml << "<pageMax>"+page.last.number.to_s+"</pageMax>"
      xml << "</info>"
      #to_xmlメソッドで自動的に付け足されるヘッダを削除
      xml << books.to_xml.gsub(xmlhead,"")
    xml << "</root>"

    render :xml => xml.join("\n")
  end
end

こんな感じに出力されました。




あとはas3で取得するだけ。今回はTimerを使って自動で次のページを取得しています。

package{
  import flash.display.Sprite;
  import flash.text.TextField;
  import flash.net.URLLoader;
  import flash.net.URLRequest;
  import flash.utils.Timer;
  import flash.events.*;
  
  public class PaginateReader extends Sprite {
    private const targetURL:String = "http://localhost:3000/book/list?page=";
    private const loadPageInterval:uint = 1000;
    private var bookIndex:uint = 0;
    private var pageContainer:Array = new Array();
    
    function PaginateReader(){    
      var ULoader:URLLoader = new URLLoader();
      var URequest:URLRequest = new URLRequest();
      var pageIndex:uint = 2;
      /*1ページ目のXMLを取得し終わったら、最大ページ分loadPage呼び出し*/
      ULoader.addEventListener(Event.COMPLETE, 
        function():void{
          var page:XML = new XML(ULoader.data);
          var loadTimer:Timer = new Timer(loadPageInterval, (page..info.pageMax-1));
          loadTimer.addEventListener(TimerEvent.TIMER,
            function():void {
              loadPage(pageIndex++);
            }
          );
          loadTimer.start();
          createPage(page);
          pageContainer[1] = page; //ページのXMLをコンテナに格納
        }
      );
      URequest.url = targetURL+"1";
      ULoader.load(URequest);
    }
    public function loadPage(index:uint):void{
      var pageLoader:URLLoader = new URLLoader();
      var pageRequest:URLRequest = new URLRequest();
      var pageURL = targetURL + index;
      pageLoader.addEventListener(Event.COMPLETE,
        function():void{
          var page:XML = new XML(pageLoader.data);
          pageContainer[index] = page; //ページのXMLをコンテナに格納
          createPage(page);  
        }
      );
      pageRequest.url = pageURL;
      pageLoader.load(pageRequest);
    };
    public function createPage(page:XML):void{
      for(var i:uint=0; i<page..books.book.length(); i++){ 
        createBook(bookIndex, page..books.book[i].name, page..books.book[i].author);
        bookIndex++;
      }
    };
    public function createBook(index:uint, name:String, author:String):void{
      trace((index+1)+":"+name+"/"+author);
    };
  }
}

とりあえずcreateBookで名前と著者名をtraceしていますが、ここで別Spriteなどを作ってaddChildすればTweenerなどでかっこ良く表示できるという事です。この例では1秒ごとに次ページを読み込んでいますが、レスポンス速度に合わせた時間にするといい感じです。

*1:rails2.0では標準で使えません。ここではrails1.2.5を使っています。

あみだくじを作ってみる。

言語を練習するとき、面白い問題があると便利ですね。どう書く?orgではそんな問題が沢山掲載されています。そこに乗っていた二つの問題を解いてみたのでメモしておきます。コードが稚拙なので投稿するのは憚られますが・・・。
まず1問目の問題を解きます。これは、与えられたあみだくじを解くだけの問題です。基本的な解法としてはあみだくじの行数分ループさせ、最初に与えられた1行をあみだくじに従って入れ替える事が考えられますが、この方法は既に投稿されているので、別の方法をとってみることにします。

class Walker
  LINE_MOVE_NUM = 2
  attr_reader :character
  attr_accessor :x, :y
  def initialize(character,x,y)
    @character = character
    @x = x
    @y = y
  end
  def go_to_next(line)
    @y += 1
    if line[@x+1,1] == "-"
      @x += LINE_MOVE_NUM
    elsif line[@x-1,1] == "-"
      @x -= LINE_MOVE_NUM
    end
  end
end
amidakuji = <<END_OF_AMIDAKUJI
A B C D E
| | |-| |
|-| | |-|
| |-| |-|
|-| |-| |
|-| | | |
END_OF_AMIDAKUJI
result = ""
walkers = Array.new
amidakuji.each_line do |line|
  if walkers.empty?
    line.split(//).each_with_index do |c,i|
      print "#{c}"
      walkers << Walker.new(c,i,0) unless c == " "
      result << c
    end
  else
    walkers.each do |c|
      c.go_to_next(line)
      result[c.x,1] = c.character
    end    
    print("#{line.chomp} : #{result}")
  end
end
puts result

最初の一行を、あみだくじを進める人(Walker)オブジェクトと見なし、go_to_nextメソッドで次の行を見に行く仕組みです。エレガントでもなんでもないですが、Rubyでクラスを使う練習にはなりましたね。確認結果は次のようになります。

A B C D E
| | |-| | : A B D C E
|-| | |-| : B A D E C
| |-| |-| : B D A C E
|-| |-| | : D B C A E
|-| | | | : B D C A E
B D C A E

あみだくじの横の数字は、一行ごとに、そのときwalker(s)がいた座標を表します。もっと複雑なあみだくじも試してみます。

A B C D E F G H
| | |-| | |-| | : A B D C E G F H
|-| | |-| | |-| : B A D E C G H F
| |-| |-| |-| | : B D A C E H G F
|-| |-| |-| |-| : D B C A H E F G
|-| | | | |-| | : B D C A H F E G
B D C A H F E G

ちゃんと答えが出ています。ただし、このコードでは横棒が連なっている場合(|-|-|)は対処できずヘンな結果になります。

とりあえず解く事はできたので、2問目に進みます。これは、あみだくじの答え(結果?)からあみだくじ自体を生成するという問題です。生成されたあみだくじの一行目は、答えを昇順にソートしたものになります。ここではオブジェクトをつかって云々やると大変なので、ソートという特性を使ったコードを書きました。

def generate_amidakuji_from_result(arg)
  #配列をディープコピー
  sorted_list = Marshal.load(Marshal.dump(arg)).sort.to_s
  list        = Marshal.load(Marshal.dump(arg))
  amidakuji = Array.new
  i = 0
  #リストがソート済みの結果になるまでループ
  until(sorted_list == list.to_s)
    j = 0
    #列の最後まで走査
    while(list[j+1] != nil)  
      if(list[j].to_i > list[j+1].to_i)
        #入れ替え
        list[j],list[j+1] = list[j+1],list[j] 
        add = "|-|"
        j+=1
      else
        add = "|"
      end
      j+=1
      #列の最後ならば縦棒で閉じる。
      add << " |" if j < list.size && (list[j] == nil || list[j+1] == nil) 
      (amidakuji[i] ||= "") << add << " "
    end
    amidakuji[i].strip!
    i += 1
  end
  amidakuji
end

#list  = [3,1,5,5,1,2,4,5,5,7,3,1,6,2,4]
#list = [3,5,2,4,0,1]
list = ARGV
puts list.sort.join(" ")
puts generate_amidakuji_from_result(list).reverse
puts list.join(" ")

このコードには汚い点があります。until式で「リストがソート済みの結果になるまでループ」していることです。つまり、最初に受け取った配列をまずソートしてから文字列に直し、処理中の配列も文字列に変換し、毎ループごとに見比べるからです。これはパフォーマンス的にも問題があるし、なんかダサイですね。これ以外の方法を思いついたら教えてください。
結果は以下のようになります。rubyでこのコードを走らせるときは、引数にスペース区切りでリストを与えてください。問題の例を使うならば、コード中のコメントを外して使ってください。また、このコードでは1桁以上の数字はサポートできません。これはあみだくじの体裁のためです。

$ ruby amida2.rb 3 5 2 4 0 1
0 1 2 3 4 5
| |-| | | |
|-| |-| |-|
| |-| |-| |
|-| |-| |-|
| |-| |-| |
3 5 2 4 0 1

なんとか生成できたようです。ところで、どう書く?に投稿されているRubyコード(現時点)では同じ値を含むリストを渡されると正しく処理ができないようです。(問題文にないから当然ですが)このコードはそういったリストも処理できます。

$ ruby amida2.rb 3 3 3 3 3 3 2
2 3 3 3 3 3 3
|-| | | | | |
| |-| | | | |
| | |-| | | |
| | | |-| | |
| | | | |-| |
| | | | | |-|
3 3 3 3 3 3 2

1 1 1 2 2 3 3 4 4 5 5 5 5 6 7
| | |-| |-| | | | | | | | | |
| | | |-| |-| | | | | | | | |
| | | | |-| |-| |-| | | | | |
| | | | | |-| |-| |-| | | | |
| | | | |-| |-| |-| |-| | | |
| | | | | |-| |-| |-| |-| |-|
| | |-| |-| |-| |-| |-| |-| |
| |-| |-| |-| |-| |-| |-| | |
| | |-| |-| | | |-| |-| | |-|
|-| | |-| | | | | |-| | |-| |
3 1 5 5 1 2 4 5 5 7 3 1 6 2 4

1問目のWalkerをつかってあみだくじを解くと、正しくルーティングされている事がわかります。

1 1 1 2 2 3 3 4 4 5 5 5 5 6 7
| | |-| |-| | | | | | | | | | : 1 1 2 1 3 2 3 4 4 5 5 5 5 6 7
| | | |-| |-| | | | | | | | | : 1 1 2 3 1 3 2 4 4 5 5 5 5 6 7
| | | | |-| |-| |-| | | | | | : 1 1 2 3 3 1 4 2 5 4 5 5 5 6 7
| | | | | |-| |-| |-| | | | | : 1 1 2 3 3 4 1 5 2 5 4 5 5 6 7
| | | | |-| |-| |-| |-| | | | : 1 1 2 3 4 3 5 1 5 2 5 4 5 6 7
| | | | | |-| |-| |-| |-| |-| : 1 1 2 3 4 5 3 5 1 5 2 5 4 7 6
| | |-| |-| |-| |-| |-| |-| | : 1 1 3 2 5 4 5 3 5 1 5 2 7 4 6
| |-| |-| |-| |-| |-| |-| | | : 1 3 1 5 2 5 4 5 3 5 1 7 2 4 6
| | |-| |-| | | |-| |-| | |-| : 1 3 5 1 5 2 4 5 5 3 7 1 2 6 4
|-| | |-| | | | | |-| | |-| | : 3 1 5 5 1 2 4 5 5 7 3 1 6 2 4
3 1 5 5 1 2 4 5 5 7 3 1 6 2 4

また、次のようにStringクラスにto_iメソッドを追加すれば、文字列リストを渡しても文字列コードでソートしてくれます。*1

class String
  def to_i
    self
  end
end
$ ruby amida2.rb C D A E F D B
A B C D D E F
| |-| | | | |
| | |-| | | |
| | | |-| | |
| | | | |-| |
|-| | |-| |-|
| |-| | |-| |
C D A E F D B

$ ruby amida2.rb O W A T A  ^ o ^
A A O T W ^ ^ o
| |-| |-| | | |
|-| |-| | | | |
| |-| |-| | |-|
O W A T A ^ o ^

以上。