2018년을 맞이해서 열심히 풀기 시작했던 Codefights.com 의 Arcade - Python 부분의 모든 문제를 클리어하였다.

전 문제가 영어로 나오는데다 가끔 문제 수준이 언어에 대한 이해도가 상당히 높아야 해결 가능한 경우가 있어서 여러 번 막혔지만, 그래도 몇시간 붙잡고 풀다 보니 전부 푸는데 성공할 수 있었다.



 그 중에서도 가장 재밌었던 챕터를 꼽으라면 55~64번째번째 문제가 속한 Drilling the Lists 가 아닐까 싶다. 그림에서도 보다시피 map, filter, reduce 및 lambda를 자유자재로 활용하여 짧고 간결하고 효율적인 코드를 작성하는 방법에 대한 문제들이다. 실제로 여기서 문제를 풀다 보니 회사에서 사용하는 파이썬 코드도 좀 더 간결하고 효율적으로 짤 수 있게 되었고, 활용의 폭이 매우 넓어졌다고 느끼고 있다. 매일 한 문제 이상 푸는 것을 목표로 풀어나가고 있는데, 이 속도대로라면 이번 년도가 끝나기 전 Arcade를 전부 클리어 가능하지 않을까 하고 기대하고 있다.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

댓글을 달아 주세요

파이썬 공부를 계속하다가 데코레이터(Decorator)를 공부하게 되었고, 그 내용을 정리하고자 포스팅한다.


여기 아주 엄청난 일을 하는 함수가 있다.

1
2
def HelloName(name):
    print "Hello, %s!" % (name)
cs

 무려 인자로 이름을 입력받아 인사를 해주는 무척 대단한 함수이다. 아무 문제 없이 함수를 사용하다 보니, 이제 만났을때 하는 인사 외에도 헤어질 때 하는 인사를 만들고 싶어졌다. 정말 열심히 공을 들여서 다음과 같은 함수를 만들어냈다.


1
2
def GoodbyeName(name):
    print "Goodbye, %s!" % (name)
cs

 만났을 때 인사하고, 헤어질 때 인사하고 나니 안부를 묻는 말도 만들고 싶어졌다. 또 열심히 공을 들여서 다음과 같은 함수를 만들어냈다.


1
2
def HowAreYouName(name):
    print "How are you, %s?" % (name)
cs

 뿌듯하게 바라보는 와중, 문득 머릿속에서 이 함수들의 기능만으로는 너무 밋밋하다는 생각이 들어서 출력하기 전에 위아래에 경계선을 출력하도록 만들고 싶어졌다. 그래서 만들어진 결과가 다음과 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
def HelloName(name):
    print "============================"
    print "Hello, %s!" % (name)
    print "============================"
 
def GoodbyeName(name):
    print "============================"
    print "Goodbye, %s!" % (name)
    print "============================"
 
def HowAreYouName(name):
    print "============================"
    print "How are you, %s?" % (name)
    print "============================"
cs

 딱 봐도 무엇인가 많이 반복되고 있고, 불편함이 느껴진다. 이 함수들은 모두 다른 함수들이지만, 공통적으로 위아래에 경계선을 출력한다는 행위는 동일하다.

 파이썬에서 함수는 다른 함수의 인자로 넘길 수도 있고, 리턴할 수도 있는 특징을 가지고 있다. 이를 이용해서 일단 데코레이터를 사용하지 않고 원시적인 방법을 사용하여 이 문제를 해결해 보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def AddLine(targetFunction, args):
    print "============================"
    targetFunction(args)
    print "============================"
 
def HelloName(name):
    print "Hello, %s!" % (name)
 
def GoodbyeName(name):
    print "Goodbye, %s!" % (name)
 
def HowAreYouName(name):
    print "How are you, %s?" % (name)
 
def main():
    AddLine(HelloName, "5kyc1ad")
    AddLine(GoodbyeName, "5kyc1ad")
    AddLine(HowAreYouName, "5kyc1ad")
cs

 함수와 그 인자를 넘겨 함수 시작 전, 함수가 끝난 후에 경계선을 출력하도록 하는 새로운 함수를 만들고, 그것을 이용해 각자의 함수를 실행하도록 변경해 보았다. 원하던 대로 결과가 나오기는 하지만, 무엇인가 불편한 기분이 드는 것은 여전하다. 호출하고 싶은 함수는 HelloName, GoodbyeName, HowAreYouName 인데 실제 호출하는 함수는 AddLine이고, 인자의 전달도 직관적으로 눈에 들어오지 않는다.

직관적으로 HelloName 함수를 실행하는 것이 보이면서 인자의 전달도 깔끔하게 정리할 수 있는 방법이 없을까?

답은 '함수를 새로 만들어서 사용하는 것' 이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def AddLine(targetFunction):
    def wrapper(args):
        print "============================"
        targetFunction(args)
        print "============================"
    return wrapper
 
def HelloName(name):
    print "Hello, %s!" % (name)
 
def GoodbyeName(name):
    print "Goodbye, %s!" % (name)
 
def HowAreYouName(name):
    print "How are you, %s?" % (name)
 
def main():
    newHelloName     = AddLine(HelloName)
    newGoodbyeName   = AddLine(GoodbyeName)
    newHowAreYouName = AddLine(HowAreYouName)
 
    newHelloName("5kyc1ad")
    newGoodbyeName("5kyc1ad")
    newHowAreYouName("5kyc1ad")
cs

 AddLine 함수 내에 wrapper라는 이름으로 다른 함수 하나를 선언하고, 내부에서 인자로 전달받은 함수를 실행하기 전, 실행이 끝난 후에 줄을 추가로 출력하도록 한 후, AddLine 함수가 끝날 때 wrapper 함수 자체를 반환하도록 만들었다. 이제 main 에서 AddLine 함수에 원하는 출력 함수를 넣은 후 리턴된 함수를 받아와서 사용해주면 된다.

 정답에 아주 가까워졌지만 여전히 불편한 점이 몇가지 있다. 내가 선언한 함수는 HelloName인데 새로 newHelloName을 만들어서 사용해주어야 한다는 점, 함수마다 전부 저 작업을 추가로 해주어야 한다는 점 등이다. 선언한 함수를 그냥 사용할 수 없다는 점에서 또 직관성이 떨어지는 코드이기도 하다.

 이 모든 문제점을 해결하기 위해 사용하는 것이 데코레이터(Decorator)이다. 다른 함수들의 시작 전이나 후에 특정한 장식(Decoration)을 붙여 주는 것이다. 여기의 예제에서는 위아래에 붙은 경계선이 '장식' 되시겠다.

사용법은 생각보다 무척이나 간단하다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def AddLine(targetFunction):
    def wrapper(*args, **kwargs):
        print "============================"
        targetFunction(*args, **kwargs)
        print "============================"
    return wrapper
 
@AddLine
def HelloName(name):
    print "Hello, %s!" % (name)
 
@AddLine
def GoodbyeName(name):
    print "Goodbye, %s!" % (name)
 
@AddLine
def HowAreYouName(name):
    print "How are you, %s?" % (name)
 
def main():
    HelloName("5kyc1ad")
    GoodbyeName("5kyc1ad")
    HowAreYouName("5kyc1ad")
cs

wrapper 에서 받는 인자가 *args, **kwargs 가 되어서 당황할 수 있지만, 사실 이는 가변 인자를 넘겨주기 위한 것으로 생각보다 간단한 개념이다. 여기에 잘 설명되어 있으므로 참고.

 AddLine 함수는 달라진 점이 전혀 없고, 각 함수 위에 @AddLine 한 줄씩이 추가된 것을 볼 수 있다. @AddLine은 데코레이터 AddLine을 이 함수에 적용시키겠다는 의미이다. 각 함수들은 선언 순간 데코레이터 AddLine이 적용되어 리턴된 wrapper 함수를 자기 자신으로서 갖게 되고, 따라서 사용할 때는 별도의 작업 없이 선언한 함수를 그대로 사용하면 된다.

이것이 가장 기초적인 데코레이터의 사용법이다.


이제 여기서 한 계단만 더 올라가보자.

 데코레이터를 사용하다 보면 데코레이터 자체가 인자를 받는 모습을 볼 수 있다. 예를 들면 Flask를 이용한 REST API 개발을 해본 사람이라면, 다음과 같은 코드가 함수 앞에 붙는 것을 봤을 것이다.


1
2
3
@app.route("/file", methods=["GET"])
def blah_blah():
    pass
cs

 딱 봐서는 blah_blah 라는 함수에 app.route라는 데코레이터를 적용하는 코드로 보이는데, 지금까지 보지 못한 부분이 추가되었다.

바로 app.route에 ("/file", methods=["GET"]) 로 인자를 받는 부분이 추가되었다는 것이다.


 생각해 보면 지금까지 작성한 예시에서 데코레이터가 하는 일을 나타내어 봤을 때 '함수 시작 전과 끝난 후에 특정 줄을 하나씩 출력한다'라고 표현할 수 있다. 그런데, 모든 함수가 다 같은 줄만을 출력하는 것이 아닌 특정 문자열을 출력하고 싶은 것일 수도 있다. 즉, 같은 데코레이터를 이용하여 다른 내용을 출력할 수 있도록 만들고 싶을 수 있는 것이다.

 이렇게 데코레이터에 인자를 넣기 위해서는 데코레이터 함수를 한번 더 감싸주면 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def AddLine(targetStr):
    def RealDecorator(targetFunction):
        def wrapper(*args, **kwargs):
            print targetStr
            targetFunction(*args, **kwargs)
            print targetStr
        return wrapper
    return RealDecorator
 
@AddLine("===HelloName===")
def HelloName(name):
    print "Hello, %s!" % (name)
 
@AddLine("===GoodbyeName===")
def GoodbyeName(name):
    print "Goodbye, %s!" % (name)
 
@AddLine("===HowAreYouName===")
def HowAreYouName(name):
    print "How are you, %s?" % (name)
 
def main():
    HelloName("5kyc1ad")
    GoodbyeName("5kyc1ad")
    HowAreYouName("5kyc1ad")
cs

이런 식으로 말이다.

함수 위에 @AddLine으로 명시된 함수들은 선언 순간 AddLine 데코레이터를 적용시키게 되며, AddLine 데코레이터에는 출력할 문자열을 인자로 넘기도록 되어 있다. 위 예시의 실행 결과는 다음과 같다.


각각의 함수에서 AddLine 데코레이터에 인자로 넘긴 문자열이 함수의 시작과 끝에서 정상적으로 출력되는 모습을 확인할 수 있다.


 데코레이터를 잘만 이용하면 Repeat 등의 이름을 가진 데코레이터를 만들어서 한 함수를 여러 번 반복시키는 것도 가능하다.

대표적인 문제가 codefights.com 의 Arcade - Python의 77번째 문제인 'Merging Vines' 이다.

상당히 재미있고 데코레이터를 익히는 데에 적절한 문제이므로 한번씩 풀어보는 것도 좋을 것 같다.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

댓글을 달아 주세요


Codefights.com에서 Arcade - Python 을 풀다가 상당히 재미있는 버그를 하나 알게 되어 작성한다.


1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
 
int division(int x, int y)
{
    return x / y;
}
int main()
{
    std::clog << division(15-4<< std::endl;
    return 0;
}
cs

 C에서 이렇게 int끼리의 나눗셈의 결과가 나머지가 존재할 경우, '/' 연산자 특성상 나머지를 버리고 몫만을 리턴하기 때문에 결과 값은 -3이 된다. 소숫점으로 나타내면 -3.xx~ 로 나타나겠지만 int 형으로 형변환하면서 -3이 되는 것이다. 

( C에서는 두 오퍼랜드 모두 Integer형일 경우 Integer를 반환하고, 하나라도 Float일 경우 Float형으로 소숫점을 붙여 반환한다. 이를 Classic Division이라고 한다. )


1
2
3
4
def division(x, y):
    return x / y
 
print division(15-4)
cs

 하지만 같은 역할을 할 법한 코드를 Python 2에서 작성하여 쓸 경우, 예상과는 달리 -4가 리턴된다. 

 이는 흔히 Floor Division 이라고 일컫는 정수 나눗셈 방법인데, 두 오퍼랜드가 모두 정수이고 나누어져서 나온 결과가 정수가 아니라면 항상 나온 값보다 작은 정수를 리턴하는 방식이다. 예를 들어, 5/3 의 경우 1.xx 로 1보다는 크고 2보다는 작은 값이 나오므로 1이 리턴된다. 따라서 위의 예시인 15/-4 를 할 경우 -3과 -4 사이의 값이므로 더 작은 값인 -4가 리턴되는 것이다. 이 방식이 문제가 되는 이유는, 흔히 참이라고 생각하는 연산이 거짓이 된다는 점에 있다.

 예를 들면, 정수 x와 y에 대해서, (x/-y) == -(x/y) 는 당연히 참이 되어야 하지만, 실제 위의 예시를 들어 보면 (15/-4) 는 -4가 되고 -(15/4) 는 -3이 되므로 거짓이 되어버린다. 

 이 문제는 Python 3 버전에서는 고쳐졌다고 한다. Python 3에서는 정수를 정수로 나누어도 Float을 반환해버리는 True Division을 사용하기 때문에 애초부터 저런 문제가 발생하지 않으며, Floor Division을 사용하고 싶을 때에는 15//-4 처럼 '//' 연산자를 사용하면 된다.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

댓글을 달아 주세요

 새해 맞은 이후 문제 풀이 능력도 기를 겸 짬짬이 시간내서 풀던 codefights.com의 Arcade - Intro 챕터를 전부 클리어했다. 이게 좀 종잡기 어려운 사이트인게, 분명히 Intro라고 씌여있는데 앞에서 어려운 문제가 나오고 뒤로 갈수록 쉬워지는 경향이 있다. 10번대 이전에 몇시간동안 머리아프게 한 문제가 나오는가 하면 50번대쯤에 단 한 줄로 풀려버리는 무지막지하게 간단한 문제가 나오기도 한다. 어지간하면 하루에 한 문제 이상은 꼭 풀려고 했고, 가끔은 퇴근 이후 카페에 가서 커피를 마시며 몇 문제를 풀고 돌아오기도 했다. 문제 하나하나에 걸리는 시간이 생각보다 그리 많지 않아서 꾸준히 풀다 보면 문제 풀이 능력 향상에 꽤 도움이 될 듯 하다.


 게다가 이 사이트에서는 프로필에 레벨이라는 것이 있는데, 문제를 풀 때마다 경험치가 쌓여서 레벨업을 할 수가 있다. 지금 나는 Trainee 2 레벨에 있다. 레벨 올리는 재미도 쏠쏠하게 있다. 아참, 페이스북 아이디나 구글 아이디로도 쉽게 연동하여 가입할 수 있어서 편하기도 하다.


 현재 인트로 난이도를 제외하면 4개의 챕터가 남았다. 대충 둘러본 바에 따르면 Databases는 쿼리문을 다루는 것에 대한 것 같다. 가장 기대되는건 역시 The Core와 Python이다. 뭔가 챕터 이름부터 The Core는 메모리 깊숙한 곳 또는 핵심 알고리즘을 다룰 것 같은 느낌이고, Python은 요즘 내가 가장 많이 사용하는 언어라 좀 더 고도화된 사용법을 배울 수 있지 않을까 싶어서이다. 앞으로도 계속 이렇게 한문제씩 풀어 나가면 이번 년도가 끝나기 전에 Arcade를 모두 클리어할 수 있지 않을까 하는 기대도 해본다 :)

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

댓글을 달아 주세요

CodeFights - alternatingSums

2018. 1. 15. 14:21

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

CodeFights - commonCharacterCount

2017. 12. 27. 23:12

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.