github 블로그로 이전 중입니다..



$jekyll new sitename

로 새로운 사이트를 만들 때 아래와 같은 에러가 발생했다.


Bundler: ruby: No such file or directory -- /usr/lib/ruby/gems/2.3.0/gems/bundler-1.16.1/exe/bundle (LoadError)


$gem env

로 환경설정을 보면 경로가 아래와 같이 나온다.

/var/lib/gems/2.3.0


해당 경로로 심볼릭 링크를 걸어주거나, jekyll 설정을 변경하면 해결된다.





 오픈소스 데이터 분석 및 모니터링 도구인 그라파나(Grafana)에서 그래프를 표시할 때 오른쪽 y 축(right y-axis)을 사용할 수 있다. 이 글에서 사용한 그라파나 버전은 4.6.3이다.

 InfluxDB 등에서 가져온 2가지 메트릭을 그래프로 표시하면 처음에는 아래와 같이 된다.






해당 그래프에서 Display탭의 Series overrides로 들어가 오버라이드할 항목의 alias와 Y축 관련 설정을 추가한다.





아래 그림과 같이 우측에도 y축이 생기는 것을 볼 수 있다. 축의 수치 범위는 Axes 탭에서 설정이 가능하다.







예전에 '온라인저지 구축기' 라는 글을 올린 적이 있었는데, HUSTOJ가 버전업이 계속 되었고, 설치방법에 대한 문의가 있어 다시 적어봅니다.


설치 OS : Ubuntu 16.04.2 LTS

HUSTOJ repo : https://github.com/zhblue/hustoj (2018년 2월 28일 버전)



1. git을 설치한다.

sudo apt-get install git 



2. git clone으로 저장소에서 파일을 복사해온다.

아래 명령어는 항상 최신 버전을 받아오기 때문에, 이전 버전이 필요하다면 github에서 이전 커밋을 찾아보아야 한다.

git clone https://github.com/zhblue/hustoj



3. 받은 파일의 trunk/install 경로에서 install-ubuntu16+.sh 파일을 실행한다.

여기서는 문제 발생을 위해 기본 설정을 따를 것이므로, mysql이나 nginx가 이미 설정되어 있는 환경에서는 충돌이 발생할 수도 있다. (위의 repo에서 Docker 이미지도 제공하고 있으므로, 도커 사용에 익숙하다면 해당 이미지를 이용하여 쉽게 설치할 수 있다.)

sudo bash ./install-ubuntu16+.sh



4. 설치 도중에 mysql을 설치하고, 비밀번호를 설정하는 창이 나온다. 비밀번호를 입력하여 설치를 계속한다.


5. 수 분을 기다리면 설치가 완료된다. 경우에 따라 경고 메시지가 뜰 수 있는데, 심각한 내용이 아니면 일단 넘어간다. judge 라는 리눅스 계정이 생성되고, /home/judge 라는 홈 디렉터리가 만들어진다. 이 경로에 들어가서 파일이 제대로 설치되있는지 확인한다.



6. ps -ef | grep judged 를 실행하여 judged 데몬이 제대로 실행되고 있는지 확인한다.



7. 브라우저를 열고 127.0.0.1 (원격 접속중이라면, 해당 서버의 IP) 으로 접속하여 페이지가 잘 나오는지 확인한다.



8. /home/judge/src/web/include/db_info.inc.php 을 수퍼유저 권한으로 열어서(sudo) 언어를 수정한다. 언어 설정과 관련되어 있는 'cn'(중국어)이 2개 있다 이것을 en으로 바꿔준다.

zh-CN 라고 되어있는 것은 en-US로 수정한다.



9. 이제 관리자 계정을 생성해야 하는데, 기본 관리자 계정이 없어서 아이디를 만들고 DB를 수정해야 한다. 상단의 Login 메뉴에서 회원가입을 한다.



10. mysql -u root -p로 mysql 클라이언트를 열고 4번에서 지정한 비밀번호를 입력한다.



11. use jol; 을 입력하여 데이터베이스를 선택한다.



12. INSERT INTO privilege(user_id, rightstr) VALUES('생성한 아이디', 'administrator'); 쿼리를 입력하여 유저권한을 지정한다.



13. exit를 쳐서 DB에서 빠져나온다.



14. 9번에서 만든 관리자 ID로 로그인한다. 문제와 테스트케이스를 만들고 저장한다. 추가적인 테스트케이스를 입력하라는 버튼이 나오는데, 이 페이지는 다음에 다시 들어갈 수 있다. 관리자 페이지와 문제 추가에 대한 설명은 생략한다.



참고) 테스트용 문제를 추가할 때, 입력 테스트케이스에 아무 숫자나 입력하고, 출력 테스트케이스에는 Hello 등의 간단한 문자열을 지정한다. 그러면 테스트용 코드를 작성할 때 간단하다. 테스트 케이스를 입력하지 않으면 채점이 되지 않을 수도 있다.



15. 관리자 페이지에서 빠져나와, 문제를 풀고 평가가 제대로 되는지 확인한다. Pending이 지속되지 않고, Accepted 혹은 Wrong Answer, Compile Error 등의 결과가 나오면 성공!


16. 관리 페이지의 문제 리스트에서 Reserved 되어 있는 문제의 status를 클릭하여 Available 상태로 만든다. 이 상태가 되면 관리자가 아닌 일반 유저도 문제를 볼 수 있게 된다.




※ Troubleshootings

- /home/judge/ 디렉터리 내에서 hustoj 관련 파일을 찾을 수 없을 때

install-ubuntu16+.sh 파일 실행 중에 실패한 것이다. 슈퍼유저 권한을 주지 않고 실행했거나, 기존에 설치되어 있던 mysql 또는 nginx 설정과 충돌했을 가능성이 있다. install 파일을 실행할 때 발생한 출력 메시지를 확인하여 에러를 수정한다.


- 웹 사이트가 뜨지 않을 때

에러 메시지에 따라 nginx나 mysql 설정을 확인한다. (구글링..)


- 코드를 제출했는데 pending 상태에서 무한히 대기할 때

ps -ef | grep judged 명령으로 judged 데몬이 실행중인지 확인한다. 실행중이라면 프로세스를 (kill -9 등으로) 죽이고 재실행해 본다. 실행중이 아니라면, 실행한다.(sudo service judged start)

문제가 해결되지 않으면 /home/judge/src/core/judged 경로로 들어가 해당 프로세스를 포그라운드로 실행하여 에러 메시지를 확인 후 조치한다. 파일 접근 권한 문제이거나 DB 접속 문제라면 관련 에러가 뜰 것이다.

만약 아무 에러도 뜨지 않는다면, 관리 페이지에서 테스트케이스 데이터를 확인해 본다.


- 입력 테스트케이스의 문자열 길이가 잘못 측정될 때

테스트케이스 추가페이지에서는 줄바꿈에 \r\n을 사용한다. 이 부분은 관리페이지의 내부 php 코드를 수정하여 캐리지리턴(\r)을 빼고 라인피드(\n)만 저장되도록 수정해야 한다. (이런 잔문제들이 많다..)




HUSTOJ를 사용하여 https://judge.zetagate.com을 운영하고 있었으나, 여러가지 문제가 많고, 확장성이 없다고 판단하여 새로 제작 중이다. https://github.com/ice-judge 아직 Github 저장소만 만들어 놓은 시작 단계다...





티스토리에서 연초마다 블로그결산 기능을 제공하여 이번에도 한번 돌려 보았습니다.

올해는 블로그를 좀더 해봐야겠습니다. 근데 github으로 옮길까 하는 생각도...




일출을 보기 위해 아침 6시경 남한산성으로 향했다.

성남시 쪽에서 올라가는 도로가 몰려든 인파로 매우 붐볐기 때문에 늦지 않을까 걱정하였지만, 다행히 해 뜨기 전 7시 26분에 남문 앞에 도착할 수 있었다.


원래 계획은 수어장대(守禦將臺)까지 올라가려고 했으나, 시간이 늦어지는 관계로 남문과 수어장대 사이에 있는 영춘정(迎春亭)에서 해가 뜨는 것을 보게 되었다.



성남시 방향을 내려다 보며..





7시 49분이 되자 해가 보이기 시작했다.









핸드폰 카메라가 좋지 않아서(혹은 찍는 실력이 없어서) 해가 완전히 뜬 이후의 사진은 제대로 찍히지 않았다.





수어장대에서 서문(우익문) 방향으로 내려는 길.





송파구 방향을 내려다보며.. 멀리 롯데월드타워가 보인다. 사진에는 잘 보이지 않지만 날씨가 맑아서 북한산까지 볼 수 있었다.





서문(우익문)에 도착. 이후 왔던 길을 되돌아왔다.





날씨가 추워서인지 폰 배터리가 빠르게 방전되는 바람에 사진을 많이 찍지 못한 것이 아쉬웠다.







 기수정렬(Least Significant Digit 방식) 관련 글들을 찾아보면 개념을 쉽게 설명하기 위해 십진수 단위를(base 10) 사용한 것들이 많은데 실제로 이것을 구현하면 생각보다 느린 것을 볼 수 있다. 나눗셈 연산과 나머지연산이 수행되기 때문이다.  예를 들어 100의 자리에서 기수정렬할 단계를 보면, (현 상태에서 m은 100이라고 가정한다.)


arr[i]/m%10    


위와 같이 / 연산과 % 연산이 사용된다.



 이러한 문제를 개선하기 위해 버킷의 개수를 2의 거듭제곱으로 하면 속도가 빠른 비트연산을 사용할 수 있다. 버킷의 개수를 8로 하면 위 연산식이 아래와 같이 된다. (현 상태에서 m은 64라고 가정)


(arr[i]>>m)&7


2의 거듭제곱으로 나누었을 때의 나머지는, 굳이 나누어서 계산해볼 필요 없이 해당 위치의 비트만 AND연산으로 추출하면 되는 것이다.




구현


 아래 코드는 0부터 99,999,999까지의 정수 데이터들을 정렬할 수 있는 기수정렬(base 8) 알고리즘을 코드로 구현한 것이다. 

     for(int i=0; i<9; ++i) {


           idx = 0;

for(int j=0; j<8; ++j) { counts[j] = 0; } for(int j=0; j<n; ++j) { digit = (arr[j]>>i*3)&7; bucket[digit][counts[digit]++] = arr[j]; } for(int j=0; j<8; ++j) { for(int k=0; k<counts[j]; ++k) { arr[idx++] = bucket[j][k]; } } }


비트시프트 연산을 보면, i는 데이터를 8진수로 나타냈을 때 특정 자리의 숫자를 얻을 때 사용된다. i를 8이 될때까지만 반복시키는 이유는  으로, 99,999,999를 8진수로 나타낼 수 있는 가장 큰 자릿수가 8이기 때문이다.




수행시간 분석


 base 8 기수정렬에서 전체 데이터의 양을 N개라고 했을 때 버킷의 크기는 bucket[8][N]이다. base 8 대신 base 4나 base 2를 사용하면 메모리 사용량은 각각 2배, 4배 줄어들게 된다. 반면, 기수정렬의 시간복잡도가 O(dN)이므로, 수행시간은 더 늘어난다. (d는 해당 base에서 데이터의 최대 자릿수이다.) 표 1은 내림차순으로 정렬된 100만개 ~ 500만개 데이터를 오름차순으로 정렬했을 때 각 알고리즘의 수행 시간(ms)이다. 표 2는 랜덤함수로 생성한 무작위 데이터를 오름차순으로 정렬했을때의 결과이다.


표 1


표 2




 표 1의 결과를 그래프로 나타내 보면 다음과 같다.




 base 8 대신 16이나 32를 사용하면 메모리 사용량이 늘어나고, 속도가 약간 줄어들겠지만, 값 복사시간이 기본적으로 존재하기 때문에 속도가 줄어드는 데에는 한계가 있다. 수행시간이 카운팅 정렬을 사용했을때 이하로 떨어지지는 않을 것이다.


 Dovelet이나 BOJ 같은 사이트들을 풀어보면서 직접 문제들을 만들어 보고 싶었고, 알고리즘을 공부하는 사람들과 소규모 대회를 열어보기 위해 온라인저지(Online Judge)시스템을 구축하게 되었다.




DMOJ 설치


 DMOJ는 Django 기반으로 되어 있는 온라인저지 시스템으로, github 커밋이 꾸준히 올라오고 있고 현대적인 사이트 디자인이 특징이다. 설치 가이드에 따라 설치를 진행하였는데 마지막 단계에서 nginx 권한 관련 오류를 해결하지 못하여 진행하지 못하였다. 도커이미지도 제공되고 있어서 컨테이너를 실행해 보았더니 이번에는 Django 내부 코드에서 오류가 발생하여 포기.




HUSTOJ 설치



* 2018년 2월에 갱신되었습니다. 아래 글 보다는 이 글을 참고하세요.


 일단 구동이 되는 것을 찾다가 HUSTOJ를 설치해보기로 하였다. HUSTOJ는 교내 프로그래밍 대회에 사용되고 있고, CodeUp 및 여러 온라인저지에서 사용하고 있는 오픈소스 프로젝트이다. 채점기는 C++로 짜여져 있으며 웹페이지는 PHP로 구동된다. 설치할 때 공식 페이지 블로그 포스팅을 참고하여 진행하였다. 설치 과정은 아래와 같다.


1) install/install.sh에서 WEBBASE, APACHEUSER, DBUSER, DBPASS, core 디렉터리 경로, web 디렉터리 경로를 환경에 맞게 설정한다. subversion 관련 코드들은 삭제한다. 


2) install/judge.conf를 확인한다. OJ_HOST_NAME, OJ_USER_NAME, OJ_PASSWORD와 MySQL관련 설정들을 환경에 맞게 수정하면 된다.


3) web/include/db_info.inc.php 파일에서 원하는 설정을 한다. 이 부분은 설치 후에도 변경할 수 있다.


4) install.sh를 실행하여 설치를 진행한다. 이 프로젝트는 Ubuntu 14에서 테스트되었고, Ubuntu 16을 비추천한다고 하는데, 16에서도 잘 구동된다. php5 대신 php7을, apache대신 nginx를 이용하는 것은 install 디렉터리에 함께 있는 ubuntu16용 설치 sh파일을 참고한다. sh파일을 실행하면,

/etc/nginx/site-enabled에서 root은 /home/judge/src/web이 되고, php관련 location설정은

       location ~ \.php$ {

                include snippets/fastcgi-php.conf;

        #

        #       # With php7.0-cgi alone:

        #       fastcgi_pass 127.0.0.1:9000;

                fastcgi_pass unix:/run/php/php7.0-fpm.sock;

        }

와 같이 작성된다.




사이트 수정


 레이아웃을 비롯하여 회원가입, 관리자페이지, 채점기 등 많은 부분을 수정하였다. judge 기본 디렉터리에서 data에는 문제의 테스트케이스가 있고, src/core에는 채점 프로그램 관련 소스와 makefile이 존재한다.

 웹페이지는 src/web 디렉터리에 있는데, src/web에는 SQL에서 데이터를 받아오는 부분이 있고,  src/web/template/bs3에는 주로 뷰 관련 코드(html)가 들어 있다. 그러나 잘 분리되어 있지 않아 수정이 번거롭다.

 PHP와 SQL, html이 섞여 있어 구조적으로 알아보기 불편하고, SQL튜닝이 잘 되어있지 않아 데이터가 많아지면 상당한 오버헤드가 예상된다. 일단 이 프로젝트를 사용하여 회원탈퇴, 소스공유, 랭킹시스템 등 필수적인 기능을 구현할 것이다. 그리고 저지사이트 구축을 공부해볼겸 하여 기존의 코드를 버리고 프레임워크를 새로 만들어 보기로 계획하였다.


현재 진행중인 사이트는 여기다.






2차원 평면상에서 선분 AB와 CD의 정보가 주어질 때, 두 선분의 교차 여부를 빠르게 판단할 수 있는 방법이 있다. 벡터의 외적(벡터곱)을 이용하는 방법이다.


그림 1과 같이 두 선분이 교차하는 경우를 생각해 보자. 벡터 BA를 기준으로 점 C와 점 D가 서로 다른 회전방향에 있으므로, 아래 두 경우는 벡터곱 연산의 결과로 나온 z값의 부호가 다르다.

- 벡터 BA와 벡터 BC를 외적한 결과

- 벡터 BA와 벡터 BD를 외적한 결과


벡터 DC에 대해서도 역시 부호가 다르게 된다.

- 벡터 DC와 벡터 DA를 외적한 결과

- 벡터 DC와 벡터 DB를 외적한 결과 


     

그림 1. 교차하는 두 선분                                  


즉, 벡터곱 연산을 이용하여 한 선분의 벡터를 기준으로, 다른 선분의 두 점들이 같은 회전뱡향에 있는지, 다른 회전방향에 있는지 검사하면 교차 여부를 판단할 수 있는 것이다.
두 선분에 대해 이러한 판단을 수행하여 모두 만족하면 교차하는 것으로 판정한다.


그림 2에서는 벡터 CD에 대해 다른 선분의 두 점(A, B)는 서로 다른 회전방향이지만, 벡터 AB에 대해서는 두 점(C ,D)가 같은 회전방향에 있기 때문에 교차하지 않는 것으로 판정한다.


 

그림 2. 교차하지 않는 두 선분



Javascript로 표현하면 아래와 같이 쓸 수 있다.

    let p1 = line1.p1;

    let p2 = line1.p2;

    let p3 = line2.p1;

    let p4 = line2.p2;


    let sign1 = (p2.x-p1.x)*(p3.y-p1.y) - (p3.x-p1.x)*(p2.y-p1.y);

    let sign2 = (p2.x-p1.x)*(p4.y-p1.y) - (p4.x-p1.x)*(p2.y-p1.y);


    let sign3 = (p4.x-p3.x)*(p1.y-p3.y) - (p1.x-p3.x)*(p4.y-p3.y);

    let sign4 = (p4.x-p3.x)*(p2.y-p3.y) - (p2.x-p3.x)*(p4.y-p3.y);


    return sign1*sign2<0 && sign3*sign4<0;




그러나 위 코드에는 문제점이 있는데, 그림 3과 같이 두 선분의 기울기가 같을 때는 교차하지 않는 것으로 판단한다는 것이다. (이런 결과를 의도했다면 문제가 되지 않겠지만..)


그림 3. 두 선분의 기울기가 같고, 교차점이 무수히 많은 경우



그림 3과 같은 경우에도 두 선분이 교차하는 것으로 판정하기 위해, 비교문을 아래와 같이 고치면 또다른 문제가 발생한다. 그림 4와 같이 기울기가 같고 서로 떨어져 있을 때 잘못 판정한다는 것이다.

return sign1*sign2<=0 && sign3*sign4<=0;



그림 4. 교차 여부를 잘못 판정한 경우




두 선분이 완전히 떨어져 있을 경우(더 정확히 말하면, 각각의 선분이 이루는 직사각형 영역이 서로 겹치지 않는 경우)에 false를 리턴하면 이 문제를 해결할 수 있다. 외적 연산에 앞서 사전 영역 검사를 하는 것이다.

아래 코드에서 모든 부호가 0일 때 true를 반환하는 부분을 추가한 이유는, 그림 3과 같이 교차점이 무수히 많은 경우도 교차하는 것으로 판단하기 위해서이다.


    let p1 = line1.p1;

    let p2 = line1.p2;

    let p3 = line2.p1;

    let p4 = line2.p2;


    if(Math.max(p1.x, p2.x) < Math.min(p3.x, p4.x)) return false;

    if(Math.min(p1.x, p2.x) > Math.max(p3.x, p4.x)) return false;

    if(Math.max(p1.y, p2.y) < Math.min(p3.y, p4.y)) return false;

    if(Math.min(p1.y, p2.y) > Math.max(p3.y, p4.y)) return false;


    let sign1 = (p2.x-p1.x)*(p3.y-p1.y) - (p3.x-p1.x)*(p2.y-p1.y);

    let sign2 = (p2.x-p1.x)*(p4.y-p1.y) - (p4.x-p1.x)*(p2.y-p1.y);


    let sign3 = (p4.x-p3.x)*(p1.y-p3.y) - (p1.x-p3.x)*(p4.y-p3.y);

    let sign4 = (p4.x-p3.x)*(p2.y-p3.y) - (p2.x-p3.x)*(p4.y-p3.y);


    if(sign1==0 && sign2==0 && sign3==0 && sign4==0) return true;


    return sign1*sign2<=0 && sign3*sign4<=0;



그림 5. 교차 여부를 정상적으로 판정한 경우




경우에 따라서 그림 6과 같이 선분의 끝점이 교차할 때는 교차하지 않는 것으로 판단하는 것이 필요할 때가 있다.

 

그림 6, 7. 두 선분이 교차하는 것으로 판단하는 경우



아래 코드에서와 같이 사전 영역 검사에 동등 조건을 넣어  좀더 엄격하게 바꾸고(그림 6과 관련), 마지막의 리턴 코드에서 동등 조건을 제거한다.(그림 7과 관련)


    let p1 = line1.p1;

    let p2 = line1.p2;

    let p3 = line2.p1;

    let p4 = line2.p2;


    if(Math.max(p1.x, p2.x) <= Math.min(p3.x, p4.x)) return false;

    if(Math.min(p1.x, p2.x) >= Math.max(p3.x, p4.x)) return false;

    if(Math.max(p1.y, p2.y) <= Math.min(p3.y, p4.y)) return false;

    if(Math.min(p1.y, p2.y) >= Math.max(p3.y, p4.y)) return false;


    let sign1 = (p2.x-p1.x)*(p3.y-p1.y) - (p3.x-p1.x)*(p2.y-p1.y);

    let sign2 = (p2.x-p1.x)*(p4.y-p1.y) - (p4.x-p1.x)*(p2.y-p1.y);


    let sign3 = (p4.x-p3.x)*(p1.y-p3.y) - (p1.x-p3.x)*(p4.y-p3.y);

    let sign4 = (p4.x-p3.x)*(p2.y-p3.y) - (p2.x-p3.x)*(p4.y-p3.y);


    if(sign1==0 && sign2==0 && sign3==0 && sign4==0) return true;


    return sign1*sign2<0 && sign3*sign4<0; 





결론


만들고자 하는 프로그램에 따라 선분 끝점을 어떻게 판단해야할지가 달라진다. 위 코드를 활용하여 비교문들을 조정하면 선분의 교차 판단을 목적에 맞게 할 수 있을 것이다.



 

소스코드


동작하는 전체 코드는(Javascript)는 아래 github에서 다운로드할 수 있습니다.

https://github.com/tibyte/algorithms/tree/master/line-intersection








 깃허브 페이지(github page)는 tibyte.github.io와 같은 github.io 주소로 접속하였을 때 HTTPS를 지원하지만 커스텀 도메인을 설정하였을 경우 그림 1과 같이 지원되지 않는 것을 볼 수 있다.

그림 1. 커스텀 도메인 설정한 깃허브 페이지를 접속하였을 때의 주소창



 커스텀 도메인을 연결한 깃허브 페이지에 SSL(Secure Sockets Layer)을 설정하기 위해서는 먼저 SSL 인증서를 발급받아 설치해야 하는데, Cloudflare의 네임서버를 사용하면 이 과정을 간단하게 할 수 있다.

 Cloudflare의 Add Website 페이지에서 도메인을 등록하면 DNS 레코드들을 자동으로 불러온다. 목록을 확인한다.


그림 2. DNS 레코드 설정화면



 DNS 레코드를 확인 후 설정을 저장하고, 소유하고 있는 도메인의 관리 페이지(도메인 등록 대행업체)로 들어가서 네임서버를 Cloudflare에서 안내한 주소로 연결한다.


그림 3. 네임서버 설정



 도메인 네임서버 주소 변경은 전파되는 데 수 분에서 수 일까지 걸릴 수 있다. 정상적으로 전파되었다면, Cloudflare의 Crypto 메뉴에서 SSL 사용여부를 선택할 수 있다. 여기서 옵션이 있는데 github 페이지는 HTTPS를 지원하므로 권장방식인 Full SSL을 선택한다.

그림 4. SSL 설정


 설정을 완료하고, https:// 를 명시한 커스텀 도메인을 사용하여 github 페이지를 접속해 보면 HTTPS가 적용된 것을 볼 수 있다. CA는 COMODO사의 것으로 나온다.

       

그림 5. 커스텀 도메인 설정한 깃허브 페이지를 접속하였을 때의 주소창


 

  주소창에 https://를 입력하지 않고 접속하였을 경우엔 이전과 같이 HTTPS가 미적용된 페이지가 나오는데, 코드상에서 리다이렉팅을 시키면 되지만 Page Rule을 적용하여도 이 효과를 볼 수 있다.

그림 6. HTTPS 강제 리다이렉트 설정





+ Recent posts