4월26일 목요일에 월차내고 SW공학센터에 다녀왔습니다.


아래 사진은 광역 슬립 스킬을 시전하고 있는 모습입니다.

이런 대규모 강의에 익숙하지 않다보니 본의아니게 졸립게 만들어 들인듯 합니다. 아니면 전날 디아블로3 클로즈베타 시작일이라서 그런것이라 위로를...


ps. 이런 자리를 만들어준 강승준 책임, 땡큐~


AND

며칠 전, 강의+자문 요청이 들어왔다.

그 업체에서 자사 엔진 위에 사용자 변경이 가능한 script를 올리고 싶은데, script 언어들에 대한 대략적인 소개 및 비교를 하고, 요구사항들에 대한 가능성, 디자인 등에 대한 조언을 요청한 것이었다.

남 앞에 서는것도 별로 안 좋아하고 방콕 스타일인 나에겐 일상적이지 않은 일이기에 심사숙고를 하다가, 진행하기로 결정하고 오늘 다녀왔다.

첫번째 시간은 2시간 동안 진행되는 강의세션, 두번째 시간은 2시간 동안 해당 프로젝트에 대한 논의 세션으로 진행했다. 개인적으론, 양쪽 모두 원하는 바를 얻은 유익한 시간이었다고 생각하고 있는 중이다... :)

아래 자료는 내가 맡은 부분들에 대한 발표자료들이다. 언젠가 쓸모 있지 않을까 해서 slideshare에 올려 놓았다 ㅎㅎ




ps. C binding쪽의 발표를 맡은 최동진선임도 밤 늦게까지 수고 많이 하셨어요.



AND

간만에 정규식에 대해서 정리할 일이 생겨서 블로그에도 정리해 봅니다. :)


정규식에는 greedy한 방식과 lazy(non-greedy)한 방식이 있습니다.

일예로 ".*"의 경우가 greedy한 방식입니다. 즉, 조건을 만족하는 한 가장 긴 문자열을 선택하려고 합니다.
그리고 반대의 경우, ".*?"의 경우가 lazy한 방식입니다. 조건을 만족하는 한 가장 짧은 문자열을 선택하려고 합니다.

음, 저도 써 놓고 보니 무슨 소리인지 모르겠네요 :)
예제를 살짝 바꿔서 돌려보면 차이를 이해하실 수 있을 것입니다.

아래와 같이 "<"과 ">"로 둘러싼 문자열을 greedy하게 선택해 보겠습니다.  (@python 2.6)

>>> import urllib
>>> html = urllib.urlopen('http://www.python.org').read()
>>> import re
>>> greedy = re.compile(r'<.*>', re.I|re.S)
>>> len( greedy.findall(html) )
1

엇, 결과 개수가 1개네요. 즉, <>로 둘러싼 가장 긴 문자열을 선택하려고 했으니 html 태그 전체를 선택하게 된 것을 보
실 수 있습니다.
이번에는 살짝 바꿔서 lazy하게 선택해 보겠습니다.

>>> lazy = re.compile(r'<.*?>', re.I|re.S)
>>> len ( lazy.findall(html) )
469

네, 이번에는 <>로 둘러싼 짧은 문자열을 선택했으니, 모든 태그들을 선택하게 되었습니다.


원문 : http://groups.google.com/group/python3/browse_thread/thread/c6c40a3738818d77
AND

만화 트윗봇

개발/python 2010. 4. 15. 13:26
요즘 제가 RSS보다 트위터를 자주 보는거 같아서, 만화전용 트윗봇을 런칭했습니다 --;
게다가 자주가던 번역 블로그들마저, 저작권에 걸려 추풍낙엽으로 떨어져 나가더라구요.

수집 대상 만화는 강철의 연금술사, 클레이모어, 나루토, 블리치, 헌터x헌터, 원피스입니다.
강철의 연금술사, 클레이모어는 1달에 1번 나오고
나루토, 블리치, 헌터x헌터, 원피스는 매주 금요일 정도에 나오네요.


이 만화들에 관심있으시면 팔로우 하세요~
http://twitter.com/hotmanga

다만 단점은 영문이라는 점과 만화선정이 제맘이라는... --;
감사합니다.
AND

Train Timetable

개발/python 2009. 8. 22. 01:03
이번에는 두번째 문제인 기차시간표입니다.

문제는 다음과 같습니다.


제일 가까운 기차들을 모두 이어주고,
앞뒤로 이어지지 않은 기차들을 카운트해주면 됩니다.

소스코드는 다음과 같습니다.

  1. import sys
  2. import datetime
  3.  
  4. class train:
  5.   def __init__(self, row, dir):
  6.     st = row.split(' ')[0]
  7.     end = row.split(' ')[1]    
  8.     self.start = datetime.datetime( 2009,1,1, int(st.split(':')[0]), int(st.split(':')[1]) )
  9.     self.end = datetime.datetime( 2009,1,1, int(end.split(':')[0]), int(end.split(':')[1]) )
  10.     self.dir = dir
  11.     self.reserved1 = 0
  12.     self.reserved2 = 0
  13.  
  14. def asc_start(a,b):
  15.   if a.start < b.start:
  16.     return -1
  17.   else:
  18.     return 1
  19.  
  20. def desc_end(a,b):
  21.   if a.end < b.end:
  22.     return 1
  23.   else:
  24.     return -1
  25.  
  26. if __name__=="__main__":
  27.   if len(sys.argv)>1:
  28.     inp = sys.argv[1]
  29.   else:
  30.     print "append an input file param"
  31.     sys.exit()
  32.  
  33.   f = open( inp, 'rt' )
  34.   nTC = int( f.readline() )
  35.   print 'the number of tc :', nTC
  36.  
  37.   output_file = inp.split('.')[0]+"_output.txt"
  38.   fout = open( output_file, 'wt')
  39.  
  40.   for i in range(0,nTC):
  41.     turnaround = int( f.readline() )
  42.    
  43.     NA_NB = f.readline()
  44.     NA = int( NA_NB.split(' ')[0] )
  45.     NB = int( NA_NB.split(' ')[1] )
  46.    
  47.     trains = []
  48.     for n in range(0,NA):
  49.       row = f.readline()
  50.       trains.append( train( row, 'A' ) )
  51.     for n in range(0,NB):
  52.       row = f.readline()
  53.       trains.append( train( row,'B') )
  54.  
  55.     trains.sort(desc_end)
  56.    
  57.     #process
  58.     ta = datetime.timedelta( 0, turnaround*60 )
  59.    
  60.     for t1 in trains:
  61.       if t1.reserved2>0: continue
  62.       cands = []
  63.       for t2 in trains:
  64.         if t2.reserved1>0: continue
  65.          
  66.         if ( t1.end + ta <= t2.start ) and (t1.dir != t2.dir):
  67.           cands.append( t2 )
  68.          
  69.       if cands:
  70.         cands.sort( asc_start )
  71.         min = cands[0]
  72.         #print t1.start, t1.end, t1.dir
  73.         #print '\t',min.start, min.end, min.dir
  74.        
  75.         t1.reserved2 = min.reserved1 = 1
  76.  
  77.     NA = NB = 0
  78.     for t in trains:
  79.       if t.reserved1==0:
  80.         if t.dir=='A':
  81.           NA+=1
  82.         else:
  83.           NB+=1
  84.  
  85.     # result
  86.     print 'Case #'+str(i+1)+':' ,NA, NB
  87.     fout.write( 'Case #'+str(i+1)+': ' +str(NA) + ' '+str( NB)+'\n')
  88.  
  89.   f.close()
  90.   fout.close()


실행결과는 다음과 같으며, 모두 Correct입니다.


AND

Saving the Universe

개발/python 2009. 8. 21. 01:48
요즘 기분이 꿀꿀해서 CodeJam 2008 Qualification Round 문제 중 하나인, 'Saving the Universe'를 풀어봤습니다.

1번 문제(Saving the Universe)는 다음과 같습니다.
문제링크 : http://code.google.com/codejam/contest/dashboard?c=agxjb2RlamFtLXByb2RyEAsSCGNvbnRlc3RzGI36AQw#

그냥 간단하게 검색엔진 list를 둬서 카운트 하다가 꽉차면 다시 비우고를 반복하면 끝입니다.
파이썬 코드는 다음과 같습니다. (2.5.4버전으로 했음)

  1. import sys
  2.  
  3. def isAllChecked(d):
  4.   ret = True
  5.   for k in d.keys():
  6.     if d[k]<1:
  7.       ret = False
  8.   return ret
  9.  
  10. if __name__=="__main__":
  11.   if len(sys.argv)>1:
  12.     inp = sys.argv[1]
  13.   else:
  14.     print "append an input file param"
  15.     sys.exit()
  16.  
  17.   f = open( inp, 'rt' )
  18.   nTC = int( f.readline() )
  19.   print 'the number of tc :', nTC
  20.  
  21.   output_file = inp.split('.')[0]+"_output.txt"
  22.   fout = open( output_file, 'wt')
  23.  
  24.   for i in range(0,nTC):
  25.     nEngine = int( f.readline() )
  26.     engines = []
  27.     for e in range(0,nEngine):
  28.       engine = f.readline()
  29.       engines.append( engine )
  30.     nKeyword = int( f.readline() )
  31.     keywords = []
  32.     for k in range(0,nKeyword):
  33.       keyword = f.readline()
  34.       keywords.append( keyword )
  35.    
  36.     # process
  37.     dEngine = {}
  38.     for e in engines:
  39.       dEngine[e]=0
  40.     nCount = 0
  41.     for k in keywords:
  42.       if k in dEngine.keys():
  43.         dEngine[k] += 1
  44.         if isAllChecked(dEngine):
  45.           for e in engines:
  46.             dEngine[e] = 0
  47.           nCount += 1
  48.           dEngine[k] += 1
  49.          
  50.     # result
  51.     print 'Case #' + str(i+1)+': '+str( nCount )
  52.     fout.write( 'Case #' + str(i+1)+': '+str( nCount ) + '\n' )
  53.      
  54.   f.close()
  55.   fout.close()

실행결과는 다음과 같으며, 둘다 Correct 입니다.


AND

요즘 뜨는 SNS인 트위터를 회사에서 모니터링하기에는 너무 눈치가 보여서, 브라우저 플러그인이나 독립어플이 아닌 커멘트라인으로 어떻게 바꿀 수 있을까 생각 중이었는데요. 트위터는 open api가 잘 되어있고, python-twitter 모듈이 쉽게 정리되어 있어서 구현이 정말 쉽더군요.

특히 왜 이렇게 스팸들이 많은지 이해할 수 있었습니다. 모니터링하면서 following하고 스팸 뿌리기가 너무 쉽네요 --;

뭐, 하여간 회사에서 약간 투명하게 커맨드라인 설정해 놓고 쓰기 적당하게 대충 짜 보았습니다. 소스 링크는 다음과 같습니다.
[peeping_tweet.zip]
소스는 보시다시피 거의 python-twitter 그대로입니다 --;

peepingtweet.py를 열어보시면 소스 상단에,
login_id = ""
login_password = ""

과 같이 되어있는데요.
이렇게 되어 있는 경우에는 아래 스샷과 같이 public tweet들이 보이게 됩니다,



자신의 친구들 글을 보려면 login_id와 login_password에 적당한 값을 넣으면 아래 스샷과 같이 제대로 나오게 되는 것을 확인할 수 있습니다.


회사에서는 우분투에서 돌려야지... --;

ps1. python 2.5 기반입니다.
그러므로 혹시나 파이썬이 안깔려있다면 까셔야합니다.
http://python.org/download/releases/2.5.4/

ps2. py2exe는 패스...
아, 그리고 이건 모니터링 온리입니다. 글 올리는 기능은 없습니다.
물론 넣기는 쉽겠지만, 제가 그러한 요구사항을 못 느껴서요 ...
필요하신 분들은 걍 추가해서 사용하세요...
AND

yes24 판매지수의 로그가 남지 않아서,
판매량의 변화를 알고 싶어서 간단하게 짰습니다.

예약작업이나 시작프로그램에 넣으시거나, crontab 등에 등록해서 돌리면 됩니다.
하루에 한번만 실행되며, 레코드가 이미 존재하는 경우에는 다시 쓰지 않습니다.
sqlite3를 사용하므로, 데이터확인은 직접 콘솔로 확인하셔도 되고, db 클래스의 확인용 함수를 사용하셔도 됩니다. 저는 아래 그림과 같이 firefox용 sqlite manager를 써서 확인합니다.



소스코드는 다음과 같습니다.
수집대상을 바꾸려면 초반의 books 사전정보를 변경하시면 됩니다.

import urllib2, time, traceback
from BeautifulSoup import BeautifulSoup
import sqlite3

books = {
    'python':'http://www.yes24.com/24/goods/3432490',
    'lua':'http://www.yes24.com/24/goods/3081202'
}

def getContent( url ):
    req = urllib2.Request( url )
    response = urllib2.urlopen(req)
    return response.read()

class DB:
    "SQLITE3 wrapper class"
    def __init__(self):
        self.conn = sqlite3.connect('bookDB')
        self.cursor = self.conn.cursor()
        for title in books.keys():
            self.cursor.execute('CREATE TABLE IF NOT EXISTS %s(date text, sale int)'%title)
            self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS IDX001 ON %s(date)'%title)
       
    def __del__(self):
        self.conn.commit()
        self.cursor.close()

    def insertPython(self, title, date, sale):
        try:   
            self.cursor.execute("INSERT INTO %s VALUES ('%s',%d)"%(title,date,sale))
        except:
            print '%s : maybe already inserted'%title
            return 0
        else:
            print '%s: success'%title
            return 1

    def printPythonResult(self, title):
        self.cursor.execute('SELECT * FROM %s ORDER BY date ASC'%title)
        for row in self.cursor.fetchall():
            print row[0],'\t', row[1]

    def printPythonResult(self, title, num):
        self.cursor.execute('SELECT * FROM %s ORDER BY date DESC LIMIT %d'%(title,num))
        for row in self.cursor.fetchall():
            print row[0],'\t', row[1]

db = DB()

if __name__ == "__main__":
    curtime = time.localtime()
    curday = "%d/%02d/%02d"%(curtime[0],curtime[1],curtime[2])
   
    for title,url in books.items():
        content = getContent( url )
        soup = BeautifulSoup( content )
       
        a = soup('dt', {'class':'saleNum'})
        salenum = -1
        if len(a)>0:
            try:
                text = str( a[0].contents[0] ).split('|')[1]
                #print text
                splited = text.split(' ')
                for s in splited:
                    if s.isdigit():
                        salenum = int(s)
                        break           
            except:
                traceback.print_exc()
               
            print title, ': try to insert :',curday, salenum
            db.insertPython( title, curday, salenum )
           
            print title, ': === recent 10 sale points ==='
            db.printPythonResult( title, 10 )
       
    time.sleep(5) # for reading results....

파일 다운로드 : [ salepoint_checker.py ]

ps. python 2.5 기반입니다.
  
AND

4. Rss.py는 다음과 같습니다.
mod_python으로 연결해 놓으시면 됩니다.

# -*- coding: utf-8 -*-

from mod_python import apache
import pickle, re
import os.path, time

url_head = "http://asialadders.battle.net/war3/ladder/W3XP-player-profile.aspx?Gateway=Kalimdor&#38;&#38;PlayerName="

def conv( date ):
    ds = date.split(',')
    ds1 = ds[1].split(' ')
    ds2 = ds[2].split(' ')
    date = ds[0][:3]+', '+ds1[2]+' '+ds1[1][:3]+' '+ds2[1]+' '+ds2[2]+' '+ds2[3]
    return date
def getInfo( ):
    f = open('/var/www/war3/info', 'rb')
    info = pickle.load( f )
    f.close()
    return info

def handler(req):
    req.content_type="Text/xml"
    req.send_http_header()

    t = os.path.getctime('/var/www/war3/info')
    pubdate = time.asctime( time.gmtime(t) )

    body ="""<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>Frozen Throne - Kalimdor Tracer</title>
        <link>http://cybershin.x-y.net/tt/</link>
        <description>누가누가 달렸나 모니터링 시스템</description>
        <language>ko</language>
        <pubDate>%s</pubDate>
        <generator>dsp generator</generator>""" %pubdate
    footer = """
    </channel>
</rss>"""
    info = getInfo()
    for i in info:
        user, level, date = i
        date = conv(date)
        level = level.replace('l','l ')
        link = url_head + user
        body += """
        <item>
            <title>%s</title>
            <link>%s</link>
            <description>%s, Last Ladder Game : %s</description>
            <author>(%s)</author>
            <guid>%s#%s</guid>
            <pubDate>%s</pubDate>
        </item>"""%(user, link, level, date, user, link, re.sub(' ','',date), date )
        # Sat, 18 Apr 2009 00:15:00 +0900
    body += footer
    req.write( body )
    return apache.OK


5. 성공적으로 실행되면 브라우저상에서 다음과 같이 잘 출력되는 것을 볼 수 있으며,
hanrss, outlook 등으로 연결해서도 잘 되는 것을 확인할 수 있습니다.

Frozen Throne - Kalimdor Tracer
누가누가 달렸나 모니터링 시스템
     
alpakook
Level 12, Last Ladder Game : Sat, 09 May 2009 1:54 AM
dspshin
Level 6, Last Ladder Game : Fri, 08 May 2009 12:40 AM
soudz
Level 22, Last Ladder Game : Thu, 23 Apr 2009 12:58 AM
milkelf
Level 9, Last Ladder Game : Sun, 03 May 2009 8:20 PM
sacrea
Level 2, Last Ladder Game : Sun, 12 Apr 2009 8:31 PM
again4you
Level 1, Last Ladder Game : Tue, 05 May 2009 6:49 PM

AND

이번 예제는 친구들이 워3를 했나안했나 체크해서,
rss로 제공해 주는 rss feed generator입니다. ㅎㅎ

1.
저는 Python으로 할 것이므로 mod_python을 설치/설정합니다.
당연히 PHP등 다른 언어 사용해도 무방.
> 방법 : http://cybershin.x-y.net/tt/188 참고.

2.
이 예제는 특정 사이트를 모니터링하며 해당 정보가 업데이트되면 RSS로 알려주는 예제입니다.
고로 Request가 올때마다 특정 사이트를 읽어서 답하면 너무 늦으므로 프로세스를 2개로 나눠서 실행합니다.
즉, 정보 수집을 맡는 Crawl.py 와 RSS결과를 반환해 주는 rss.py로 분리.

3. Crawl.py는 다음과 같습니다.
crontab 등으로 하루에 몇번만 실행시키면 됩니다.


#!/usr/bin/python
# -*- coding: utf-8 -*-

import urllib2, re, pickle, sys, time
from BeautifulSoup import BeautifulSoup

users = [
'alpakook', 'dspshin', 'soudz', 'milkelf', 'sacrea','again4you'
]

url_head = "http://asialadders.battle.net/war3/ladder/W3XP-player-profile.aspx?Gateway=Kalimdor&PlayerName="

def getInfo( user ):
    url = url_head + user
    contents = urllib2.urlopen(url).read()
    soup = BeautifulSoup( contents )
    B = soup('b', {'class':'small'})
    date = ''
    for b in B:
        if str(b).find(':')>-1:
            date = b.contents[0].strip().encode('ascii')

    level = ''
    lv = soup('div', {'style':"Z-INDEX: 200; LEFT: 75px; POSITION: relative; TOP: -25px"})
    if len(lv)>0:
        body = str(lv[0])
        sp = body.find('Level')
        ep = body[sp:].find('<')
        level = body[sp:sp+ep].strip()
        level = re.sub('\s', '', level)

    return level, date

if __name__=="__main__":
        print sys.version
        print 'run crawl.py : '+ time.strftime("%B %dth %A %I:%M ",time.localtime())
        info = []
        for user in users:
                try:
                        level, date = getInfo(user)
                except:
                        print sys.exc_info()
                else:
                        print user, level, date
                        info.append( (user, level, date) )

        #print info
        f = open('/var/www/war3/info', 'wb')
        pickle.dump( info, f )
        f.close()



>>> 나머지는 다음 글에...


AND