Languages/Ruby or Rails2008.10.10 11:00

Ruby를 주마간산식으로 배운 사람에게는 감동적인 코드...

module ActionController
  module StatusCodes #:nodoc:
    # Defines the standard HTTP status codes, by integer, with their
    # corresponding default message texts.
    # Source: http://www.iana.org/assignments/http-status-codes
    STATUS_CODES = {
      100 => "Continue",
      101 => "Switching Protocols",
      102 => "Processing",

      200 => "OK",
      201 => "Created",
      202 => "Accepted",
      203 => "Non-Authoritative Information",
      204 => "No Content",
      205 => "Reset Content",
      206 => "Partial Content",
      207 => "Multi-Status",
      226 => "IM Used",

      300 => "Multiple Choices",
      301 => "Moved Permanently",
      302 => "Found",
      303 => "See Other",
      304 => "Not Modified",
      305 => "Use Proxy",
      307 => "Temporary Redirect",

      400 => "Bad Request",
      401 => "Unauthorized",
      402 => "Payment Required",
      403 => "Forbidden",
      404 => "Not Found",
      405 => "Method Not Allowed",
      406 => "Not Acceptable",
      407 => "Proxy Authentication Required",
      408 => "Request Timeout",
      409 => "Conflict",
      410 => "Gone",
      411 => "Length Required",
      412 => "Precondition Failed",
      413 => "Request Entity Too Large",
      414 => "Request-URI Too Long",
      415 => "Unsupported Media Type",
      416 => "Requested Range Not Satisfiable",
      417 => "Expectation Failed",
      422 => "Unprocessable Entity",
      423 => "Locked",
      424 => "Failed Dependency",
      426 => "Upgrade Required",

      500 => "Internal Server Error",
      501 => "Not Implemented",
      502 => "Bad Gateway",
      503 => "Service Unavailable",
      504 => "Gateway Timeout",
      505 => "HTTP Version Not Supported",
      507 => "Insufficient Storage",
      510 => "Not Extended"
    }

    # Provides a symbol-to-fixnum lookup for converting a symbol (like
    # :created or :not_implemented) into its corresponding HTTP status
    # code (like 200 or 501).
    SYMBOL_TO_STATUS_CODE = STATUS_CODES.inject({}) do |hash, (code, message)|
      hash[message.gsub(/ /, "").underscore.to_sym] = code
      hash
    end

    # Given a status parameter, determine whether it needs to be converted
    # to a string. If it is a fixnum, use the STATUS_CODES hash to lookup
    # the default message. If it is a symbol, use the SYMBOL_TO_STATUS_CODE
    # hash to convert it.
    def interpret_status(status)
      case status
      when Fixnum then
        "#{status} #{STATUS_CODES[status]}".strip
      when Symbol then
        interpret_status(SYMBOL_TO_STATUS_CODE[status] ||
          "500 Unknown Status #{status.inspect}")
      else
        status.to_s
      end
    end
    private :interpret_status

  end
end

해쉬 hash는 처음에 {}로 초기화된다. STATUS_CODES 내의 아이템은 하나씩 (code, message)로 전달된다. 이 때 message에 포함된 공백을 전부 ""로 대체하고, 소문자로 바꾼 다음, 심볼 (:ok와 같은)로 바꾸고 hash 객체의 key로 삼는다. 해당 심볼에 매핑되는 값은 code가 된다.

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Ruby or Rails2008.06.13 10:03

제가 번역했던 Beginning Ruby on Rails 책을 구매하신 분중 두 분깨서 다음과 같은 오류를 지적해 주셨습니다. 해결책을 포스팅하겠다고 약속했었는데 늦어서 죄송합니다.

이 문제는 Rails가 sqlite3를 기본 database로 사용하게 되면서 빚어진 문제입니다. MySQL을 데이터베이스로 사용하려면 rails <app name> --database=mysql 과 같이 해주어야 합니다. 그렇게 하지 않으면, 지적된 오류가 발생하게 됩니다.

Windows

Ruby 1.8.6 버전을 Windows에 설치한 다음 gem을 통해 rails를 설치하고 Rails 프로그램을 만들어 돌려보면 no such file to load -- sqlite3 라는 오류가 발생하는 것을 보게 됩니다. 이 오류의 원인은, Rails가 sqlite3를 기본적으로 설치하지 않는데서 빚어지는 것입니다. 따라서, 이 오류를 교정하기 위해서는 다음과 같은 절차를 밟아야 합니다. sqlite3를 설치하기 위한 절차입니다. 현재로서는 Windows에서 발생한 경우만 확인되었으므로, Windows에서의 해결방법을 기준으로 말씀드리겠습니다.

1. http://www.sqlite.org/download.html을 방문합니다.

2. 화면 좌측의 Precompiled Binaries for Windows 섹션에서 sqlite-3_5_9.zip과 sqlitedll-3_5_9.zip의 두 파일을 다운로드 받습니다. 이 두 파일의 압축을 ruby 설치 디렉터레 아래의 bin 이라는 이름의 서브디렉터리에 풀어 놓습니다. 그 결과 해당 디렉터리 아래에 sqlite3.exe sqlite3.dll sqlite3.def의 세 가지 파일이 생겨있어야 합니다.

3. 그런 다음에 명령행에서 gem install sqlite3-ruby를 실행합니다. 화면에 여러 가지 옵션들이 나오게 될텐데, 1번 옵션에 나오는 sql3-ruby 1.2.2 (mswin32)를 선택합니다.

Bulk updating Gem source index for: http://gems.rubyforge.org
Select which gem to install for your platform (i386-mswin32)
 1. sqlite3-ruby 1.2.2 (mswin32)
 2. sqlite3-ruby 1.2.2 (ruby)
 3. sqlite3-ruby 1.2.1 (mswin32)
 4. sqlite3-ruby 1.2.1 (ruby)
 5. Skip this gem
 6. Cancel installation
> 1

위와 같이 하면 이제 sqlite3가 설치될 것입니다. 여기까지 하면 sqlite3 관련 오류는 해결이 되어야 합니다.

Linux (Ubuntu)

Ubuntu에서는 원래

sudo apt-get install sqlite3
sudo gem install sqlite3-ruby

위의 두 과정이 제대로 수행되면 문제가 없이 되어야 합니다.
그런데 간혹 두 번째 명령을 실행하다가 no such file to load -- mkmf 라는 메시지가 나오면서 설치가 제대로 안되는 경우가 있습니다. 그런 경우에는

sudo apt-get install ruby1.8-dev

위의 명령을 먼저 실행해 줍니다.

그런 다음 다음과 같이 하면 됩니다.

bjlee@bjlee-ubuntu804:~$ sudo gem install sqlite3-ruby
Select which gem to install for your platform (i486-linux)
 1. sqlite3-ruby 1.2.2 (mswin32)
 2. sqlite3-ruby 1.2.2 (ruby)
 3. sqlite3-ruby 1.2.1 (mswin32)
 4. sqlite3-ruby 1.2.1 (ruby)
 5. Skip this gem
 6. Cancel installation
> 2

그러면 다음과 같은 메시지가 주욱 나오면서 설치가 될 것입니다.

Building native extensions.  This could take a while...
Successfully installed sqlite3-ruby-1.2.2
Installing ri documentation for sqlite3-ruby-1.2.2...
Installing RDoc documentation for sqlite3-ruby-1.2.2...

이제 웹 서버를 내렸다가 다시 올리기만 하면 문제가 해결되어야 합니다.

감사와 사죄의 말씀

이 오류를 지적해주신 분은 킹상수님과 딸기벌레님, 두분입니다. 답변은 구글에 문의하면 찾을 수 있는 내용이긴 합니다만, 제가 빠른 답변을 드리겠다고 해 놓고 답을 제때 못드려서 죄송하다는 말씀, 다시한번 드립니다.

질문주신 두분께는 메일로 따로 연락드리도록 하겠습니다.



신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 까아~

    이게 문제였군요!! ㅠㅠ 안되서 설치만 여러번 한것 같아요...
    감사합니다. ^ㅡ^ 책은 구매해서 잘 보고있어요. 행복한 하루 되세요..

    2008.08.29 10:08 신고 [ ADDR : EDIT/ DEL : REPLY ]

생각하는 프로그래밍(Programming Pearls)의 8장을 보면, 최대 부분합을 구하는 알고리즘을 고안하는 절차가 기술되어 있습니다. 이 책의 다른 부분도 멋집니다만, 저는 특히 이 8장이 굉장히 멋지다고 생각합니다. (아직도 이해가 잘 안되는 부분이 있긴 하지만 말입니다.)

Network Algorithmics라는 책이 있습니다. 이 책은 인터넷을 아우르는 네트워크 장비들이 오늘날같이 발전되어 오기까지 수많은 연구자들이 알고리즘을 어떤 식으로 개선시켜왔는지를 보여주는 멋진 책입니다. 그런 면에서 보면, Programming Pearls와 지향하는 바가 같다고 볼 수도 있겠습니다.

이런 책을 읽다보면 느끼는 것입니다만, 알고리즘의 개선은 알고리즘이 실제로 적용될 데이터와, 알고리즘이 돌아갈 시스템에 대한 깊은 이해가 없이는 성취하기 힘든 것이 아닌가 하는 생각이 듭니다. 그런 것들을 이해한 뒤에야, 결국 "문제 그 자체"를 제대로 이해했다고 말할 수 있다는 것이죠. 결국 우리가 알고리즘을 통해 풀어야 하는 문제는, 알고리즘과 데이터, 그리고 시스템에 걸쳐 퍼져 있음을 이해해야 한다는 뜻이 되겠군요.

각설하고.

이 책의 8장 4절을 보면 O(n)의 복잡도를 갖는 scanning 알고리즘이 나와 있습니다. 이 부분을 보기 전에 저도 같은 문제를 (아무 생각없이) Ruby로 한 번 풀어봤습니다. 제가 알고리즘을 구현할 때 착안한 점은 다음과 같습니다.

부분합이 음수가 되면, 그 때 까지 계산한 부분합은 버려도 된다
그 아이디어를 대락 십분 정도 걸려서 구현하고, 정확성을 검증한 다음에, 책에 나온 알고리즘과 성능 비교를 한 번 해봤습니다. 소스 코드는 다음과 같습니다. 원래 책에 나온 대로 구현한 알고리즘이 first_version이고, 제가 구현한 형태의 알고리즘이 second_version입니다.

def make_array(size)
  array = []
  size.times do
    array << rand(10000) - 5000
  end
  return array
end

def max(l, r)
  return (l>r)?l:r
end

def first_version(array)
  maxsofar = 0
  maxendinghere = 0
  for i in (0..array.length-1)
    maxendinghere = max(maxendinghere + array[i], 0)
    maxsofar = max(maxsofar, maxendinghere)
  end
  return maxsofar
end

def second_version(array)
  sum = 0
  left = 0
 
  max_sum = 0
  max_left = 0
  max_right = 0
 
  for i in 0 .. array.length - 1
    sum  = sum + array[i]
    if ( sum < 0 )
      sum = 0
      left = i+1
    elsif ( sum > max_sum )
      max_sum = sum
      max_left = left
      max_right = i
    end
  end

  return array.length, max_sum, max_left, max_right
end

def print_values(*value_array)
  (0..value_array.length-1).step(1) do |i|
    print value_array[i]
    if ( i != value_array.length-1 )
      print ", "
    end
  end
  puts ""
end

#
# main starts
#

(10000..150000).step(10000) do |i|
 
  test_array = make_array(i);
 
  start_time = Time.now.to_i
  max = first_version(test_array)
  end_time = Time.now.to_i
 
  print_values("f", test_array.length, max, (end_time - start_time))
 
  start_time = Time.now.to_i
  len, max, left, right = second_version(test_array)
  end_time = Time.now.to_i
 
  print_values("s", len, max, left, right, (end_time - start_time))
end

성능 비교 결과는 다음과 같습니다. 원래 알고리즘의 실행 결과는 그 줄 앞에 "f"가 붙어 출력되고, 제가 만든 알고리즘의 실행 결과는 그 줄 앞에 "s"가 붙어 출력되므로 확인하기 어렵지는 않습니다.

f, 10000, 671773, 0
s, 10000, 671773, 884, 6580, 0
f, 20000, 572515, 0
s, 20000, 572515, 1839, 13202, 0
f, 30000, 540911, 0
s, 30000, 540911, 9561, 25188, 0
f, 40000, 1074401, 0
s, 40000, 1074401, 11395, 35425, 0
f, 50000, 1138364, 0
s, 50000, 1138364, 27126, 34215, 0
f, 60000, 539056, 1
s, 60000, 539056, 19715, 29205, 0
f, 70000, 560118, 0
s, 70000, 560118, 59001, 69944, 0
f, 80000, 1785579, 0
s, 80000, 1785579, 11987, 79133, 0
f, 90000, 961688, 1
s, 90000, 961688, 57476, 82005, 0
f, 100000, 1058398, 0
s, 100000, 1058398, 38817, 73416, 0
f, 110000, 1226686, 1
s, 110000, 1226686, 62664, 107244, 0
f, 120000, 1368168, 0
s, 120000, 1368168, 77202, 119980, 0
f, 130000, 1585253, 0
s, 130000, 1585253, 30075, 100395, 0
f, 140000, 1203882, 1
s, 140000, 1203882, 43789, 99896, 0
f, 150000, 653564, 0
s, 150000, 653564, 16504, 41747, 1

그 결과, 원래 알고리즘보다 이해하기도 훨씬 낫고 (정말로?) 원래 알고리즘보다 더 많은 정보를 제공하는 알고리즘을 만들 수 있었습니다. 조금 깊이 들여다보면 알수 있는 일입니다만, 제가 구현한 알고리즘은 원래 알고리즘과 똑같은 알고리즘입니다. 소스 코드의 양만 많을 뿐이죠.

사실 제가 만든 알고리즘은 원래 이중 루프를 사용하고 있었습니다(부분합이 음수가 되면 버리는 로직이 있어서 이중 루프를 쓰더라도 선형적으로 수행될 것으로 기대했었습니다. 하지만 그렇게 하니까 최악 수행 시간이 굉장히 나빠지는 문제가 있더군요). 그런데 오늘 이 글을 적으면서 안쪽 루프가 필요 없다는 사실을 깨달았고, 결국 코드를 고쳤죠.

이 글을 적기 위해 샘플 프로그램을 만들면서 느낀 것이지만, 역시 가장 중요한 것은 알고리즘을 어떻게 만들어 나갈 것이냐 하는 문제입니다. 원래 제가 만들었던 second_version 함수의 성능은 오늘 뜯어고친 버전에 비해 그 성능이 굉장히 좋지 못했습니다. 그런 알고리즘이 누군가에게 팔릴 소프트웨어의 핵심적인 부분에 들어가 있었다면, 누구도 그 소프트웨어를 사지 않았을 것입니다.

그리고 한편으로는 이런 생각도 듭니다. 책에 나오는 O(n) 알고리즘은 사실 이해하기가 여간 어려운 것이 아닙니다. 하지만 그 알고리즘을 조금 다른 각도로 바꿔서 생각해 보면, 결국 제가 마지막으로 구현한 알고리즘과 동일한 일을 하는 알고리즘임을 깨달을 수 있습니다. 제가 알고리즘을 만들기 시작하면서 착안했던 지점과 책의 저자가 착안했던 지점은 조금 다릅니다만, 결국 알고리즘을 다듬고 다듬고 다듬어 얻은 결과물은 똑같아진 것이죠.

결국, 알고리즘을 만드는 데 있어서 중요한 것은 "문제를 끊임없이 개선해 나가려는 노력"이라는 소리가 되겠군요. 그런데 실무에서 프로그래밍을 하다 보면, 이런 사소한 진리를 망각하게 되는 일이 아주 빈번히 벌어집니다. 적당히 돌아가게 만들어 둔 알고리즘을 프로그램 안에 넣어놓고 연동시험이 끝나는 순간 그에 대해 잊어버리는 것이죠. (물론 그런 부분이 존재한다는 사실은 나중에 프로파일링 과정에서 드러나기는 하겠습니다만...)

위의 예제를 구현하면서, 새삼 제가 예전에 저질렀던 수많은 게으름의 흔적들이 떠올라 부끄러워졌습니다. 앞으로는 좀 더 열심히 해야겠어요. :-)




신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 아.. 저두 스캐닝 알고리즘을 한참 동안 봤습니다. ^^

    2008.10.09 14:46 신고 [ ADDR : EDIT/ DEL : REPLY ]