태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

'Daily life'에 해당되는 글 213건

  1. 2012.12.07 라즈베리파이에 웹캠 연결해서 비디오 덤프 (4)
  2. 2012.08.02 도깨비: 요청/응답 json 포맷 사용 + mongodb 저장소 붙이기 (6)
  3. 2012.07.26 MongoDB 읽고쓰기 (6)
  4. 2012.07.12 도깨비(tokebi) 더미 API (5)
  5. 2012.07.05 2012/07/05: Hello world in xcode (5)
  6. 2011.09.03 An introduction into SICP (6)
  7. 2011.07.20 먹고싶다: 떡
  8. 2011.07.17 그 힘으로 다시 나아간다
  9. 2011.07.15 EBS Space 공감 보다가 (2)
  10. 2011.07.13 Reading: How To Design Programs(HTDP)
  11. 2011.07.03 맥북프로 화면 구석이 노랗게 변색 (5)
  12. 2011.06.28 진화를 멈추라 (2)
  13. 2011.06.26 지를까 말까 지를까 말까 지를까 말까 (2)
  14. 2011.06.19 예당에서 한강까지
  15. 2011.06.18 Raindrops
  16. 2011.06.12 Animals in Everland (2)
  17. 2011.06.11 에버랜드 레이드
  18. 2011.05.25 -경- 생 일 -축-
  19. 2011.05.12 D 프로그래밍 언어 - 오타(?) (3)
  20. 2011.04.27 SICP study: 근황 (3)
  21. 2011.04.24 킨들 활용 - ? (1)
  22. 2011.04.20 The Mountain by tesophotography.com Terje Sorgjerd / Hammock - Floating Away In Every Direction
  23. 2011.04.11 48/2(9+3) (4)
  24. 2011.03.02 회고: 잉여로운 2011년 2월 (2)
  25. 2010.12.12 오늘의 삽질 (3)
  26. 2010.11.23 500에러 기념샷 for GMail (4)
  27. 2010.11.23 잉여로운 출근길
  28. 2010.10.30 2010년 10월 자출기록
  29. 2010.10.22 올해 마지막 지름 (4)
  30. 2010.10.21 예술의 전당 수묵화반, 전시회 준비 (3)

라즈베리파이에 웹캠 연결해서 비디오 덤프

Daily life/Hard study 2012.12.07 23:22

# 목표: 동영상을 캡쳐한다

# 들어가기 전에
    * opencv 라이브러리 설치: sudo apt-get install libopencv-*
    * pyopencv 설치: sudo pip install pyopencv
    * Doc: http://docs.opencv.org/

    * OpenCV 튜토리얼: http://docs.opencv.org/doc/tutorials/tutorials.html
    * OpenCV Python: http://opencv.willowgarage.com/wiki/PythonInterface
    튜토리얼이 많이 부족하다. 아직 작업중인데, 당신이 쓸거면 연락달라고 쓰인걸로 봐서 한동안은 기대할 수 없을듯.


# Modules:
     * core
     * imgproc
     * video
     * calib3d
     * features2d
     * objdetect
     * highgui
     * gpu
     * and so on …


# API Concepts

    * 모든 클래스와 함수는 cv 이름공간 안에 있다.
    * OpenCV 는 메모리 해제를 스스로 한다. 레퍼런스 카운트 시스템이 있다.
    * 대부분의 경우 함수의 결과물에 대한 메모리 할당도 스스로 한다.
    * 라이브러리 전체적으로 Saturation arithmetic 사용
    * 템플릿은 좀 자제했음 - 파이썬, 자바 등 다른 언어로 바인딩을 쉽게하고 인터페이스/구현 분리를 잘 하기 위해
    * 편의성 및 중복 제거를 위해 InputArray, OutputArray 가 여기저기서 사용됨
    * OpenCV 는 예외를 사용한다 (derived from cv::Exception)
    * fully re-enterable


# How to capture video?

내가 필요한건 highgui(http://docs.opencv.org/modules/highgui/doc/highgui.html) 에 있는 것 같다.
highgui 문서 앞쪽에 보면 아래와 같은 설명이 있다.

   * Create and manipulate windows that can display images and “remember” their content (no need to handle repaint events from OS).
   * Add trackbars to the windows, handle simple mouse events as well as keyboard commands.
   * Read and write images to/from disk or memory.
   * Read video from camera or file and write video to a file.


cv::VideoCapture 클래스가 있다. 그리고 API 페이지(http://docs.opencv.org/modules/highgui/doc/reading_and_writing_images_and_video.html#videocapture)를 보니, python, C 인터페이스까지 나와 있다. C/C++ 인터페이스가 별도라는 데 주의. 일단 C++ 로 먼저 해본다.

VideoCapture 클래스로 웹캠에서 프레임을 얻어내고, 이렇게 얻어낸 프레임들을 imwrite 로 이미지파일로 각각 저장하거나 VideoWriter 을 사용해서 영상파일로 덤프하자.


# C++ 코드 컴파일

    * /usr/include/opencv, /usr/include/opencv2 이렇게 디렉토리가 두개다. opencv 는 이전 인터페이스를 하위호환성때문에 유지하는 듯. opencv2/opencv.hpp 를 가져다 쓰자.
    * 링크할 때 -lopencv_core -lopencv_highgui 이렇게 core 와 사용할(한) 모듈을 주어야 한다. -lopencv 이 아니다.


# Code

#include <cstdio>
#include <boost/format.hpp>
#include <opencv2/opencv.hpp>

int main(int, char**) {
    cv::VideoCapture cap(0);
    if (!cap.isOpened()) return -1;

    cv::Mat frame;
    for (int i=0; i<10; ++i) {
        cv::Mat image;
        while (!cap.read(image))
            usleep(1000*100);
        std::fprintf(stderr, "%d\n", i);
        cv::imwrite((boost::format("%05d.jpg") % i).str(), image);
    }
    return 0;
}

근데 망했다. 파일이 나오긴 나오지만 다 그냥 까만 화면이다. 카메라에 설정값이 뭔가 안좋은듯 하다. BRIGHTNESS, CONTRAST, SATURATION, GAIN 를 모두 적당한 값(중간값)으로 세팅해봐야겠다. 일단 지금 값을 확인하기 위한 함수를 하나 만들고 스케일에 맞춰 적당한 값을 넣자.



0~255 스케일이라고 생각했는데 0.0~1.0 스케일이다. 0.5 를 넣는다.

#include <cstdio>
#include <boost/format.hpp>
#include <opencv2/opencv.hpp>

void printProperties(cv::VideoCapture& cap) {
    fprintf(stdout, "CV_CAP_PROP_POS_MSEC    : %f\n", cap.get(CV_CAP_PROP_POS_MSEC));
    fprintf(stdout, "CV_CAP_PROP_FRAME_WIDTH : %f\n", cap.get(CV_CAP_PROP_FRAME_WIDTH));
    fprintf(stdout, "CV_CAP_PROP_FRAME_HEIGHT: %f\n", cap.get(CV_CAP_PROP_FRAME_HEIGHT));
    fprintf(stdout, "CV_CAP_PROP_FPS         : %f\n", cap.get(CV_CAP_PROP_FPS));
    fprintf(stdout, "CV_CAP_PROP_MODE        : %f\n", cap.get(CV_CAP_PROP_MODE));
    fprintf(stdout, "CV_CAP_PROP_BRIGHTNESS  : %f\n", cap.get(CV_CAP_PROP_BRIGHTNESS));
    fprintf(stdout, "CV_CAP_PROP_CONTRAST    : %f\n", cap.get(CV_CAP_PROP_CONTRAST));
    fprintf(stdout, "CV_CAP_PROP_SATURATION  : %f\n", cap.get(CV_CAP_PROP_SATURATION));
    fprintf(stdout, "CV_CAP_PROP_GAIN        : %f\n", cap.get(CV_CAP_PROP_GAIN));
    fprintf(stdout, "CV_CAP_PROP_HUE         : %f\n\n", cap.get(CV_CAP_PROP_HUE));
}

int main(int, char**) {
    cv::VideoCapture cap(0);
    if (!cap.isOpened()) return -1;

    printProperties(cap);
    cap.set(CV_CAP_PROP_BRIGHTNESS, 0.5);
    cap.set(CV_CAP_PROP_CONTRAST, 0.5);
    cap.set(CV_CAP_PROP_SATURATION, 0.5);
    cap.set(CV_CAP_PROP_GAIN, 0.5);
    cap.set(CV_CAP_PROP_FRAME_WIDTH, 160);
    cap.set(CV_CAP_PROP_FRAME_HEIGHT, 120);
    printProperties(cap);

    cv::Mat frame;
    for (int i=0; i<10; ++i) {
        cv::Mat image;
        while (!cap.read(image))
            usleep(1000*100);
        std::fprintf(stderr, "%f\n", i);
        cv::imwrite((boost::format("%05d.jpg") % i).str(), image);
    }
    return 0;
}




select timeout 이 뜨면서 계속 까만 화면만 나온다. 구글링 결과 http://www.raspberrypi.org/phpBB3/viewtopic.php?f=37&t=11745 이런 페이지를 발견했다! 그리고 원래 잘 동작하던 uvccapture 로도 까만 스샷만 나오고 있다. 파이를 리붓해보자.



현재 라즈베리파이와 웹캠을 던져둔 방에 불이 꺼져서 어두운 상태라는 것이 확인되었다 -_-; 집으로 컴백 후 장소를 옮기고 나서 작업해야겠다. ㅠㅠ


Trackbacks 0 : Comments 4

도깨비: 요청/응답 json 포맷 사용 + mongodb 저장소 붙이기

Daily life/Hard study 2012.08.02 22:20

지난번에 작업 - http://blog.dgoon.net/449 - 하다 말았던 부분을 이어서 한다. 도깨비에 추가로 할 작업은 다음 두가지이다.

1. 일단 요청/응답 포맷을 json으로 바꾼다.
2. persistent layer를 붙인다(mongodb?)

-----
query string 이나 parameter 는 버린다.
POST 로 요청을 받고 -> Content-type 이 application/json 인지 확인하고 -> body 를 읽어서 json.loads 를 한다.
요청이 가져야 하는 값들은 여기서 나오는 json 에 모두 포함되어 있어야 한다. 몇 가지 사소한 것들을 적어둔다.

a. 요청 타입(POST/GET)은 지난번에 @require_http_methods("POST") 로 강제했다
b. content-type은 request.META.get('CONTENT-TYPE') 에 들어있다.
c. body는 request.read() 를 하면 나온다

필수 데이터가 들어있는지 확인하는 코드(check_required)와 content-type을 확인하는 코드(check-content-type)을 함수로 분리하면 더미 로직은 get/put 둘 모두,

        check_content_type(request, "application/json")
        body = request.read()
        req_dict = simplejson.loads(body)
        check_required(GET_REQUIRED_FIELDS, req_dict.keys())
        print req_dict


이 정도로 간단해진다. 조건이 맞지 않는 경우 check_required, check_content_type 함수들은 직접 예외를 생성해서 던진다. 테스트 요청을 날리던 get.py, put.py 에서는 content-type 만 application/json 으로 바꾸고, urlencode 하던 부분을 simplejson.dumps 로만 바꾸면 된다.

{'app_key': 'deadbeef', 'condition': {}}
[02/Aug/2012 21:06:05] "POST /api/v1/get/ HTTP/1.1" 200 2
{'content': 'THIS_IS_CONTENT', 'tag_list': ['tag1', 'tag2', 'tag3'], 'meta': {}, 'app_key': 'deadbeef'}
[02/Aug/2012 21:06:16] "POST /api/v1/put/ HTTP/1.1" 200 2

이전보다 코드도 짧아지고, tag_list 도 리스트로 제대로 들어온다. 에잉 훨 낫네.

-----
persistent layer를 붙인다. 전에 튜토리얼을 따라가본 몽고디비를 쓰고 싶다. 그때는 c++ 클라이언트였는데, 파이썬이라고 뭐 딱히 다르진 않지 싶다. 더 쉽겠지...

http://www.mongodb.org/display/DOCS/Drivers -> http://www.mongodb.org/display/DOCS/Python+Language+Center

여기서 찾아보면 몇 가지 라이브러리가 있다. PyMongo 를 선택. 일단 언제나처럼 pip,easy_install,apt-get 순서로 패키지가 있나 뒤져본다.

sudo pip install pymongo

빙고. 파이썬 쉘 띄워서 import pymongo 해보니 뭔가 임포트 된다. 이걸 쓰면 되겠다.

http://api.mongodb.org/python/current/tutorial.html

여기 튜토리얼이니 잠깐 읽어보면 어렵지 않아요~ 지난번에 삽질하면서 katy 에 몽고디비 설치는 해 두었으니 그대로 활용하자. db=tokebi, collection=twit 으로 간다. store_mongodb.py 를 만들고, MongoStore 클래스에 몽고디비 접근 구현 코드를 둔다. 인터페이스는 mongo_store.get/put 이며 api_get, api_put 이 받은 요청을 거의 그대로 받는다.


api_get:
        app_key, condition = get_items(req_dict, GET_REQUIRED_FIELDS)             
        store = MongoStore('twit')
        retrieved = store.get(app_key, condition)

api_put:
        app_key, content, tag_list, meta = get_items(req_dict, PUT_REQUIRED_FIELDS)
        store = MongoStore('twit')
        ack = store.put(app_key, content, tag_list, meta)

api_get, api_put 에 각각 위의 코드가 추가되었다. 테스트 요청을 날려보면서 디버깅을 좀 해야 했는데, python get.py localhost 2223 > ~/public_html/get_error.html 등으로 저장한 뒤에 브라우저로 열어보면 좀 쉽다. 몽고디비에 쓰고 읽을 때 필요한 자료구조는 뭘까 하고 튜토리얼을 봤더니,

In PyMongo we use dictionaries to represent documents. ...

저장 단위가 파이썬 맵이므로 구현은 아주 간단하다. 심지어 문서를 꺼내오는 조건도 사전이므로,몽고디비 접근은 거의 자명하다.

#-*- encoding: utf-8 -*-from pymongo import Connection
CONN = Connection()

def doc2dict(d):
    ret = dict(d)
    ret['_id'] = str(ret['_id'])
    return ret

class MongoStore(object):
    def __init__(self, collection):
        self.collection = CONN.tokebi[collection]

    def get(self, app_key, condition):
        cond = condition.copy()
        cond['app_key'] = app_key
        return map(doc2dict, self.collection.find(condition))

    def put(self, app_key, content, tag_list, meta):
        doc = {
            'app_key': app_key,
            'content': content,
            'tag_list': tag_list,
            'meta': meta,
        }
        return self.collection.insert(doc)


api_get, api_put 에서 응답을 적절히 json 포맷으로 만들어 준다면 테스트 요청을 날리는 get.py 에서는 여러개의 문서 리스트를 응답으로 얻게 된다. 받은 결과를 화면에 찍어보면,

dgoon@katy:~/works/tokebi/sample_query$ python get.py localhost 2223
{'Content-type': 'application/json', 'Accept': 'text/plain'}
{'condition': {}, 'app_key': 'deadbeef'}
0
        content THIS_IS_CONTENT
        _id 501a74c272653e2217c9b255
        meta {}
        app_key deadbeef
        tag_list ['tag1', 'tag2', 'tag3']
1
        content THIS_IS_CONTENT
        _id 501a74d472653e222068f2b8
        meta {}
        app_key deadbeef
        tag_list ['tag1', 'tag2', 'tag3']
2
        content 두만강푸른물에
        _id 501a750372653e222068f2b9
        meta {}
        app_key deadbeef
        tag_list [u'\uac15\uc0b0\uc5d0', u'\ub178\ub798']
3
        content 티비유치원 하나둘셋
        _id 501a768072653e225e6cb3c1
        meta {}
        app_key deadbeef
        tag_list ['hana', 'dul', 'set']


이렇게 확인할 수 있다. 한글도 잘 들어가는 것을 확인할 수 있다. 2번 문서의 tag_list에 들어있는 것은,

>>> for k in [u'\uac15\uc0b0\uc5d0', u'\ub178\ub798']:
...     print k
...
강산에
노래

이렇게 유효한 한글이다. PyMongo 튜토리얼에서 설명되어 있는데 - BSON strings are UTF-8 encoded 라고 한다. Unicode문자열은 encode 되어 저장된다고. 그리고 simplejson 으로 직렬화 할때는 다시 유니코드가 되어서 응답을 받은 쪽에서는 한글이 모두 유니코드로 떨어지게 된다. 나중에 좀 헷갈릴 것 같다.


-> http://git.dgoon.net/?p=tokebi.git;a=commit;h=da49398646c0812a0d33d71cbec23e5052f5ca09


-----

추가적으로 해야 하는 작업은 ... 잊고 있었는데, 나 말고 다른 사람도 이걸 쓰게 하려면


1. user_id 같은걸 넣어야 하고,


최신 엔트리를 가져오려면,


2. created_at 이 필요하고,


사람을 가리거나 정렬이 필요하니


3. 적절한 인덱스 가 필요하다.


다음 작업은 이상 3개가 되겠다.

Trackbacks 0 : Comments 6

MongoDB 읽고쓰기

Daily life/Hard study 2012.07.26 23:10

몽고DB의 C++ 클라이언트는 github 에서 구할 수 있다.

mongodb c++ client: github.com/mongodb/mongo
mongodb 서버는 빌드해서 설치할 수도 있지만 귀찮으니, apt-get 으로 설치하기로 한다.

a. debian: mongodb-server mongodb-dev mongodb-clients mongodb
b. ubuntu: mongodb
c. etc: http://docs.mongodb.org/manual/tutorial/install-mongodb-on-debian-or-ubuntu-linux/

세번째가 제일 나은 듯 하다. 그 와중에 upstart 라는걸 보게 되었다. 나중에 천천히 읽어볼 것: http://upstart.ubuntu.com/
사용한 client 라이브러리는 C++ 2.1.2 이다. API DOC: http://api.mongodb.org/cplusplus/2.1.0/


0. Install
  mongodb server:
  sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10
  echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" >> /etc/apt/sources.list
  sudo apt-get update
  sudo apt-get install mongodb-10gen


  mongodb client library:
  wget http://downloads.mongodb.org/cxx-driver/mongodb-linux-x86_64-2.1.2.tgz
  tar zxvf mongodb-linux-x86_64-2.1.2.tgz
  cd mongo-cxx-driver-nightly
  sudo scons install

이렇게 하면 /usr/local 밑에 몽고DB 클라이언트 라이브러리가 설치된다.
/usr/local/include/mongo 아래쪽 권한이 좀 이상하게 설치되어서 sudo chmod 755 /usr/local/include/mongo -R 을 해 주었다.
언제나 그런건지, scons 사용을 잘못한건지는 잘 모르겠다.

C++ 튜토리얼은 이걸 보면 된다. http://www.mongodb.org/pages/viewpage.action?pageId=133415
이 아래의 내용은 기본적으로 튜토리얼 내용을 그냥 따라간 로그이다.

1. Connect
#include <cstdio>
#include "client/dbclient.h"
using namespace std;

int main() {
    try {
        mongo::DBClientConnection c;
        c.connect("localhost");
    }
    catch (mongo::DBException& e) {
        printf("Error: %s\n", e.what());
    }
    return 0;
}


g++ -I/usr/local/include/mongo test.cpp -lboost_system -lboost_thread -lboost_filesystem -lmongoclient -o test

이렇게 빌드하면 ./test 를 실행했을때 아무일도 안 일어나게 된다. localhost 를 remotehost 같은걸로 바꾸고 해보면 에러가 나는걸 확인할 수 있다.


$ ./test
Thu Jul 26 21:36:19 getaddrinfo("remotehost") failed: No address associated with hostname
Error: can't connect couldn't connect to server remotehost:27017

2. Insert
접속은 했으니 이제 읽기/쓰기를 해보자. 아직 아무것도 들어있는게 없으므로, 먼저 쓰기를 하고 나서 쓴 값을 읽어보도록 하자. 몽고DB는 데이터를 BSON이라는 형식으로 저장하는데, JSON이랑 닮은 - 정확히는 json with custom extension - 포맷이다. 방법은 간단하다. BSON 객체를 생성 -> DB에 추가, 하면 된다.

a. BSON 객체 생성
BSON 객체는 BSONObjBuilder 를 사용해서 만든다. 사용법은 아래와 같다.

BSONObjBuilder builder;
builder.append("HERE_GOES_KEY", "HERE_GOES_VALUE");
builder.append("name", "dgoon");
BSONObj obj = builder.obj();

이걸 아래와 같이 체이닝 할 수도 있다

BSONObj obj = BSONObjBuilder().append("HERE_GOES_KEY", "HERE_GOES_VALUE").append("name", "dgoon").obj();

하지만 난 여러 줄에 풀어 쓰는걸 선호한다.
그 외에 stream 체이닝이나, Object ID 를 명시적으로 지정하는 방법 등 몇 가지 화두가 더 있지만 과감하게 생략.

b. DB 에 추가
이렇게 만든 BSONObj 객체를 DB에 넣기 위해서는,

conn.insert("database.collection", obj);

를 해주면 된다. database, collection 은 무엇인가! 대충 RDB 개념을 빌리자면 database=database, collection=table 정도로 생각하면 된다.

#include <cstdio>
#include "client/dbclient.h"
using namespace std;

int main() {
    try {
        mongo::DBClientConnection c;
        c.connect("localhost");

        mongo::BSONObjBuilder b;
        b.append("name","dgoon");
        b.append("addr", "nakseongdae");
        mongo::BSONObj p=b.obj();

        c.insert("mydb.character", p);
    }
    catch (mongo::DBException& e) {
        printf("Error: %s\n", e.what());
    }
    return 0;
}


소스코드를 이렇게 수정하고 나서 실행해보면, 역시 아무런 말도 없다. 잘 된거겠지… 확인을 해보자. 읽는 코드를 만들기 전에 잠깐 shell 에서 보도록 한다.

c. SHELL

몽고DB는 강력한지는 잘 모르겠고, 여튼 쉘 환경이 있다. 위의 c로 설치했다면 mongo 라는 커맨드가 있을 것이다.

dgoon@katy:~/works/mongo-cxx-driver-nightly$ mongo
MongoDB shell version: 2.0.6
connecting to: test
>

위에서 mydb.character 에 데이터를 넣었던 것이 기억나는가? db=mydb, collection=character 이다.

> show dbs
local     (empty)
mydb     0.078125GB
> use mydb
switched to db mydb
> show collections
character
coll
system.indexes
things
> db
mydb
> db.character
mydb.character
> db.character.find()
{ "_id" : ObjectId("50113dbb9aa68f690df58a39"), "name" : "dgoon", "addr" : "nakseongdae" }
{ "_id" : ObjectId("50113dbe9aa68f690df58a3a"), "name" : "dgoon", "addr" : "nakseongdae" }
{ "_id" : ObjectId("50113dbf9aa68f690df58a3b"), "name" : "dgoon", "addr" : "nakseongdae" }
>

이렇게 들어가 있다. … 왜 3개냐구? 내가 실행을 3번 했으니까. … 그렇다, 딱히 KEY 를 지정해 준 것이 아니기 때문에 여러번 추가해도 중복이 아니므로, 실행한 횟수만큼 여러개가 들어간다.


3. Retrieve

읽어오는건 코드가 조금 더 길다. 읽어올 조건과 함께 query를 날린다 -> 커서를 받는다 -> 커서를 순회하며 데이터를 꺼낸다, 순서로 작업하면 된다.

데이터를 꺼내오기 위해 명시해 주어야 하는 것은, 어디에서? 어떤 녀석을? 이다. connection.query 는 2개의 인자를 받는데, 하나는 어디에서? 또 하나는 어떤 녀석을? 에 대한 것이다. 여기서 조건 역시 BSONObj 타입으로 들어간다. 그냥 빈 객체를 넣으면 모두 다 꺼내오라는 뜻이다. BSONObj 객체의 각 필드가 AND로 조합되어 조건으로 들어간다고 한다. 우리는 모두다 꺼내와보자.

test.cpp 는 이제 아래와 같이 길어졌다.

#include <cstdio>
#include "client/dbclient.h"
using namespace std;

int main() {
    try {
        mongo::DBClientConnection c;
        c.connect("localhost");

        mongo::BSONObjBuilder b;
        b.append("name","dgoon");
        b.append("addr", "nakseongdae");
        mongo::BSONObj p=b.obj();

        c.insert("mydb.character", p);

        auto cursor=c.query("mydb.character",
                            mongo::BSONObj());
        while (cursor->more()) {
            printf("%s\n", cursor->next().toString().c_str());
        }
    }
    catch (mongo::DBException& e) {
        printf("Error: %s\n", e.what());
    }
    return 0;
}


auto는 타이핑을 줄이기 위해 사용했는데, 정확한 타입은 auto_ptr<mongo::DBClientCursor> 이다. auto_ptr을 사용한 이유는 소유권을 넘기면서 객체 소멸을 라이브러리 사용자(caller)에게 넘기고 난 귀찮으니 잊어버리겠어 -_- 라는 것이라 생각한다. auto 사용때문에 아래 컴파일 옵션에 -std=c++0x 가 추가된 것에 주의하자.

dgoon@katy:~/works/mongo-cxx-driver-nightly$ g++ -I/usr/local/include/mongo test.cpp -lboost_system -lboost_thread -lboost_filesystem -lmongoclient -std=c++0x -o test
goon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
goon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
goon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407a9aa68f690df58a3e'), name: "dgoon", addr: "nakseongdae" }
goon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407a9aa68f690df58a3e'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407b9aa68f690df58a3f'), name: "dgoon", addr: "nakseongdae" }
goon@katy:~/works/mongo-cxx-driver-nightly$

실행할 때마다 엔트리가 하나씩 늘어나면서 가져오는 갯수도 늘어나고 있다. 참고로 b.append("age", 18); 처럼 value 에는 문자열 이외의 타입도 들어갈 수 있다.


4. Update

데이터를 가져오는 것과 별로 다르지 않다. 어디에 있는? 어떤 아이들에? 어떤 값을? 을 지정해주면 된다.


#include <cstdio>
#include "client/dbclient.h"
using namespace std;

int main() {
    try {
        mongo::DBClientConnection c;
        c.connect("localhost");

        c.update("mydb.character", // 어디에 있는?
                 BSON("age" << 18),  // 어떤 아이들에게?
                 BSON("$inc" << BSON("visits" << 1)));  // 어떤 값을??

        auto cursor=c.query("mydb.character",
                            mongo::BSONObj());
        while (cursor->more()) {
            printf("%s\n", cursor->next().toString().c_str());
        }
    }
    catch (mongo::DBException& e) {
        printf("Error: %s\n", e.what());
    }
    return 0;
}

이렇게 하면 mydb.character 에서 age=18 인 아이들을 찾아서(설명엔 없지만 위에서 몇개 추가해 두었다) visits 을 1 증가시킨다고 한다. $inc 가 뭔가 특별한 의미가 있나보다. 그래서 위의 프로그램을 여러번 실행하면,

dgoon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407a9aa68f690df58a3e'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407b9aa68f690df58a3f'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011421d9aa68f690df58a40'), addr: "nakseongdae", age: 18, name: "dgoon", visits: 10 }
{ _id: ObjectId('5011421f9aa68f690df58a41'), addr: "nakseongdae", age: 18, name: "dgoon", visits: 1 }
dgoon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407a9aa68f690df58a3e'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407b9aa68f690df58a3f'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011421d9aa68f690df58a40'), addr: "nakseongdae", age: 18, name: "dgoon", visits: 11 }
{ _id: ObjectId('5011421f9aa68f690df58a41'), addr: "nakseongdae", age: 18, name: "dgoon", visits: 1 }

이런 결과가 나온다. 처음 한 번은 5011421f9aa68f690df58a41 를 고치더니 그 후로는 5011421d9aa68f690df58a40 를 고치고 있다. 어어?
… 요약하면 해당 조건에 맞는 데이터가 여러개 있는 경우에, 그 중 임의로 하나를 선택하여 갱신, 한다고 생각한다.
혹은 선택 기준은 있지만, 갱신할 때마다 선택되는 아이들이 달라지게 될 수도 있다. 여튼 곤란.

uniqueness 를 보장하기 위해서는 매번 업데이트 할 때마다 query를 보내어 확인을 한번 해 주어야 하는건가?

조건에 맞는 엔트리가 하나도 없는 경우는 아무일도 일어나지 않는다.



+
http://api.mongodb.org/cplusplus/2.1.0/dbclient_8h_source.html


ensureIndex의 함수 시그니쳐는 다음과 같다. unique=false가 기본값인데, true를 줄 수 있다.


virtual bool ensureIndex( const string &ns , BSONObj keys , bool unique = false, const string &name = "", bool cache = true, bool background = false, int v = -1 );


주의할 점은 특정 key에 unique=true가 걸리면 그 키가 동일한 경우는 물론 이고, 해당 키가 "없는 것도 하나의 값" 이 되어 버린다. 즉 ensureIndex("mydb.character", fromjson("{age:1}"), true); 로 인덱스를 생성했다면

mydb.character 에 age 가 없는 데이터는 단 하나밖에 존재할 수 없다

는 사실.


+ 도깨비의 백엔드 저장소로 몽고디비 사용할 예정.


Trackbacks 0 : Comments 6

도깨비(tokebi) 더미 API

Daily life/Hard study 2012.07.12 22:19


목표: 아이폰에서 읽고 쓸 수 있는 임의의 저장소 인터페이스를(만) 만드는 것


1. Initial commit
  - put: (app_key, content, tag_list, meta_info)
  - get: (app_key, condition)

  일단 일반적인 모양만 정의하고 세세한 사용은 나중에 정의하기 위하여 메타정보, 조건 등을 dictionary 로 받는걸로 한다. POST 요청만 다루며 GET은 예외를 던져버리도록 하자. 일단 인터페이스를 만드는게 목표이므로 실제로 데이터를 저장하는 부분은 고려하지 않는다.


성능은 고려사항이 아니다. 아이폰 앱을 만들 때 붙여놓을 더미가 필요한 것이므로 django로 후다닥 작성한다. django 1.4 기준이다. 혹시 나중에 필요하게 되면 비동기로 다시 작성한다. 개인적으로 eventlet 에 관심이 ...

  app_key: fixed-length binary
  content: binary
  tag_list: list of strings
  meta_info: dictionary
  condition: dictionary

  URL:
    - /api/v1/put
    - /api/v1/get

  이 저장소는 만들어질 아이폰 앱과는 완전히 별개의 코드베이스이므로 독립적인 git 저장소를 생성한다. dgoon's key value storage -> dkv -> 도깨비 -> tokebi 로 한다. 난 부끄러움이 많으니까 github에 공개 저장소를 만들진 않는다. 하지만 비공개 저장소도 싫으니까 개인서버에 조용히 판다. git.dgoon.net

django 프로젝트(tokebi)를 생성하고 기본 세팅. RDB는 사용하지 않을 예정이므로 DATABASES는 깔끔하게 날린다.
나중에 관리 페이지라던가 다른게 생길 수 있으니 api 라는 애플리케이션을 생성해서 거기에 코드를 넣는다.
위에서 정의한대로 필요한 콜은 put, get 두개이다. 이 각각을 받아줄 endpoint 를 설정한다. 정의되지 않은 URL에 대해서는 일단 404를 알아서 만들도록 둔다. secret key 따위 알게뭐야 흥.

----- api/views.py -----
from django.http import HttpResponse

def api_get(request):
    return HttpResponse('OK', status=200)

def api_put(request):
    return HttpResponse('OK', status=200)


----- tokebi/urls.py -----
...
from api.views import api_get, api_put

urlpatterns = patterns('',
    url(r'^api/v1/get/', api_get),
    url(r'^api/v1/put/', api_put),

…)


이렇게 하고 테스트 서버를 띄워서 접속하면 /api/v1/get, /api/v2/put 에 대해서 OK 를 얻을 수 있다. 일단 여기서 Initial commit 을 해 둔다.


2. Test request

필요한 정보가 모두 있는 경우 OK를, 부족한 경우 500 에러를 내도록 한다. 이게 되어야 개발에 사용할 더미로써 가치가 있다.
그러기 위해선 쿼리를 보낼 수 있어야 한다.

put.py, get.py 를 만든다.

  - put.py: 명령행에서 HOST PORT CONTENT TAG_LIST 를 입력받는다. 메타정보는 일단 {} 를 넣는다.
  - get.py: 명령행에서 HOST PORT 를 입력받는다. 조건에 {} 를 넣는다.

두 경우 모두 app_key 는 'deadbeef' 를 하드코딩한다. 요청은 파이썬으로 작성한다. httplib.HTTPConnection(http://docs.python.org/library/httplib.html#httpconnection-objects) 을 사용하면 되겠지.

----- put.py -----
import sys, httplib, urllib

APP_KEY = 'deadbeef'

if '__main__'==__name__:
    if len(sys.argv)<5:
        print('Usage: %s host port content tag_list' % sys.argv[0])
        sys.exit(0)

    host = sys.argv[1]
    port = int(sys.argv[2])
    content = sys.argv[3]
    tag_list = sys.argv[4:]

    conn = httplib.HTTPConnection(host, port)
    body = {
        'app_key': APP_KEY,
        'content': content,
        'tag_list': tag_list,
        'meta': {},
    }
    header = {
        'Content-type': 'application/x-www-form-urlencoded',
        'Accept': 'text/plain',
    }
    conn.request("POST", "/api/v1/put/", urllib.urlencode(body), header)

    res = conn.getresponse()
    res_body = res.read()
    assert(res.status==200 and res_body=='OK')


그런데 python put.py localhost 2223 HAHAHA tag1 tag2 tag3 를 실행해더니 assertion error가 떳다.

[12/Jul/2012 21:06:55] "POST /api/v1/put/ HTTP/1.1" 403 2282
[12/Jul/2012 21:07:07] "GET /api/v1/put/ HTTP/1.1" 200 2

403이 put.py 로 날린 요청, 200 이 브라우저에서 날린 요청이다. 500도 아니고 403 ?? put.py 에서 res_body 를 찍게 해 봤더니,
csrf관련된 내용이 보인다. 말인 즉슨, cross-site request forgery를 방지하기 위한 미들웨어가 있는데 그녀석을 위한 토큰이 포함되어야 한다나 어쩐다나.
나는 템플릿을 쓸 생각이 없고, 요청에 그런걸 만들어 보내는것도 귀찮으므로 csrf 관련 옵션을 비활성화 해버리겠다. settings.py 에서 csrf 로 검색하면 바로 나온다. MIDDLEWARE_CLASSES 에서 django.middleware.csrf.CsrfViewMiddleware 를 주석처리하고 나니 잘 된다.

api_put 을 고쳐서 들어온 body 를 확인하도록 하자. 콘솔에 그냥 print 를 해보면 볼 수 있다.

----- api_put @ views.py -----
def api_put(request):    method = request.META['REQUEST_METHOD']
    print method
    print request.GET
    print request.POST
    return HttpResponse('OK', status=200)

이렇게 하고 put.py 로 한번, 브라우저로 리프레시 한번 하면 콘솔에 아래와 같이 찍힌다.

POST
<QueryDict: {}>
<QueryDict: {u'content': [u'THIS IS CONTENT'], u'tag_list': [u"['tag1', 'tag2', 'tag3']"], u'meta': [u'{}'], u'app_key': [u'deadbeef']}>
[12/Jul/2012 21:27:37] "POST /api/v1/put/ HTTP/1.1" 200 2
GET
<QueryDict: {}>
<QueryDict: {}>
[12/Jul/2012 21:27:40] "GET /api/v1/put/ HTTP/1.1" 200 2

귀찮으니

  a. content, tag_list, meta, app_key 가 모두 있지 않은 경우
  b. method=POST 가 아닌 경우

에 모두 예외를 던져버리자. … 코드를 만들고 나서 생각해보니 method=POST 강제하는건 왠지 데코레이터 있을 것 같다. 검색 ㄱㄱ

https://docs.djangoproject.com/en/dev/topics/http/decorators/

빙고. 그러면 최종적으로 api_put 의 모양은 아래와 같게 된다.

----- api_put @ views.py -----
from django.views.decorators.http import require_http_methods
class MissingRequiredField(BaseException): pass                                    

PUT_REQUIRED_FIELDS = ['app_key', 'content', 'tag_list', 'meta']

@require_http_methods(["POST"])
def api_put(request):                                                             
    try:                                                                          
        params = request.POST
            if (set(PUT_REQUIRED_FIELDS) - set(params.keys())):
                raise MissingRequiredField('MissingRequiredField')                                                                                                        
        content = params['content']                                               
        app_key = params['app_key']                                               
        tag_list = params['tag_list']                                             
        meta = params['meta']                                                     
                                                                                  
        print(app_key, content, tag_list, meta)                                   
                                                                                  
        return HttpResponse('OK', status=200)                                     
    except BaseException, msg:                                                    
        print('Exception raised: %s' % str(msg))                                  
        return HttpResponse(str(msg), status=500)          

그런데, 콘솔 출력을 보면

(u'deadbeef', u'THIS IS CONTENT', u"['tag1', 'tag2', 'tag3']", u'{}')
                       
이렇게 보이는게, tag_list, meta 가 직렬화된 상태로 넘어온 것 같다. … 음 이거 귀찮은데 …
그냥 요청을 json 으로 받을까. 일단 get.py 도 만들고 나서 고민하자. get.py 는 put.py 의 copy&paste로 뚝딱.
api_get 도 api_put 을 복사붙이기로 작성한다.

views.py: http://git.dgoon.net/?p=tokebi.git;a=blob;f=tokebi/api/views.py;h=09dd4ab585b3be18654fd51356f1ccd1d7f50fdc;hb=c4a018b479157c9e27a6a67c912079e3ebe1be6d
get.py: http://git.dgoon.net/?p=tokebi.git;a=blob;f=sample_query/get.py;h=4d10dabf8d1097464fad6d15bed582366480a2ca;hb=c4a018b479157c9e27a6a67c912079e3ebe1be6d
put.py: http://git.dgoon.net/?p=tokebi.git;a=blob;f=sample_query/put.py;h=1f2f5ef3aef9be70e6758094b3fcdd238ec33619;hb=c4a018b479157c9e27a6a67c912079e3ebe1be6d

오늘은 일단 여기까지. 추후에 요청을 json 으로 받도록 해보자. … 는 나중에 하고, 간단한 persistent layer를 붙여서 아이폰에서 put/get 동작시키는걸 먼저 해야겠다.


tags : Django, REST, tokebi, web
Trackbacks 0 : Comments 5

2012/07/05: Hello world in xcode

Daily life/Hard study 2012.07.05 22:50

1. 일단 화면에 hello world을 출력하는 간단한 앱을 만들어보자.

2. 텍스트를 입력받고 버튼을 누르면 뒤집힌 텍스트를 출력하는 앱으로 업그레이드 해보자.

-----

Create new project를 누르면 application 타입을 물어본다. 뭐 이런 것들이 있다:
  Master-detail
  OpenGL
  Page-based
  Single view
  Tabbed
  Utility
  Empty

일단 헤딩이니까 Empty로 해보자
  project name: helloworld
  company name: dgoon
  class prefix: DGOON
  - 헐 자동으로 local git 저장소를 만드네?

프로젝트 생성하고 그냥 실행을 하면 (enable developer mode? 를 물어본다)
  run/stop 버튼 오른쪽에 target device를 설정할 수 있는 select box가 있다. ipad는 너무 크니까 iphone 으로 한다
  실행하면 아무것도 없는 하얀 화면이 나옴

프로그렘 진입은 main.m 파일이다. 하는 일은 UIApplicationMain 클래스를 만드는 것 뿐이다.

-> UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName);

DGOONAppDelegate 가 결국에는 모든 일을 다 하는 녀석인데, 이는 DGOONAppDelegate.h/m 파일에 있다.
결국 우리는 UIAppilcationDelegate를 상속받아 클래스를 만들어서 UIApplicationMain에게 던져주면 되는것이다!

DGOONAppDelegate.m 을 보면 몇 개의 함수들이 정의되어 있다.

  didFinishLaunchingWithOptions
 applicationWillResignActive
 applicationDidEnterBackground
 applicationWillEnterForeground
 applicationDidBecomeActive
 applicationWillTerminate

이름을 보면 하는 일들은 명확하다. 각 함수 안에 있는 주석을 읽으면 도움이 되겠지.
나는 didFinishLaunchingWithOptions 에서 화면에 helloworld만 찍어주면 된다.

didFinishLaunchingWithOptions 함수를 보면 self.window 를 만든 다음에 makeKeyAndVisible 을 불러준다.
검색해보니 key window 란 입력 이벤트를 받는 윈도우다. 즉, 화면에 보이게 하고 터치나 키 입력 등의 이벤트를 저 윈도우로 받게 세팅하는 것이다.

self.mytext 를 만들어서 저기에 "helloworld" 를 넣은 후에 보이게 하면 될것같다.

self.window 는 DGOONAppDelegate.h 에 선언되어 있는데,

@property (strong, nonatomic) UIWindow *window;

이런 모양이다. property 를 좀 검색해서 찾아보았다. http://maclove.pe.kr/28 이 문서에 나름 잘 설명되어 있는듯. …
그런데 strong 에 대한 설명은 없다! 검색해보니 최근에 추가된 것 같은데 retain 의 대체이며, reference counting 에 관련된 속성이라 한다.

헤더(.h)에는 이렇게:

@property (strong, nonatomic) UITextView *mytext;

구현(.m)에는 이렇게:

@synthesize mytext = _mytext;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

    self.window = [[UIWindowalloc] initWithFrame:[[UIScreenmainScreen] bounds]];

    // Override point for customization after application launch.

    

    self.mytext = [[UITextViewalloc] initWithFrame: CGRectMake(100, 100, 200, 200)];

    self.mytext.text = @"HelloWorld!";
    [self.window addSubview:self.mytext];

        

    self.window.backgroundColor = [UIColor whiteColor];

    [self.window makeKeyAndVisible];

    returnYES;

}


추가하였다. cmd+b 로 빌드는 성공. cmd+r 로 실행해보면 옆으로 누워있는 아이폰에 HelloWorld! 라고 쓰여있는게 보인다.
cmd+방향키로 오른쪽 왼쪽으로 돌릴 수 있다. textview는 기본적으로 수정 가능한가보다. 클릭을 하면 키보드가 뜨고
키입력을 하면 뭔가 써 넣을 수도 있고, select, copy 등도 가능하다. 수정 못하게 해보자.

    [self.mytext setEditable:false];

mytext생성 후에 이렇게 한줄 넣어주면 된다. xcode의 자동완성이 아주 도움이 많이 된다. 있을법한 이름을 때려 넣으니까 나온다.
수정은 불가능한데 여전히 select, copy 등은 가능하다. 이것도 막고 싶은데 setSelectable 은 없는 듯 하다. 나중에 찾아본다. …

helloworld 찍는 앱 완성.

-----

2번으로 넘어가기 전에, 테스트를 좀 해보자. self.mytext 에 스트링을 바꿔 넣으면 바로 반영이 되는가?
applicationDidEnterBackground 함수에 아래와 같이 한 줄을 넣으면,

    self.mytext.text = @"enter background!";

처음 실행할 때에는 HelloWorld! 가 보이고, 홈버튼을 누른 후에 다시 아이콘을 클릭하면 enter background! 로 텍스트가 바뀐다.
그런데, 다시 시작하니까 화면이 누워있다. 이거 기본 로테이션을 바꿀 수 없나?

왼쪽 project tree view의 루트를 클릭하고,  Target Helloworld -> Info -> Supported interface orientations 에서 item 0/1/2 순서를 바꿔주면 된다. Portrait를 0번으로 놓았다.

-----

2번을 위해서는

  a. UITextView 2개를 넣어서 하나는 입력(a), 하나는 출력(b)에 쓴다.
  b. UIButtonView (가 있나?) 를 만들어서 콜백함수를 작성 -> 자동완성을 보니 UIButton 이 나온다.
  c. 버튼의 콜백함수에서 (a)를 읽어 뒤집은 후 (b)에 넣는다.

를 하면 된다. 수정해보자.

위에서 추가했던 mytext 를 지우고, 대신 이런 아이들을 넣는다.

.h ->

@property (strong, nonatomic) UITextView *input_text;

@property (strong, nonatomic) UITextView *output_text;

@property (strong, nonatomic) UIButton *reverse;

.m ->

@synthesize input_text = _input_text;

@synthesize output_text = _output_text;

@synthesize reverse = _reverse;


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

    self.window = [[UIWindowalloc] initWithFrame:[[UIScreenmainScreen] bounds]];

    // Override point for customization after application launch.

    

    self.input_text = [[UITextViewalloc] initWithFrame: CGRectMake(100, 100, 100, 20)];

    self.input_text.text = @"INPUT";

    [self.input_textsetScrollEnabled:false];


    self.output_text = [[UITextViewalloc] initWithFrame: CGRectMake(100, 200, 100, 20)];

    [self.output_textsetEditable:false];

    [self.output_textsetScrollEnabled:false];

    self.output_text.text = @"OUTPUT";

    

    self.reverse = [UIButtonbuttonWithType: UIButtonTypeRoundedRect];

    self.reverse.frame = CGRectMake(100, 150, 100, 20);

    [self.reversesetTitle:@"REVERSE!"forState:UIControlStateNormal];

    [self.reverseaddTarget:selfaction:@selector(reverse_output) forControlEvents:UIControlEventTouchUpInside];

    

    [self.windowaddSubview:self.input_text];

    [self.windowaddSubview:self.output_text];

    [self.windowaddSubview:self.reverse];

        

    self.window.backgroundColor = [UIColorwhiteColor];

    [self.windowmakeKeyAndVisible];

    returnYES;

}


- (void)reverse_output

{

    self.output_text.text = @"CLICKED!";

}

이렇게 하면 버튼을 눌렀을 때 output_text 가 CLICKED! 로 바뀌는 화면이 만들어진다.
여기서 reverse_output 을 수정해서 입력 텍스트를 뒤집게만 하면 된다. 일단 NSString 에 reverse 메소드가 있는지 확인을 해본다.
없다. … 검색을 해보니 … … ?? 뭐 이리 복잡해 으아앙 멘붕.

그냥 파일 이름을 DGOONAppDelegate.mm 으로 바꾸고 제일 위에

#include <algorithm>

#include <string>

using namespace std;

이걸 추가하고 나서 reverse_output 을 아래와 같이 고쳐버렸다. … C++이 최고다. orz

- (void)reverse_output

{

    std::string s([self.input_text.textUTF8String]);

    std::reverse(s.begin(), s.end());

    self.output_text.text = [NSStringstringWithUTF8String: s.c_str()];

}

이게 한글, 일본어같은 문자열에 대해서는 (당연히) 동작을 안할텐데 일단 이렇게 놓고 오늘은 끝.
어떻게 해야 쉽고 안전하고 이식성 있게 뒤집을 수 있는가!?


'Daily life > Hard study' 카테고리의 다른 글

MongoDB 읽고쓰기  (6) 2012.07.26
도깨비(tokebi) 더미 API  (5) 2012.07.12
2012/07/05: Hello world in xcode  (5) 2012.07.05
An introduction into SICP  (6) 2011.09.03
Reading: How To Design Programs(HTDP)  (0) 2011.07.13
D 프로그래밍 언어 - 오타(?)  (3) 2011.05.12
Trackbacks 0 : Comments 5

An introduction into SICP

Daily life/Hard study 2011.09.03 21:37
SICP는 Structure and Interpretation of Computer Programs 의 약자다. 마법사 책(wizard book)으로도 알려져 있으며, 한때(지금도?) MIT에서 1학년들에게 가르쳤다고 한다. 해커를 동경하거나, 이미 해커이거나, 해커가 되어가고 있는 사람들이라면 LISP의 한 방언인 Scheme의 존재는 알고 있을테고, scheme을 언급할 때 함께 튀어나올 수 밖에 없는 책으로 해커 문화와 뗄 수 없을 것 같... 다고 마음대로 생각한다.

유명세에 비해서 완독한 사람은 생각보다 많지 않은데, 책의 난이도가 - 혹은 읽는데 드는 잉여력이 - 꽤 높기 때문이다. 프로그래밍 스킬과는 별도로 체계적이고 논리적인 사고, 적절한 추상화 능력, 2차 이상의 Higher order 를 상상할 수 있는 능력 그리고 어느 정도의 하드웨어 지식을 요구한다. 거꾸로, 이 책에서 주려는 것이 저런 능력이기 때문에 잉여력만 충분하면 읽으며 깨달아가도 된다. 이런 경우까지 고려하여 이 책을 읽기 위한 최소한의 요구사항은 끈기Scheme 코드를 돌려볼 수 있는 환경이다.

책은 총 5개의 챕터로 구성되어 있으며, 각각

1. Building abstractions with procedures
2. Building abstractions with data
3. Modularity, Objects and State
4. Metalinguistic abstraction
5. Computing with register machines

이런 제목을 가지고 있다. 각 챕터에 대한 간략한 소개를 하자면,

1장:
 스킴의 기본적인 문법부터 시작한다. 프로시져-그러니까, 그냥 함수라고 하자-를 만들고 사용하는 연습을 주로 한다. 만들어진 프로시져 여러개를 조합해서 Compound procedure 를 만들면서 Building block을 쌓는게 이런거구나 느껴보게 된다. 그리고 장 말미에 Procedure 를 만드는 Procedure 를 만드는 Procedure ... 같은 Higher order procedure 의 맛을 좀 보게 된다.

2장:
 1장의 연결이라는 느낌이다. 1장에서 다룬 프로시져 사이를 넘나드는 "데이터"를 조명한다. 그리고, 프로시져와 데이터를 엮어 인터페이스의 생성 - 즉 추상화 배리어가 어떤 의미를 가지는지에 대해서 구구절절 설명하고 실습하게 된다. 그리고 Data-directed programming 이라는 짓을 하게 되는데, 이 부분은 Object-oriented 쪽의 정수와 맞닿아 있다. 거의 타입 시스템과 Message-passing 시스템을 직접 구현해 올리게 되는 (꽤) 놀라운 체험을 하게 된다.

3장:
 1, 2장까지의 Scheme 은 순수한 함수형 언어였다. 모든 프로시저는 referential transparency 를 가지고 있었다. 여기부턴 안그렇다. 시간에 따라 변화하는 상태가 등장하기 시작하며 머리가 조금 아파지기 시작한다. 기초적인 concurrency 에 대한 개념, 따라오는 문제, 이런 문제를 다루는 방법 등에 대해 맛을 볼 수 있다. (미친듯한 회로 시뮬레이션-_-) 그리고 3장 마지막에 나오는 stream - 사실 3장의 핵심은 이거다. Stream이 무엇인가? 어떻게 동작하는가? 에 대한 설명을 하며 다음 장에 본격적으로 등장할 Lazy evaluation 을 준비시킨다.

4장:
 책의 주제가 슬슬 드러나기(난이도가 급상승하기) 시작하는 곳이다. 책 표지에 왜 Apply-Eval 이 있을까? 에 대한 답이 시작된다. 우리는 스킴으로 스킴 코드를 실행시키는 인터프리터를 만들게 된다. 그 와중에 깨알같은 최적화들이 나오게 되고, 일반적인 인터프리터나 컴파일러가 이런 짓을 하겠구나 - 싶은 영감을 좀 얻을 수도 있다. 특히, Evaluator 를 만들고 여기에 Lazy-evaluation 을 적용시키는 작업을 직접 해보면, 꽤 많은 걸 느껴볼 수 있게 된다. 앞에서 주구장창 이야기했던 추상화의 소중함이랄까 ... 그리고, 뒤쪽 두 개의 소챕터는 amb로 설명되는 nondeterministic computing과, logic programming이 어떻게 구현될 수 있는지 맛보기를 보여준다. (STREAM!) 텍스트가 아이디어->구체화->코드, 의 과정을 설명하고 있는데 읽고 나면 간단해 보이지만, 책을 덮고 직접 그 길을 따라가 보면 이 책을 쓴 사람들이 얼마나 미친 내공의 소유자들인지 깨닫게 된다. ...

5장:
 4장에서 우리는 소프트웨어 세상의 끝을 보았다. 4장에서 코드를 실행시키는 코드를(Metacircular) 만들었으니까... 이제 한 단계 아래로 내려간다. Higher order이기 때문에 주의해서 읽어야 한다. 우리는 일반적인 가상의 기계어를 정의한 후에, 이 기계어를 시뮬레이션 하는 스킴 코드를 만들고, 이 기계어로 스킴 실행기를 만들어서, 기계어로 만들어진 스킴 실행기에 스킴 코드를 넣어 실행하는 시뮬레이션을 한다. 그리고 나서, 이번엔 실행기가 아니라 스킴 코드를 우리가 정의한 기계어로 컴파일하는 컴파일러를 만든다. 인터프리터와 컴파일러가 어떻게 다른지, 컴파일러가 만든 코드가 인터프리터에 비해 어떤 최적화를 더 할 수 있어서 빠른건지를 이해할 수 있게 된다. 연습문제들이 앞 3, 4 장에서 만들었던 유틸리티 코드를 많이 재사용하기 때문에 앞 장을 대강 읽었거나 문제를 하나도 안풀고 그냥 넘어왔다면 5장에서 능욕당하게 된다.
 
텍스트와 함께 많은 수의 연습문제들이 배치되어 있는데, 이 책을 제대로 읽은 사람들이 하나같이 "연습문제를 풀어보지 않으면 읽었다고 말할 수 없다"고 이야기한다는 점에 주목할 필요가 있다. 위에서 말한 완독이라는 표현은, 연습문제를 풀어보았다 - 라고 말할 정도를 뜻한다. 참고로 나는 모든 문제를 풀어본 상태는 아니고, 모든 문제에 대해 풀이를 시도해본 정도이다. 몇몇 난이도가 미친듯한(혹은 잉여력 스카우터를 폭발시킬듯한) 문제를 제외하고는 대부분 풀긴 했다. ... 사실 연습문제를 풀지 않으면, 끝까지 읽는게 불가능할지도 모르겠다. 이런 부분이 많지는 않지만, 앞쪽 연습문제에서 다룬 내용을 뒤쪽 텍스트에서 당연하다는 듯이 사용하고 있으니까.

나같은 경우에는, 연습문제를 풀면서 많이 배웠다. 진짜로, 많이. 원래 훌륭한 사람이라면 나만큼 많이 배울게 없을수도 있긴 하다. 어쨌거나 텍스트만 읽을 때 보다 연습문제까지 풀었을 때에 얻을 수 있는게 훠어어얼씬 많다는 것은 확실하다.

이 책을 읽을, 연습문제를 풀 사람들에게 줄 만한 조언은,

1. 1장, 2장은 뒤쪽을 위한 몸풀기다. 여기서 어려움을 느끼면 곤란하기 때문에, 책을 읽으며 순서대로 문제를 풀려는 생각은 버리고 책을 빠르게 여러번 읽으며 내용을 씹어먹으며 연습문제를 푸는게 좋을 것 같다.

2. 3장부터는 바로 실행해보기 귀찮거나 어려운 녀석들이 등장한다. 특히 stream 관련해서 텍스트에 있는 내용이나 연습문제를 돌려보기가 까다로운 경우가 있다. 일단, 스트림 관련 코드가 나오면 sicp 홈페이지에서 코드를 구하던가 아니면 문제는 스킵하고 텍스트에 있는 코드를 먼저 입력해서 "실행해볼 수 있는" 환경을 만든 후에 문제를 푸는게 좋다. 실행 환경을 먼저 만드는 것은, 이 후 책 끝까지 언제나 중요하다. 

3. 일단 풀어보거나, 혹은 구체적인 접근 방법을 떠올리기 전에 다른 사람의 풀이를 보지 않는다.

4. 삽질 기록을 어딘가(연습장이나 위키, 그 외 어디라도) 정리해 가기를 강추. 코드도 함께 남기면 나중에 앞 장의 코드나 풀이 기록이 필요할 때(4장부터는 앞쪽을 다시 읽어보게 되는 경우가 많다) 큰 도움이 된다. 나중에 앞을 다시 봤는데 기억이 안나고 기록도 없으면 책을 다시 읽고, 연습문제를 다시 풀어야 할지도 모른다.

5. 직접 만들어도 되고 있는 것을 써도 되는데, 테스트 코드 작성을 추천. 1, 2, 3장까지는 특히나 엄청나게 매우매우 유용하다. 나는 컴키드님 자작 테스트 툴을 썼는데 큰 도움이 되었다. 4장 이후로는 테스트가 곤란한 것들이 많아서 유명무실 ...

6. Pencil & Paper 를 항상 가까이에. 

특히 실행 환경 구성이 꽤 시간을 많이 잡아먹고, 우리를 지치게 하는 부분이다. SICP와 함께 자주 거론되는 책으로 HTDP(How To Design Programs)가 있는데, 이 책 저자들은 SICP를 타산지석 삼아서 - 실제로 SICP의 악명으로부터 HTDP가 나왔다고도 한다 - HTDP에서는 테스트해볼 수 있는 환경 구축에 신경을 매우 많이 썼다. 심지어 SICP 연습문제 풀이에도 MIT Scheme 보다 Dr.Racket(PLT Scheme) 이 더 나을 정도.

SICP를 씹어먹고 나면 CS, 혹은 프로그래밍 세계에서 자주 나오는 여러가지 개념들에 대해 보다 깊은 이해를 할 수 있게 된다. 왜냐, 거의 대부분의 주제를 (기초적인 수준이지만) from scratch 로 삽을 떠 보기 때문에 ...

몇 년째 SICP를 읽고 문제를 풀고 있다고 하면 재귀나, 고차함수 같은거 실제 필드에서는 쓸일 없는데 알아서 뭐하냐, 라는 태클이 간간히 들어오기도 했다. 여기에 대해 답을 하자면 재귀, 고차함수 이런게 실제로 프로덕션 코드에 쓰이는 일이 있는지 없는지 나야 잘 모르지만, 그게 중요한 게 아니다. 중요한 것은 생각의 도구/단위를 얻는 것이다. GOF 디자인 패턴 책에 설명된 패턴 중 상당수가, 함수가 1차 객체인 많은 언어들에서는 큰 의미가 없어진다. 언어의 패러다임을 이해하고 적절한 개념을 갖추고 있는 일반적인 개발자에게는 너무 당연한 스킬이라서. 자바에서 몇백 라인에 걸쳐 십수개의 클래스를 정의해가며 구현한 패턴들이 파이썬이나 루비, 루아 혹은 스킴에서는 단 몇 줄로(가끔은 One-liner)해결되는 경우가 꽤나 많다.

적절한 개념을 탑재하고 있으면, 적절한 개념이 지원되는 언어로 적절한 코드를 만들 수 있다. 그 개념이 자연스럽게 지원되지 않는 언어라면, 그 개념을 흉내내는 바탕을 깔(배운 용어를 써보자면, 추상화 단계를 만들) 수 있다. 이런 삽질들이 모여서 새 언어가 만들어지고, 기존 언어의 스펙이 확장되고, 여러가지 프레임웍들이 생겨나는 것이다.

문법을 알고 있는 아무 언어나 골라서, 이 언어로 쓰여진 규모 있는 오픈 소스 프로젝트의 저장소를 하나 받아와보자. 언어의 문법을 알고 있는 사람이라면 누구나 이 코드를 읽을 수 있다. 하지만 언어의 문법을 알아도 1년동안 이 프로젝트의 코드베이스를 이해하지 못하는 사람이 있는 반면, 몇 시간이면 대강의 구조를 파악하고 재미삼아 코드 수정까지 하는 사람이 있다. 코드 한 줄 한 줄이 모여 만들어내는 보다 큰 논리단위의 구성을 읽는 능력의 차이인데, 다른 말로 일반적으로 쓰이는 추상화 블럭들을 읽어내는 능력이다. 한 권의 책 치고, SICP가 커버하는 프로그래밍의 핵심 블럭들은 굉장히 넓은 범위에 분포되어 있으며, 아마 자주 쓰이는 대부분의 개념에 대해 맛을 볼 수 있으리라 생각한다.

SICP를 읽으면 뭘 할 수 있게 되요? 혹은 어떤 내공이 쌓여요? 라고 묻는다면, 간단하게 이렇게 대답하련다. 코드 말고 프로젝트 단위의 읽고 쓰기를 위한 내공을 조금 얻을 수 있다고. 물론, 이 책 말고 다른 곳에서도 얻을 수 있다. 얻어야 하는 것이 무엇이건 간에 도달하는 방법은 여러가지일 수 있다는게 이 책의 중요 포인트 아니던가.

'Daily life > Hard study' 카테고리의 다른 글

도깨비(tokebi) 더미 API  (5) 2012.07.12
2012/07/05: Hello world in xcode  (5) 2012.07.05
An introduction into SICP  (6) 2011.09.03
Reading: How To Design Programs(HTDP)  (0) 2011.07.13
D 프로그래밍 언어 - 오타(?)  (3) 2011.05.12
SICP study: 근황  (3) 2011.04.27
tags : SICP, 리뷰, 추상화
Trackbacks 1 : Comments 6

먹고싶다: 떡

Daily life 2011.07.20 01:30
확인할 게 있어서 EBS 방송을 좀 보는 중인데, 이 시간에 이런걸 보여주면 어쩌잔 말이오 ㅠㅠ




으아아아아아아아악 맛있겠다 ㅠㅠㅠㅠㅠㅠㅠ

 

'Daily life' 카테고리의 다른 글

먹고싶다: 떡  (0) 2011.07.20
그 힘으로 다시 나아간다  (0) 2011.07.17
EBS Space 공감 보다가  (2) 2011.07.15
맥북프로 화면 구석이 노랗게 변색  (5) 2011.07.03
진화를 멈추라  (2) 2011.06.28
지를까 말까 지를까 말까 지를까 말까  (2) 2011.06.26
tags :
Trackbacks 0 : Comments 0

그 힘으로 다시 나아간다

Daily life 2011.07.17 04:29
책장에 꽂혀있던 만화책을 꺼내서 다시 읽어봤다. 헤헤 ^^ 린 긔엽긔.

5권은 이렇게 끝난다.

 

우리나라에선 아직 막권까지 안나왔지만 일본에선 이미 끝났고.
지킬 것과 포기할 것 사이에서 흔들리는 인간들이 등장한다.

무언가를 연료로 태워야만 앞으로 나아갈 힘을 얻을 수 있다고 한다. 조금 이해할 수 있을 것 같다.

'Daily life' 카테고리의 다른 글

먹고싶다: 떡  (0) 2011.07.20
그 힘으로 다시 나아간다  (0) 2011.07.17
EBS Space 공감 보다가  (2) 2011.07.15
맥북프로 화면 구석이 노랗게 변색  (5) 2011.07.03
진화를 멈추라  (2) 2011.06.28
지를까 말까 지를까 말까 지를까 말까  (2) 2011.06.26
Trackbacks 0 : Comments 0

EBS Space 공감 보다가

Daily life 2011.07.15 01:48

귀... 귀엽다! 목소리도 좋아! 이 처자 누구요, 정체를 내놓으시오!
 

'Daily life' 카테고리의 다른 글

먹고싶다: 떡  (0) 2011.07.20
그 힘으로 다시 나아간다  (0) 2011.07.17
EBS Space 공감 보다가  (2) 2011.07.15
맥북프로 화면 구석이 노랗게 변색  (5) 2011.07.03
진화를 멈추라  (2) 2011.06.28
지를까 말까 지를까 말까 지를까 말까  (2) 2011.06.26
Trackbacks 1 : Comments 2

Reading: How To Design Programs(HTDP)

Daily life/Hard study 2011.07.13 21:52
SICP와 자주 비교되곤 하는 HTDP를 읽으며 천천히 문제 풀이 중이다.

확실히 SICP보다는 좀 더 상냥한 책이다. SICP를 읽으며 항상 아쉬웠던 부분인 "코드를 실행해 볼 수가 없어!" 라는 포인트를 정확하게 짚어내어 공략했다. 언어에 따라가는 Dr. Racket 이라는 개발 환경에서 Teach-pack 이라는 개념으로 대부분의 예제/문제 코드를 GUI 까지 있는 상황에서 실행해볼 수 있다. 그리고 다루는 주제들이 SICP보다 훨씬 평이하다. SICP에서 Newton method, Fixed-point 이런걸 보며 떡실신한 사람이 꽤 있는데(그 중 1인), 여기서는 비슷한 곳에서(책의 백분위 위치로 볼때) 숫자 맞추기 게임 정도의 예를 사용한다.

당연하다면 당연한데, 단점으로는 too verbose, too slow 라는 느낌. 열심히 공부해야지! 하며 불타오르지 않는... 다지만, 그건 SICP 를 몇년째 계속 보고 있어서 그렇게 느껴지는걸지도 모르겠다. ㅠㅠ

밀당같은거 하지 말고 입문자를 확실히 잡기 위해서라면 HTDP 를 먼저 읽게 하는게 나을 것 같다는 생각이 든다.

뭐랄까, 경시대회나 올림피아드를 준비하고 싶어? 그럼 SICP를 보렴. 내신/수능 성적을 위한거라면 HTDP 를 보렴. 이런 느낌이랄까? 절반 이상 읽고 나면 SICP 와 다시 한번 비교하는 글을 써 봐야지.

'Daily life > Hard study' 카테고리의 다른 글

2012/07/05: Hello world in xcode  (5) 2012.07.05
An introduction into SICP  (6) 2011.09.03
Reading: How To Design Programs(HTDP)  (0) 2011.07.13
D 프로그래밍 언어 - 오타(?)  (3) 2011.05.12
SICP study: 근황  (3) 2011.04.27
예술의 전당 수묵화반, 전시회 준비  (3) 2010.10.21
Trackbacks 0 : Comments 0

맥북프로 화면 구석이 노랗게 변색

Daily life 2011.07.03 01:31


음 회사 물건이다. 일년은 넘은 것 같은데 ... 어느날 갑자기 구석에 노란 빛이 감도는 것을 발견했다. 이것은 뭐지?

맥북에어를 지를까 말까 한창 고민중이었는데, 갑자기 지르지 말자, 고 마음을 굳힐 수 있었다. 굿.

'Daily life' 카테고리의 다른 글

그 힘으로 다시 나아간다  (0) 2011.07.17
EBS Space 공감 보다가  (2) 2011.07.15
맥북프로 화면 구석이 노랗게 변색  (5) 2011.07.03
진화를 멈추라  (2) 2011.06.28
지를까 말까 지를까 말까 지를까 말까  (2) 2011.06.26
Raindrops  (0) 2011.06.18
Trackbacks 1 : Comments 5

진화를 멈추라

Daily life 2011.06.28 23:12
안(경)여(드름)돼(지) + ET(근육X, 뱃살)가 되어가고 있다. 

팔굽혀펴기 100개: http://hundredpushups.com
2마일 15분: 관악구민운동장 열한바퀴 반 15분

을 해보자. 목표는 8월 31일까지.


'Daily life' 카테고리의 다른 글

EBS Space 공감 보다가  (2) 2011.07.15
맥북프로 화면 구석이 노랗게 변색  (5) 2011.07.03
진화를 멈추라  (2) 2011.06.28
지를까 말까 지를까 말까 지를까 말까  (2) 2011.06.26
Raindrops  (0) 2011.06.18
Animals in Everland  (2) 2011.06.12
Trackbacks 0 : Comments 2

지를까 말까 지를까 말까 지를까 말까

Daily life 2011.06.26 12:58



회사 책상에 올려두고 싶다. ㅠㅠ

으으으

. 

'Daily life' 카테고리의 다른 글

맥북프로 화면 구석이 노랗게 변색  (5) 2011.07.03
진화를 멈추라  (2) 2011.06.28
지를까 말까 지를까 말까 지를까 말까  (2) 2011.06.26
Raindrops  (0) 2011.06.18
Animals in Everland  (2) 2011.06.12
-경- 생 일 -축-  (0) 2011.05.25
Trackbacks 0 : Comments 2

예당에서 한강까지

Daily life/Tour 2011.06.19 01:48
늦잠을 잔 관계로 예당에서 전시 3개 + 공연 하나를 보는 계획은 급 취소. 그냥 전시만 둘

- 오르세 미술관전(한가람 3층): 고흐의 별밤과 화가들의 꿈


오르세 미술관에 있는 그림과 사진들이 테마별로 - 신화, 노동, 레저, 자연 등등 - 전시되어 있다.
다 필요 없고 아를의 별이 빛나는 밤(La nuit etoilee, Arles) 진품을 봤다. 사진이나 이미지, 혹은 모조품으로는 메울 수 없는 간극이 있다. 내 막눈에도 보인다. ㅠ_ㅠ 이걸 보고 나니 몇십만원짜리 프린트나 모조품은 도저히 살수가 없다.

다른 그림들은 이에 비해 내 관심을 끌지는 못했는데, 이것은 그림 자체의 문제라기보다는 내가 처음부터 저 그림 한장을 보기위해서 저 전시회에 뛰어들었기 때문이다 ... ... 나오면서 고흐 그림 하드커버 노트 한권과 엽서 다섯장을 샀다. 엽서는 책상 옆에 붙여놔야지.

오르세 전 티켓이 있으면 2천원 할인을 받을 수 있는 다른 전시도 있더라. 바로 아래층(한가람 2층)에서 하고 있었는데 ... 아쉽게도 블루회원이면 이거랑 상관 없이 2천 할인이고 중복은 안되서 난 의미가 없었다.

- 지구상상 사진전


닉 브랜트(Nick Brandt)라는 작가 이름을 외울 수 있었다. 아프리카의 동물을 흑백으로 찍는 사진가인데, 사진만 보고서는 "대체 어떤 미친 망원렌즈를 쓰는거지 ㄷㄷㄷ" 하는 생각을 했는데 ... ... ... 위키 설명을 보고 그의 프로필까지 따라가서 읽어봤더니 이렇다고 한다.
I have three – a 55mm, 105mm and 200mm. So my longest lens is the 35mm equivalent of a 100mm, which hardly constitutes telephoto. However, I believe that you wouldn’t shoot a portrait of a human being on a 500mm lens from 100 feet away and expect to capture their personality. I feel the same way about photographing animals. So I photograph animals the same way I would photograph humans – the only difference is I can’t tell the animal to turn their head to the right and step back six inches.
그야말로 훌륭한 발줌 정신을 보여주고 계신다. 존경 ( ... )

그 외의 작가들 사진은... 뭔가 독특했는데, 이게 사진인지 CG인지 알 수 없는 애매한 상태였던게 개인적으로 좀 아쉬웠다.

이거 보고 나서 분수를 잠깐 구경하다가 애들 구경을 좀 했다. 날씨가 좋아서인지 가족 나들이 나온 사람이 많아 뵈더라.


----

그리고 컴백홈. 자전거 타고 한강으로 ... ... ... 가다가,

일차시기: 한눈팔고 달리다가 턱을 잘못 밟아서 넘어졌다. 반바지를 입고 있었는데 다리 아래쪽에 찰과상을 입었다. 집에 와서 보니 좀 빨갛게 부어 있어서 소독약을 바른...다기보다는 부어버리고 비명을 좀 질렀다.

이차시기: 원래 한강-도림천 합수부에서 한강대교-노들섬, 노들섬에서 좀 놀다가 갔던 길 돌거나 아니면 숭실대쪽으로 해서 돌아올 계획이었는데, 63빌딩 옆을 지날때 앞바퀴에 펑크 ... ... ... 결국 한시간동안 회사로 걸어가서 자전거를 던져놓고 왔다. 월요일에 LS 타워 지하 바이클로에 가서 정리를 좀 해야 할 듯.

오늘 자전거 타고 나갔던건 안좋은 선택이었을까. 크흑 ㅠㅠ

한강-도림천 합수부 - 염창교 - 는 자전거 라이더들의 오아시스다. ㅋㅋㅋ 여기서 강 건너에 보이는 좀 높은 공간이 뭘까 궁금했었는데 구글 지도를 띄워보고 첨 알게 되었다. 하늘공원이라고 한다. 담에 가봐야지.


Trackbacks 0 : Comments 0

Raindrops

Daily life 2011.06.18 05:50

얼마 전 비오던 날에 창가에 아이폰을 두고 녹음했던 빗소리.



소리가 작은게 아쉽다. 좀 키워볼까.

'Daily life' 카테고리의 다른 글

진화를 멈추라  (2) 2011.06.28
지를까 말까 지를까 말까 지를까 말까  (2) 2011.06.26
Raindrops  (0) 2011.06.18
Animals in Everland  (2) 2011.06.12
-경- 생 일 -축-  (0) 2011.05.25
킨들 활용 - ?  (1) 2011.04.24
Trackbacks 0 : Comments 0

Animals in Everland

Daily life 2011.06.12 03:11
에버랜드에 습격하여 찍어온 사진 몇 장. 여름이라 그런지 아이들이 힘들어 하더라. 그래도 귀엽다. 적어도 200mm 정도까진 땡길 수 있는 렌즈를 들고 갔으면 하는 아쉬움이 드는데 ... 나이가 들어서 그런가, - 아니 옛날에도 그랬던 것 같긴 한데 - 놀이기구 타는 것 보다 동물원이 훨 좋다.



Trackbacks 0 : Comments 2

에버랜드 레이드

Daily life/Tour 2011.06.11 23:44
엔써즈 문화쎈타 4인(1인 불참) 파티. 드레스 코드 동물 귀 머리띠.

날이 더웠습니다. 으어어어.


그래도 간만에 가니 재밌더군요. 음하하하.
 
Trackbacks 0 : Comments 0

-경- 생 일 -축-

Daily life 2011.05.25 03:53


http://deisys.net/378
에서 만들었던거 대강 손봐서 ... 30대 따위 아무것도 아니지!

http://katy.dgoon.net/~dgoon/fireworks/

'Daily life' 카테고리의 다른 글

Raindrops  (0) 2011.06.18
Animals in Everland  (2) 2011.06.12
-경- 생 일 -축-  (0) 2011.05.25
킨들 활용 - ?  (1) 2011.04.24
The Mountain by tesophotography.com Terje Sorgjerd / Hammock - Floating Away In Every Direction  (0) 2011.04.20
48/2(9+3)  (4) 2011.04.11
Trackbacks 0 : Comments 0

D 프로그래밍 언어 - 오타(?)

Daily life/Hard study 2011.05.12 15:02
22 page:

line.startswith(" ") 처럼 보이는 두 개는 각각
line.startswith("    ") - 4칸, line.startswith("  ") - 2칸

이다. 공백 여러개가 하나로 보여서 혼란에 빠질 수 있다.


262 page:

class Widget { ... }
final class UltimageWidget : Widget { ... }
class PostUltimateWidget : UltimateWidget { ... } // 오류 !!!

빨간색 Ultimate 부분이 빠져있다.

 
Trackbacks 1 : Comments 3

SICP study: 근황

Daily life/Hard study 2011.04.27 00:48

http://mitpress.mit.edu/sicp/full-text/book/book.html

이제 4년차다. 슈레인은 뭐 그리 느리냐고 줄창 까대지만, 흔들리지 않고 꿋꿋하고 느긋하고 여유있게 나가고 있다. 본래 우리 스터디의 수장이신 지아님의 뜻이 널럴하고 오래가는 스터디 아니었던가!

대강 훑어서 읽은 걸로는 수십번을 읽었고, 문제를 풀면서 뒤적뒤적, 앞에 했던거 기억이 안나서 돌아가 다시보기, 뒤에 나오는걸 미리 가져다 쓴다고 하면 뒤에 가서 보고 오기 ...

이제 5.1장을 막 덮었다. 괄호 범벅인 코드로 어셈코드와 이걸 해석하는 어셈블러, 레지스터 머신을 만들고 있으니 기분이 묘해진다.

여기까지 모든 문제를 다 푼 건 아니다. 내맘대로 스킵한 것도 있고, 풀고 정리하지 않은 것도 있긴 하지만 - 적어도 모든 문제를 다 읽었다고는 말할 수 있다. 풀지 못했거나, 잘못된 풀이를 내놓았더라도 문제가 무엇을 물어보는지, 무엇을 알아야 풀 수 있는지, 내가 어떤 시도를 했었는지에 대해서는 말할 수 있다는 정도?

아무래도 여름 즈음에는 일단 끝까지 다 볼 것 같다. (이후엔 HTDP를 좀 볼까 ... 생각중)

그러니 이제 슬슬 회고: 이 책이 나를 어떻게 바꿨는지, 내가 SICP를 읽으면서 어떤 성장을 했는지, 혹은 문제를 푸나 안푸나 별 차이 없었을지 - 에 대해서 정리 준비를 해야 하는데, 잘 모르겠다. 으앍 ㅋ 가장 크게 느껴지는건 꾸준히 공부하는 습관을 들인거, 스터디 멤버(대표적으로 지아대장님을 비롯한 컴키드님, 지송님, 솔리드원님, 야라님, 그리고 스쳐지나가셨던 모든 분들 + 옆 스터디(응?) 멤버들 ㅋㅋㅋ)들과 친해진 거 두가지인데, 사실 얘네들은 SICP와는 직접적인 관계가 없으니까 빼고 ... ... ...

분명 내가 몇년이나 질질 끌면서 이 책을 읽고 문제를 깨작거린 이유가 있을텐데, 이걸 깔끔하게 정리해야 한다. 그래야 다른 사람한테 추천할 수 있겠지?

으으, 고민해보자.

-----

http://wiki.dgoon.net/doku.php?id=sicp:structureandinterpretationofcomputerprograms
http://code.sicp.or.kr
http://wiki.sicp.or.kr

어쩌다보니 sicp.or.kr 을 내가 들고 있다. ( ... )
Trackbacks 0 : Comments 3

킨들 활용 - ?

Daily life 2011.04.24 17:04
아이폰 유저,
(회사물건이지만 거의 사유화해서 쓰고 있는) 맥북 프로,
(회사물건이며 가끔 내것처럼 들고 다니는) 아이패드 1.

아이폰을 통해서 아이패드나 맥북은 언제 어디서나 네트웍에 붙을 수 있다. 이 상황에서 킨들이 등장할 곳은?

... 책은 아마존에서 살 수 있는 녀석들에 한정이고, 페이퍼는 아이패드로 봐야하고 ...

이 상황에서 http://kindlefeeder.com/ 를 그냥 질러(-_-) 봤더니, 오! 훌륭하다.

아침에 눈을 뜨고 손을 뻗어 킨들을 잡으면 RSS 피드가 굴러와 있다. ... ... 아이패드는 (회사꺼긴 하지만) 충전하려면 어딘가 던져둬야 하기 때문에 + 무겁고 크다 - 좀 힘들지. 그리하여 킨들3(wifi)는 내 손에서 RSS리더+만화책리더로 다시 태어나고 있다.

 아마존을 뒤지다가 사고싶다고 생각하는 책들은 왜 e-book 이 없는거야 대체 ... OTL

 

'Daily life' 카테고리의 다른 글

Animals in Everland  (2) 2011.06.12
-경- 생 일 -축-  (0) 2011.05.25
킨들 활용 - ?  (1) 2011.04.24
The Mountain by tesophotography.com Terje Sorgjerd / Hammock - Floating Away In Every Direction  (0) 2011.04.20
48/2(9+3)  (4) 2011.04.11
회고: 잉여로운 2011년 2월  (2) 2011.03.02
Trackbacks 1 : Comments 1

The Mountain by tesophotography.com Terje Sorgjerd / Hammock - Floating Away In Every Direction

Daily life 2011.04.20 14:44
으아


The Mountain from TSO Photography on Vimeo.

'Daily life' 카테고리의 다른 글

-경- 생 일 -축-  (0) 2011.05.25
킨들 활용 - ?  (1) 2011.04.24
The Mountain by tesophotography.com Terje Sorgjerd / Hammock - Floating Away In Every Direction  (0) 2011.04.20
48/2(9+3)  (4) 2011.04.11
회고: 잉여로운 2011년 2월  (2) 2011.03.02
오늘의 삽질  (3) 2010.12.12
tags : 사진, 자연
Trackbacks 0 : Comments 0

48/2(9+3)

Daily life 2011.04.11 14:27



아 웃겨. ㅋㅋㅋ 이래서 어렸을때부터 prefix form 을 잘 가르쳐야 합니다.

내가 알던 컨벤션은, 숫자들의 경우 곱하기가 생략된 걸로 본다. 변수가 있는 경우는 우선순위를 준다. 였는데 ... 뭐 어쨌건 나랑은 상관 없지. 참고 링크 두개.

http://www.wolframalpha.com/input/?i=x%3D2%2C+y%3D%289%2B3%29%2C+48%2Fxy 
http://www.wolframalpha.com/input/?i=48%2F2%289%2B3%29 

 

'Daily life' 카테고리의 다른 글

킨들 활용 - ?  (1) 2011.04.24
The Mountain by tesophotography.com Terje Sorgjerd / Hammock - Floating Away In Every Direction  (0) 2011.04.20
48/2(9+3)  (4) 2011.04.11
회고: 잉여로운 2011년 2월  (2) 2011.03.02
오늘의 삽질  (3) 2010.12.12
500에러 기념샷 for GMail  (4) 2010.11.23
Trackbacks 0 : Comments 4

회고: 잉여로운 2011년 2월

Daily life 2011.03.02 09:38
What was good:
슬럼프를 극복...한 듯?
시간관리가 제대로 안되어서 GTD를 도입해보려고 하다가, 부분적으로만 가져다가 시스템 셋업.
한번에 하나의 일을 끝낸다 + 마친 일은 돌아보지 않는다 - 를 차용했음.
중간에 인터럽트가 걸릴 수 밖에 없기 때문에, 하루 단위로 "딱 하나의 이슈" 만 마치고 잊는다.

아침 출근 후 30분간 오늘 마칠 일을 계획, 그리고 계획한 일만 마치면 만족.
일찍 끝내게 되면 더 이상 신경쓰지 않고 아무거나 한다.
일찍 안끝나면 야근을 하건 밤을 하건 끝내고 나서 내일을 맞는다.
퇴근 시간 근처에 계획한 일이 마무리 되는 것이 베스트.

중요한 점은, 일을 마치고 나서 "잊어버리기" 위해서 끝난 것 처럼 보이는 일을 재검토를 하게 되었다.
그리고 끝났다고 생각했지만 끝나지 않은 상태 - 암이 재발하듯 - 로 둔 일이 얼마나 나중에 귀찮게 되는지 깨달았다.
코딩도 그렇지만 "마치기" 가 얼마나 힘든지 새삼 깨닫는다.


What was bad:
자전거로 출근을 아직 시작하지 못했다.
위의 일정 관리와 관련이 있는데, 일 단위를 크게 잡아서 새벽에 자는 일이 많아 제때 일어나지 못한 게 크다.
두번째로 회사에서 샤워를 하기가 힘든게 좀 문제인데 이건 어떻게 해결해야 하나 고민중.


What to do this month:
위키에 공부하는걸 그때그때 적어 두고는 있는데, 가끔은 하나의 완성된 아티클로 써 볼 필요가 있을 것 같다.
글쓰기 연습도 필요하고 아티클 형태의 퍼블리싱 경험도 괜찮을 것 같으니... 무슨 주제건 공부한 것에 대해 한달에 하나만 블로그에 쓴다.

SICP 4장을 끝낸다.


What I read:
플라톤의 국가 - 플라톤은 소크라테스를 정말 좋아했던 듯. 게이?
Moon lost - 호시노 유키노부의 만화책. 과학적인 내용은 좀 말이 안되는 부분도 있긴 하지만, 스토리텔링이 괜찮다.  달이 사라지며 지구에 발생하는 재난 같은 경우에 꽤 설득력이 있어 보였는데, 어디서 자문을 얻은걸까 궁금하다.
진격의 거인 - 1권 뿐인데, 암울한 디스토피아적 세계관이 왠지 끌림. 피안도 생각이 좀 난다.


What I'm reading:
The timeless way of building: The quality without name 에 대한 이야기를 한다. 더 읽어 봐야 뭔가 말할 수 있을 듯.

Trackbacks 0 : Comments 2

오늘의 삽질

Daily life 2010.12.12 23:16
http://www.facebook.com/careers/puzzles.php

이거나 좀 풀어볼까 - 라는 생각에,

1. 터미널에서 이메일을 보내고 받을 수 있도록 세팅 with Mutt - http://www.andrews-corner.org/mutt.html 참고
2. Content-disposition 쪽 버그(?)때문에 mime-construct | msmtp 로 쏨. 파일 확장자도 떼라고 하네 귀찮게 ( ... )
if [ $# -eq 1 ]; then
    keyword=`echo $1 | sed 's/\..*//g'`
    cp $1 /tmp/$keyword
    mime-construct --to 1051962371@fb.com --cc MY_EMAIL --subject $keyword --file-attach /tmp/$keyword --output | msmtp 1051962371@fb.com
    rm /tmp/$keyword
    echo "Puzzle $keyword submitted <$1>."
else
    echo "Usage: $0 attachment-file"
fi
3. 하는김에 dokuwiki - http://wiki.dgoon.net - 업그레이드 + gitweb 페이지 고치기(http://www.deisys.net/404)
4. 테스트로 hoppity, meepmeep을 서밋했는데 4시간마다 도는거라고 하니 ( .. ) 좀 되라. -_-;

그리고 여러가지 회사-관련 코드작업 - ... - 들.

잉여롭고, 평화롭구나.

'Daily life' 카테고리의 다른 글

48/2(9+3)  (4) 2011.04.11
회고: 잉여로운 2011년 2월  (2) 2011.03.02
오늘의 삽질  (3) 2010.12.12
500에러 기념샷 for GMail  (4) 2010.11.23
2010년 10월 자출기록  (0) 2010.10.30
올해 마지막 지름  (4) 2010.10.22
Trackbacks 0 : Comments 3

500에러 기념샷 for GMail

Daily life 2010.11.23 08:03
야호!



오늘 아침엔 신기한 구경을 다 하네 ㅎㅎㅎ

'Daily life' 카테고리의 다른 글

회고: 잉여로운 2011년 2월  (2) 2011.03.02
오늘의 삽질  (3) 2010.12.12
500에러 기념샷 for GMail  (4) 2010.11.23
2010년 10월 자출기록  (0) 2010.10.30
올해 마지막 지름  (4) 2010.10.22
사고싶지만, 가격이 안드로메다  (2) 2010.10.18
tags : 500, Gmail
Trackbacks 1 : Comments 4

잉여로운 출근길

Daily life/Tour 2010.11.23 08:01


집->보라매공원->도림천+한강 합수부->한강대교->회사

얼마전 퇴근길 답사에서도 딱 한시간 이십분 정도 걸렸고 26.??km 가 나왔으니 대강 저정도가 맞는 듯 하다.

그냥 봉천고개 넘어가면 30분도 안걸리는 길이라 심심했는데, 간혹 일찍 나와서 여유있게 잉여잉여 노래 부르며 출퇴근 할 수 있겠다.

ㅎㅎㅎ

Trackbacks 0 : Comments 0

2010년 10월 자출기록

Daily life 2010.10.30 21:25
총 거리: +174.5KM (누적 220.7KM)
탄 횟수: 22회 (출퇴근 21회)

1KM 당 100원 적립(!) -> 17450원 (누적 17450원)

현재 몸무게 68.2kg. 올해가 가기 전에 63kg 까지 빼면 메영님에게 샤브샤브를 얻어먹는다. 크릉크릉!


'Daily life' 카테고리의 다른 글

오늘의 삽질  (3) 2010.12.12
500에러 기념샷 for GMail  (4) 2010.11.23
2010년 10월 자출기록  (0) 2010.10.30
올해 마지막 지름  (4) 2010.10.22
사고싶지만, 가격이 안드로메다  (2) 2010.10.18
안경 교체  (4) 2010.10.11
Trackbacks 0 : Comments 0

올해 마지막 지름

Daily life 2010.10.22 21:44
... 이길 바랍니다.


눈이 나빠서 편하게 쓸만한 녀석 찾느라 고생했습니다.

프레임은 루디프로젝트 익셉션 STD, 겉렌즈는 Impact X Photochromic Grey, 도수 클립은  아이닥(http://www.eyedaq.com)에서 장착.

... 하지만, 아무래도 스키/보드용 고글은 따로 장만해야할 듯 합니다. 그래서 난 스키장엔 안갈 생각. (응?)

'Daily life' 카테고리의 다른 글

500에러 기념샷 for GMail  (4) 2010.11.23
2010년 10월 자출기록  (0) 2010.10.30
올해 마지막 지름  (4) 2010.10.22
사고싶지만, 가격이 안드로메다  (2) 2010.10.18
안경 교체  (4) 2010.10.11
자출 근황  (0) 2010.10.11
Trackbacks 0 : Comments 4

예술의 전당 수묵화반, 전시회 준비

Daily life/Hard study 2010.10.21 00:32
뭐 그리지 ... ? 엔써문화쎈타에서 고민하면서 붓펜으로 이것저것 깨작깨작. 이런 꽉찬 느낌으로 해볼까...



Trackbacks 0 : Comments 3