문자열을 분리하고 변형해야 하는 코딩 테스트 문제에서 공백 문자가 연속으로 나오는 경우가 많이 있습니다. 공백을 기준으로 문자열을 분리할 때 자연스럽게 split() 함수를 사용하게 되는데, 이 경우 연속 공백을 상실하게 되는 문제점이 발생합니다.

 

연속 공백을 유지하면서 문자열을 분리하여 리스트로 만들기 위해 정규식(regular expression)을 사용할 수 있습니다.

 

 

https://school.programmers.co.kr/learn/courses/30/lessons/12951#

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

프로그래머스 JadenCase 문자열 만들기 문제를 가지고 방법을 설명해 보겠습니다. (다른 다양한 문제에서도 활용할 수 있습니다!)


 

문제 : JadenCase란 모든 단어의 첫 문자가 대문자이고, 그 외의 알파벳은 소문자인 문자열입니다. 단, 첫 문자가 알파벳이 아닐 때에는 이어지는 알파벳은 소문자로 쓰면 됩니다. 문자열 s가 주어졌을 때, s를 JadenCase로 바꾼 문자열을 리턴하는 함수, solution을 완성해주세요.

 

이렇게 공백문자가 연속해서 나올 수 있다는 조건이 있는 경우 split() 함수를 사용하면 공백 여러개가 모두 없어져버리므로 조심해야 합니다.

 

 

저는 먼저 단어 1개를 JadenCase로 바꾸어 리턴하는 함수 jaden을 작성했습니다.

def jaden(w):
    try:
    	# 첫 번째 글자가 정수일 때
        int(w[0]) 
        return w[0] + w[1:].lower() 
    except:
    	# 첫 번째 글자가 정수가 아닐 때
        return w[0].upper() + w[1:].lower()

 

이렇게 작성한 jaden 함수를 가지고 최종 solution 함수를 작성해 볼게요.

 

import re

 

먼저 정규식 패키지를 불러옵니다.

 

word = "Hello       World   Bye"

 

위의 예시를 단어와 공백문자로 구분한 리스트를 어떻게 만들 수 있을까요?

 

word = "Hello       World   Bye"
re.findall("\S+|\s+",word)
# ['Hello', '       ', 'World', '   ', 'Bye']
  • re.findall() : RE가 일치하는 모든 부분 문자열을 찾아 리스트로 반환합니다. 활용해서 원하는 구성요소를 찾아 리스트로 만들어줍니다.
  • \s+ : 일치하는 모든 공백 문자를 찾아줍니다. (여러 개의 공백도 하나로 평탄화하지 않고 유지한 채로 찾아줍니다.)
  • \S+ : 일치하는 모든 비공백 문자를 찾아줍니다.

결과를 보면 여러 개의 공백 문자가 잘 보존되어 있는 것을 볼 수 있습니다. 이제 최종 솔루션 함수를 작성해 볼게요.

 

def jaden(w):
    try:
    	# 첫 번째 글자가 정수일 때
        int(w[0]) 
        return w[0] + w[1:].lower() 
    except:
    	# 첫 번째 글자가 정수가 아닐 때
        return w[0].upper() + w[1:].lower()

import re

def solution(s):
	# 입력 받은 s를 whitespace와 nonwhitespace로 구분합니다
    words = re.findall("\S+|\s+", s)
    answer = ""
    for word in words:
    	# 만약 공백문자가 아니라면 jaden 함수를 적용해서 answer에 추가합니다.
        if word.isalnum(): 
        	answer += jaden(word)
        # 만약 공백문자라면 그냥 바로 answer에 추가합니다.
        else: 
        	answer += word
    return answer

 

 
정확성: 100.0
합계: 100.0 / 100.0
 

 

끝입니다! 정규식이 기억에 잘 남지는 않지만,
잘만 숙지해 두면 연속 공백문자 처리를 해야하는 문제에서 요긴하게 잘 쓸 수 있답니다 :-) 
읽어주셔서 감사합니다!

 

프로그래머스에 비트 연산을 해야하는 MySQL문제가 종종 보이는데요. 정리해놓으면 좋을 것 같아서 문제 풀이를 한번 작성해 보겠습니다.

 


https://school.programmers.co.kr/learn/courses/30/lessons/301646

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

문제 : 2번 형질을 보유하지 않으면서 1번이나 3번 형질을 보유하고 있는 대장균 개체의 수(COUNT)를 출력하는 SQL 문을 작성해주세요. 1번과 3번 형질을 모두 보유하고 있는 경우도 1번이나 3번 형질을 보유하고 있는 경우에 포함합니다.


[1] 2진법으로 계산해 보기

먼저 각 열의 GENOTYPE의 수를 2진법으로 바꾸어 출력해 보겠습니다. 주어진 정수 데이터에 BIN() 함수를 사용하면 정수를 2진법 수로 변환한 결과를 출력합니다.

SELECT BIN(GENOTYPE) AS GENOTYPE_BIN
FROM ECOLI_DATA

 

그럼 이제 출력된 결과를 CAST함수를 이용해서 CHAR 데이터타입으로 바꾼 뒤, WHERE문을 사용해서 필터링을 해 보겠습니다.

SELECT COUNT(*) AS `COUNT` 
FROM (SELECT ID,
             CAST(BIN(GENOTYPE) AS CHAR) AS GENOTYPE_BIN
             FROM ECOLI_DATA) A
WHERE A.GENOTYPE_BIN LIKE '1' OR
      A.GENOTYPE_BIN LIKE '%10_' OR
      A.GENOTYPE_BIN LIKE '%01'

 

WHERE문의 각 줄은 다음과 같은 결과를 필터링합니다.

  1. 서브쿼리문의 수가 1인 경우
  2. 서브쿼리가 101 또는 100인 경우
  3. 서브쿼리가 01로 끝나는 경우

마지막으로 SELECT COUNT(*) AS `COUNT`를 통해 필터링된 데이터 열의 갯수를 COUNT라는 컬럼명으로 출력하도록 해 주었습니다. 정답 통과입니다.

 

 


[2] 비트 연산으로 풀어보기

MySQL에는 비트(Bit) 단위로 논리 연산을 수행하는 연산자가 있습니다. 챗지피티한테 비트 연산자의 종류에 뭐가 있는지 테이블을 만들어 달라고 했는데요.

비트 AND (&) 비트별 AND 연산을 수행합니다. 두 비트가 모두 1이면 결과는 1이 되고, 그렇지 않으면 결과는 0이 됩니다.
비트 OR ( | ) 비트별 OR 연산을 수행합니다. 두 비트 중 하나라도 1이면 결과는 1이 되고, 둘 다 0이면 결과는 0이 됩니다.
비트 XOR (^) 비트별 XOR 연산을 수행합니다. 두 비트가 같으면 결과는 0이 되고, 다르면 결과는 1이 됩니다.
비트 NOT (~) 비트를 반전시킵니다. 0은 1로, 1은 0으로 변환됩니다.
비트 왼쪽 시프트 (<<) 모든 비트를 왼쪽으로 이동시킵니다. 오른쪽에 0으로 채워집니다.
비트 오른쪽 시프트 (>>) 모든 비트를 오른쪽으로 이동시킵니다. 왼쪽에 부호 비트와 같은 값으로 채워집니다.

 

네. 이런게 있다네요. (^_^;;) 저는 여기서 &연산자와 NOT 연산자를 사용해서 위의 문제를 풀어보겠습니다.

 

SELECT * FROM ECOLI_DATA
WHERE
    GENOTYPE & 5
    AND NOT GENOTYPE & 2

 

GENOTYPE & 5

  • 정수 5를 2진법 비트로 변환하면 101입니다. 따라서 &5는 값의 첫 번째, 세 번째 비트가 1인지 여부를 확인합니다.

NOT GENOTYPE & 2

  • 비트 NOT 연산은 비트를 반전시키는 역할을 합니다. 여기서 2의 이진 표현은 10입니다. 따라서 이 비트 연산은 GENOTYPE 열의 값에서 2번째 비트를 확인하고, 그 값을 반전시키므로, 2번째 비트가 0인지 여부를 확인합니다.
SELECT COUNT(ID) AS `COUNT`
FROM ECOLI_DATA
WHERE
    GENOTYPE & 5
    AND NOT GENOTYPE & 2

 

마지막으로 카운트한 값을 출력해 주면 정답 통과입니다.


 

비트 연산자는 간단하게 풀 수 있지만 까먹기 쉽다는 단점이 있는 것 같습니다.

그래도 이런 게 있다는 걸 알아두고 필요할 때마다 열심히 꺼내 보면서 익숙해 져야겠습니다.

 

 

 

 

MySQL에서 JOIN을 이용하여 두 테이블 간의 정보를 조회할 때, 차집합(Set Difference)을 구해야 할 경우가 있습니다. 이럴 때 LEFT JOIN,RIGHT JOIN에 WHERE구문을 추가 활용하여 쉽게 표현해볼 수 있습니다.

 

 

https://school.programmers.co.kr/learn/courses/30/lessons/59044

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

관련 문제로 프로그래머스 MySQL 코딩테스트 3단계 문제 오랜 기간 보호한 동물(1) 풀이를 함께 첨부해 보겠습니다.

 

 


 

 

문제 상황 : 아직 입양을 못 간 동물 중, 가장 오래 보호소에 있었던 동물 3마리의 이름과 보호 시작일을 조회하는 SQL문을 작성해주세요. 이때 결과는 보호 시작일 순으로 조회해야 합니다.

 

 

SQL문을 실행하면 다음과 같이 나와야 합니다.


 

먼저 ANIMAL_INS 테이블에 ANIMAL_OUTS 테이블을 LEFT JOIN한 결과를 보겠습니다.

SELECT I.NAME, I.DATETIME
FROM ANIMAL_INS I
	LEFT JOIN ANIMAL_OUTS O
	ON I.ANIMAL_ID = O.ANIMAL_ID

 

아직 아무 조건을 걸어주지 않은 관계로 위 코드가 출력하는 결과는

SELECT NAME, DATETIME

FROM ANIMAL_INS와 동일합니다.

 

 

저는 여기서 A-B를 구하기 위해

A와 B의 교집합을 제거해 주려고 합니다.

어떻게 해볼 수 있을까요?

SELECT I.NAME, I.DATETIME
FROM ANIMAL_INS I
	LEFT JOIN ANIMAL_OUTS O
	ON I.ANIMAL_ID = O.ANIMAL_ID
WHERE O.ANIMAL_ID IS NULL

 

OUT 테이블의 아이디가 없는 데이터만 출력하도록 WHERE O.ANIMAL_ID IS NULL을 추가해 주었습니다.

 

이제 마지막으로 이 결과에서 가장 오래 보호소에 있었던 동물 3마리의 이름을 고르고, 결과는 보호 시작일 순으로 조회하도록 조건을 추가하겠습니다.

SELECT I.NAME, I.DATETIME
FROM ANIMAL_INS I
	LEFT JOIN ANIMAL_OUTS O
	ON I.ANIMAL_ID = O.ANIMAL_ID
WHERE O.ANIMAL_ID IS NULL
ORDER BY I.DATETIME ASC LIMIT 3;

 

정답 통과 입니다.

 

 

참고한 다이어그램

출처: 구글 이미지

 


 

비슷한 문제로 프로그래머스 3단계 '없어진 기록 찾기' 문제도 풀어보시면 좋을 것 같습니다 :-)

 

 

https://school.programmers.co.kr/learn/courses/30/lessons/59042

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

ASAC 빅데이터 분석 & AI 5기 과정 3주 차가 지났습니다.

 

 

 

집에서 편하게 쉬엄쉬엄 공부를 하다가 매일 강의실에 물리적으로 출퇴근을 하는 일이 쉬운 일은 아니었습니다만, 이쯤 되니 다행히도 몸이 잘 적응을 한 것 같습니다. 아무래도 지난 7년간 학교에 출퇴근하던 일에 비교하면 (비교할 수 없을 만큼) 훨씬 살만하네요. 학교에서 학생을 대하고 가르치는 일은 체력적, 정신적으로 굉장히 큰 에너지를 소모하는데요. 그에 비하면 지금은 그냥 방학 같아요. 내가 하고 싶은 공부를 원 없이 할 수 있는 환경과 자원이 지원된다는 게 감사할 따름입니다.

 

과정을 시작하면서 제 자신과 약속한 것이 한 가지 있어요. 바로 출퇴근하는 월요일부터 금요일까지는 반드시 아침에 일찍 일어나자는 것이었는데요. 저는 저에 대해서 꽤 잘 알거든요. 내가 원하는 시각에 망설임 없이 일어나서 하루를 시작할 수 있느냐 없느냐가 저의 그날 하루 전체 생산성을 결정합니다. 지금까지 쉬엄쉬엄 편하게 공부해 왔으니, ASAC 과정에 참여하는 6개월 동안만큼은 죽었다 생각하고 일찍 일어나기로 했어요. 그래서 저는 매일 아침 5시 40분에 기상합니다. 간단히 씻고 준비를 마친 다음 6시 15분-20분에 집을 나서 6시 28분 지하철을 탑니다. 다행히도 3주간 잘 지켜왔습니다. 앞으로도 잘 지킬 거예요.

 

집이 꽤 먼 편이라 목적지까지 환승 없이 약 45분 정도가 걸리는데요. 출퇴근 길에는 책을 읽습니다. 책 읽는 걸 너무 좋아합니다. 더 나아가 책 읽는 제 자신이 좋습니다. 책을 읽을수록 생각이 확장되고 더 나은 사람이 되는 것을 느껴서 꾸준히 읽고 기록하려고 노력하고 있어요. 데이터를 분석하고 통찰하고 관철할 수 있는 사람이 되는 데 독서도 분명 큰 도움이 될 것이라고 생각합니다. 그런데 요즘 같은 시기에 따로 시간을 내어 책을 읽기는 또 어렵다 보니, 대중교통에서 보내는 시간을 활용하는 거예요. 저도 책을 멀리하던 시절에는 지하철이나 버스에서 책 꺼내서 읽는 사람 보면 괴물 같고 그랬는데 말이죠. 막상 직접 해 보니 오히려 숏폼 영상보다도 더 즐겁고 시간도 더 잘 갑니다. 

 

강의실에 도착하고 나면 7시 20-30분 사이가 되는데 그때부터 8시 50분까지 약 한 시간 조금 넘는 시간 동안 개인 공부를 합니다. 잠깐 쉬고 9시 10분부터 수업을 듣습니다. 6시에 수업이 끝나면 저녁을 먹고 8시까지 개인 공부를 합니다. 가끔 컨디션이 안 좋으면 7시 30분쯤 일찍 가고, 컨디션이 유독 좋으면 9시까지 공부를 합니다. 이렇게 말하고 보니 정말 공부에 미친 광인 같네요. 그래도 저도 사람이기 때문에 주말엔 늦잠도 좀 자고 맛있는 것도 먹고 맥주 한 잔 하기도 하면서 편히 지내고 있어요. 요즘 주말 공부 시간은 하루에 3-6시간 사이 정도인데 조금씩 늘려가려고 합니다.

 

 

 

수업에서 3주 차 초반까지는 파이썬 기본부터 심화 문법까지 리뷰를 하고 주요 알고리즘을 몇 가지 공부했습니다. 특히 저는 알고리즘 자체를 따로 배워 본 적이 따로 없었기 때문에 구현과 탐색 알고리즘을 배웠던 시간이 큰 도움이 되었어요. 강사님께서 여러 가지 실전 코딩테스트 문제들을 가지고 하나하나 설명을 해주시면서 알고리즘을 코테 문제에 어떻게 적용하는지 보여 주신 것이 참 좋았습니다. 덕분에 저도 자극을 받아서 블로그에 코딩 테스트 문제 풀이 방법을 작성해서 올리기 시작했는데요. 무언가 배웠을 때 그걸 남에게 설명할 수 있어야 진짜로 아는 것이라고 학생들한테 누누이 말하곤 했던 걸 이제는 스스로에게 되새고 있는 저를 보면서, 기분이 묘했습니다.

 

파이썬 과정이 끝나고 간단한 테스트가 있었습니다. 다행히 쉽게 느껴지는 문제들로 구성되어 있어 10분-15분 만에 빠르게 제출을 할 수 있었습니다. 다른 동기들에 비해 문제를 빠르게 해결한 편이었는데, 제가 특별히 잘나고 똑똑해서는 아닙니다. 블로그에 직접 정리하면서 반복하고 복습했던 스니펫들을 활용할 수 있는 문제가 정말 많이 나왔어요. 운이 좋았던 거죠. 공부한 것을 남에게 설명하기를 저는 블로그에 글을 쓰는 걸로 대신하고 있는데, 이게 정말 큰 의미가 있다는 걸 실감할 수 있었던 좋은 기회였습니다. 

 

파이썬 과정이 끝나고는 짧고 콤팩트하게 MySQL을 배웠는데요. 저는 MySQL도 유데미 강의를 통해 한 번 훑고 왔기 때문에 강의 속도를 따라가는 데 큰 어려움이 없었습니다. 프로그래머스에서 MySQL 코딩테스트 문제도 제공하고 있거든요. 그걸 같이 풀면서 수업을 들으니 체화하는 데 훨씬 더 좋더라고요. MySQL이 무슨 대단한 알고리즘을 요구하는 언어는 아니기 때문에 엄청 어렵고 복잡하진 않더라도 이게 손 놓고 있으면 잊어버리기 십상이거든요. 꾸준히 놓지 않는 게 가장 중요하다고 생각되어서, 앞으로 프로그래머스 문제를 조금씩 야금야금 꾸준히 풀면서 리듬감을 유지해보려고 합니다.

 

 

 

만약 저의 회고를 참고하시고 다음 기수의 ASAC과정을 고려하고 계신 분이 계시다면, 반드시 파이썬과 MySQL의 기초 문법에 대한 (최소) 1회독을 끝낸 다음 지원을 하시라고 말씀드리고 싶습니다. 그게 정신건강에 좋습니다. 교육과정 자체가 노베이스 비기너를 위한 과정이 아닙니다. 굉장히 타이트한 교육과정이에요. 진도가 빠를 뿐만 아니라 난이도도 있습니다. 저는 비록 비전공자이기는 하지만 전공자에 뒤쳐지고 싶지 않아서 여러 가지 공부를 마친 다음 본 과정을 시작했기 때문에 아직은 수업을 따라가는 데 어려움이 없습니다. KDT 등록하면 알아서 어떻게든 되겠지- 와 같은 해이한 마음으로 시작하시면 크게 낭패를 보실 수 있다는 점을 반드시 숙지하시면 좋겠습니다.

 

그럼 4주 차도 맑은 눈의 광인 모드를 탑재하고 열심히 공부하고 돌아오겠습니다. 회고 읽어주셔서 감사합니다. :)

 

 

 

 

특정 열의 값에 대해 순위를(랭킹을) 매기기 위해서 다음과 같은 함수를 사용해볼 수 있습니다.

  • RANK
  • DENSE_RANK
  • ROW_NUMBER
  • NTILE

기본적인 함수 형식은 아래과 같습니다.

RANK() OVER ( [PARTITION BY colName1] ORDER BY colName2 [DESC] )
DENSE_RANK() OVER ( [PARTITION BY colName1] ORDER BY colName2 [DESC] )
ROW_NUMBER() OVER ( [PARTITION BY colName1] ORDER BY colName2 [DESC] )
NTILE() OVER ( [PARTITION BY colName1] ORDER BY colName2 [DESC] )

# [대괄호] 안의 값은 선택사항입니다

 

이렇게 보면 외계어같지만 실제로는 사용이 쉬운 함수들 입니다. (진짜루요)

제가 갖고 있는 데이터베이스에서 Employee의 사번, 이름, 젠더, 샐러리(연봉) 4개를 저장한 임시 테이블 TEMP를 가지고 차례차례 함수를 적용시켜 보겠습니다.

 

1. RANK()

RANK()함수는 내가 저장한 행(column)에 순위를 매겨서 정렬한 결과값을 보여주는 함수입니다.

SELECT emp_no, 
	CONCAT(first_name, ' ', last_name) AS full_name,
	salary,
	RANK() OVER(ORDER BY SALARY DESC) AS `rank`
FROM TEMP;

 

위의 쿼리에는 SALARY 행 내림차순을 기준으로 랭킹함수를 적용해서 rank라는 새로운 이름의 행(column)을 반환하도록 했습니다.

 

 

결과를 살펴봅시다. 잘 보시면 rank 행에 128등이 두 명입니다. 이유는 두 명의 salary 값이 중복되기 때문인데요. 이렇게 RANK() 함수에서는 tie : 중복값이 있는 데이터끼리는 같은 등수를 쉐어하게 됩니다. 그리고 그 등수를 쉐어하는 만큼 그 다음 등수는 밀려서 사라지게 되는데요. 128등이 두 명 오고 난 다음에 129등 없이 130등으로 시작하는 것을 보면 알 수 있습니다. 만약 128등이 3명이었다면 그 다음 등수는 131등으로 시작하겠죠.

 

2. DENSE_RANK()

DENSE_RANK() 함수는 RANK()와 거의 비슷하지만 약간 다릅니다.

SELECT emp_no, 
	CONCAT(first_name, ' ', last_name) AS full_name,
	salary,
	DENSE_RANK() OVER(ORDER BY SALARY DESC) AS `rank`
FROM TEMP;

 

 

DENSE_RANK()에서는 같은 값, 중복값의 존재나 갯수와 관계없이 무조건 1씩 차례로 증가합니다. 랭킹이 밀리지 않아요. 127등이 두 명 있다고 그 다음 등수가 129로 시작하지 않고, 128으로 시작하는 것을 보면 알 수 있어요.

 

예를 들어 올림픽에서 금메달을 2명이 공동 수상한다고 가정을 했을 때, 금메달이 2명이라고 은메달은 아무도 주지 않고 동메달을 주면 안 되잖아요? 금메달을 받은 사람의 수와 관계 없이 은메달도 반드시 준다, 라고 보면 됩니다. (금메달 공동 수상이 실제로 가능한지는.... 저도 모르지만요...)

 

그런데 보니까 아까 RANK()에서는 128등이 두 명이었는데 이번엔 127등이 두 명이네요, 왜 그럴까요? 데이터 이미지 중간에 생략된 부분에서 같은 값을 가지는 tie가 두 명 있었습니다. 그래서 RANK()는 숫자가 하나 밀려서 128등 두 명이 되었고, DENSE_RANK()는 숫자가 밀리지 않아서 127등 두 명이 되었어요.

 

3. ROW_NUMBER()

ROW_NUMBER() 함수는 기준에 따라 랭킹 정렬을 하되, 중복값과 관계 없이 무조건 1부터 차례대로 행 번호를 매겨 반환합니다.

SELECT emp_no, 
	CONCAT(first_name, ' ', last_name) AS full_name,
	salary,
	ROW_NUMBER() OVER(ORDER BY SALARY DESC) AS `rank`
FROM TEMP;

 

보시는 것처럼 같은 값이 있던 말던 무조건 1부터 시작해서 하나씩 줄번호를 매겨서 반환합니다.

 

직관적인 예시로 출석 번호나 키번호를 생각해볼 수 있을 것 같아요. 학급에 동명이인이 있다고 둘이 같은 출석번호를 쉐어하지는 않죠. 키가 같다고 키번호를 똑같이 쓰지도 않을 거구요. 이런 상황에서 ROW_NUMBER() 함수를 사용하면 되겠습니다.

 

4. NTILE()

NTILE 함수는 랭킹을 매기되, 내가 지정한 블럭 갯수만큼 구간을 나누어 등급 랭킹을 부여합니다.

SELECT emp_no, 
	CONCAT(first_name, ' ', last_name) AS full_name,
	salary,
	NTILE(5) OVER(ORDER BY SALARY DESC) AS `rank`
FROM TEMP;

 

위의 코드에서 NTILE(5)와 같이 NTILE 함수 안에 정수를 넣어 줬는데요. 이 말은 1, 2, 3, 4, 5등 구간으로 나누어 5개의 등급을 매겨 랭킹을 반환하라는 뜻이에요.

 

데이터 갯수가 많아 이렇게 초반부에는 1등급만 보이지만

샐러리를 내림차순으로 정렬한 다음 5개의 등급으로 나누어 랭킹이 1부터 5까지 차례대로 부여가 되었습니다.

 

5. PARTITION BY 추가 응용

그럼 PARTITION BY는 어떻게 활용할 수 있을까요?

SELECT emp_no, 
	CONCAT(first_name, ' ', last_name) AS full_name,
	salary,
	RANK() OVER(PARTITION BY emp_no ORDER BY SALARY DESC) AS `rank`
FROM TEMP;

 

 

 

제가 가지고 있던 TEMP 테이블에는 같은 사람의 샐러리가 매년 업데이트 되며 누적되어 있어서, 이렇게 이름에 따라서 샐러리 값을 여러 열(row)이 저장 되어 있었어요. OVER() 내부의 시작 부분에 PARTITION BY emp_no 를 추가해 주면서 직원 번호에 따라 파티션을 나누고 그 파티션 내부에서 랭킹을 매긴 값을 반환받았습니다. 이를 통해 직원별로 샐러리가(연봉이) 얼마나 상승했는지를 한 눈에 알아볼 수 있게 되었습니다.

 

<문제 설명>
어느 한 게임에서 사용되는 아이템들은 업그레이드가 가능합니다.
'ITEM_A'->'ITEM_B'와 같이 업그레이드가 가능할 때
'ITEM_A'를 'ITEM_B' 의 PARENT 아이템,
PARENT 아이템이 없는 아이템을 ROOT 아이템이라고 합니다.

(중략)....

 

<문제>
아이템의 희귀도가 'RARE'인 아이템들의 모든 다음 업그레이드 아이템의 아이템 ID(ITEM_ID), 아이템 명(ITEM_NAME), 아이템의 희귀도(RARITY)를 출력하는 SQL 문을 작성해 주세요. 이때 결과는 아이템 ID를 기준으로 내림차순 정렬해 주세요.

 


 

 

처음에 대충 생각하고 접근했더니 원하는 정답을 얻어내기 어려웠던 문제입니다!

 

다시 정신을 집중하고, RIGHT 조인과 서브쿼리를 이용해서 문제를 바로 해결했어요.

 

제가 풀이한 방법을 설명해 보겠습니다.



https://school.programmers.co.kr/learn/courses/30/lessons/273711

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 


 

 

먼저 아이템의 희귀도가 'RARE'인 아이템들을 뽑아내서 시각화해 봅니다.

SELECT * FROM ITEM_INFO WHERE RARITY = 'RARE'

 

우리는 이 4개의 아이템들의 다음 업그레이드 아이템을 찾아야 합니다. 이를 위해서는 ITEM_TREE 테이블을 확인해 보아야 해요.

 

 

ITEM_TREE 테이블을 보고 직관적으로 이해하기가 어려웠어요. 딱 봤을 때 구조가 한눈에 들어오지 않았습니다. (쉽게 보이시는 분이 계시다면.... 멋지십니다! 따봉!)

 

 

그래서 이렇게 간단히 그래프를 그려 봤더니 쉽게 이해가 되었습니다. 우리가 구한 RARE 아이템은 0, 1, 3, 4였어요. 이 아이템들의 다음 업그레이드 아이템은 (0으로부터) 1, 2번 아이템 + (2로부터) 3, 4번 아이템 => 총 1, 2, 3, 4번 아이템이 해당됩니다. 이해가 되셨을까요?

 

그렇다면 일반화된 쿼리문으로 작성하려면 어떻게 해볼 수 있을까요? 우선 우리가 구한 RARE 아이템 0, 1, 3, 4가 부모가 되는 아이템, 즉 0, 1, 3, 4의 자식 아이템을 찾아야 해요. 이를 위해서 ITEM_TREE 테이블의 PARENT_ITEM_ID와, ITEM_INFO 테이블의 ITEM_ID가 동일한 부분을 연결해 주면 됩니다.

 

SELECT * FROM ITEM_INFO I
	RIGHT JOIN ITEM_TREE T
	ON I.ITEM_ID = T.PARENT_ITEM_ID
WHERE RARITY = 'RARE'

 

1) ITEM_INFO 테이블에서 RARITY가 'RARE'에 해당하는 아이템 중에서,

2) 그 아이템이 ITEM_TREE 테이블에 있는 어떤 놈의 PARENT_ITEM_ID에 해당한다면,

3) 그 놈들을 모두 뽑아내봐요.

 

라고 명령한 것과 같습니다. 그럼 위와 같이 결과를 시각화할 수 있는데요. 여기서 우리에게 필요한 것은 무엇일까요?

 

 

우리한테 필요한 것은 저기 저 1, 2, 3, 4번 아이템 번호밖에 없어요. T 테이블(ITEM_TREE)의 ITEM_ID만 뽑아내 주면 됩니다. SELECT 뒤에만 간단하게 바꾸어 줘 봅시다.

 

SELECT T.ITEM_ID FROM ITEM_INFO I
        RIGHT JOIN ITEM_TREE T
        ON I.ITEM_ID = T.PARENT_ITEM_ID
WHERE RARITY = 'RARE'

 

 

이제 우리가 원하는 아이디를 뽑아냈으니, 이 테이블을 서브쿼리로 기존의 ITEM_INFO 테이블과 조인해 주면 끝입니다!

 

SELECT X.ITEM_ID, I.ITEM_NAME, I.RARITY
FROM (SELECT T.ITEM_ID
      FROM ITEM_INFO I
      RIGHT JOIN ITEM_TREE T
      ON I.ITEM_ID = T.PARENT_ITEM_ID
      WHERE RARITY = 'RARE') X
JOIN ITEM_INFO I ON X.ITEM_ID = I.ITEM_ID
ORDER BY ITEM_ID DESC

 

서브쿼리는 X로 이름을 지정해 주었습니다. X의 아이템 아이디와 ITEM_INFO 테이블(I)의 아이템 아이디가 같다면 아이디/이름/레어리티를 셀렉하여 프린트하고, 아이템 아이디를 기준으로 내림차순 하여 정렬하도록 지정했습니다.

 

채점 결과 100.0

 

코드 통과, 정답입니다 :)

 

이해가 안 되는 부분이 있으시다면 댓글 달아주세요!

 

서브쿼리와 RIGHT JOIN 연습을 할 수 있었던 좋은 문제였습니다.

 

 

 

 

주제 : 심장과 혈관 의학 분야에서의 인공지능과 데이터과학

일시 : 2024년 4월 4일 (목) 20:00 - 21:10

강사 : 미국 Mayo Clinic 이은정

 

 

서론

4월 4일 목요일 Zoom을 이용한 실시간 화상 온라인 방식으로 진행된 이은정 강사님의 <심장과 혈관 의학 분야에서의 인공지능과 데이터과학> 세미나에 참여했습니다. 이은정 강사님께서는 서울대학교에서 석박사 과정을 마치신 뒤 미국에서 가장 큰 병원 중 하나인 Mayo Clinic에서 Senior Data Science anlayst로 계시면서 다양한 의학 인공지능 모델을 개발하고 데이터과학을 연구하고 계신 멋진 분이셨습니다. 저는 현재 고려사이버대학교에서 최대영 교수님의 빅데이터 개론 수업을 듣고 있는데, 교수님께서 본 세미나에 참가할 수 있도록 초대해 주신 덕분에 좋은 기회로 참여할 수 있었습니다.

 

제 주변에 의학 관련 분야에 종사하는 지인이나 친인척이 몇 있습니다. 그래서 저는 평소 의학 분야에도 어느 정도 관심을 가지고 있었습니다. 특히 의료 분야에서 데이터의 중요성, 인공지능의 빠른 성장에 대해서는 익히 들어본 바가 있어 큰 호기심을 가지고 있었습니다. 그러나 의료 분야는 어쩐지 진입장벽이 있다고 느껴졌어요. 어디서부터 관련 도메인 지식을 수집해 나갈지 막막했습니다. 그렇게 멀게만 느껴지던 의학 도메인에 조금 더 가까워지는 계기가 되기를 바라며 강의를 듣기 시작하였습니다.

 

본론

(1)

 

먼저 강의는 미국의 심혈관 질환에 대한 통계 자료와 분석으로 시작하였습니다. 2016년 데이터를 기준으로 미국 인구의 약 절반이 고혈압을 가지고 있는 것으로 추정되며, 2035년까지 미국 인구의 약 45%가 심장 관련 질병 가지게 될 것으로 예상된다고 하는데요. 이를 뒷받침하는 자료로 미국에서는 성인 5명 중 오직 1명만이 적정한 양의 운동을 하고, 전자담배의 사용으로 흡연률이 치솟고 있는 등 다양한 통계를 함께 볼 수 있었습니다.

 

2022년 잠깐이나마 뉴욕에서 어학연수를 했던 기억을 떠올려 보았습니다. 뉴욕에는 확실히 한국보다 다양한 체형을 가진 사람들이 있었어요. 다양한 사이즈의 체형이 용납되는 개방적이고 자유로운 사회적 분위기가 저는 참 좋았던 기억이 있습니다. 지금 돌아보니 빅 사이즈 국민들의 건강 관리가 그만큼 중요한 과제가 될 수도 있겠다는 생각이 드네요. 이렇게 미국에서는 큰 사회적 이슈가 되고 있는 심혈관 질병 진단과 관리를 위해 인공 지능이 다양하게 개발되고 활용되고 있다고 합니다.

미국에서 심혈관 질환 관련 의료 마켓에서 AI의 성장률을 확인해볼 수 있는 그래프 자료입니다.

(2) - 1

다음으로 심혈관 질환을 위한 인공지능 모델 개발을 위한 데이터 자료에 관해 말씀해 주셨습니다. AI 트레이닝, 테스팅 데이터로 활용되는 가장 대표적인 데이터 자료 두 가지는 환자 기록과 검사 결과입니다. 환자 기록으로는 몸무게, 키, 혈압, 피검사, 환자 내원시 상담 내용 등이 있습니다. 검사 결과로는 ECG라고 불리는 심전도 검사 결과, 심초음파, 혈관조영상, 망막이미지 등이 있습니다. (심혈관 질병 관련)

 

요즘은 스마트 워치로도 간단하게 심전도 측정이 가능한 세상입니다. 이렇게 스마트 워치를 활용하여 측정한 심전도 자료 역시 인공지능 개발에 활용이 되기도 한다고 합니다. 물론 병원의 전문 장비를 이용한 측정 결과랑 비교하자면 신뢰도가 많이 떨어지기 때문에, 이렇게 신뢰도가 낮은 데이터를 가지고 모델 개발을 하면 그만큼 정확도가 떨어질 수밖에 없다고 하셨습니다. 그래도 집에서 간단하게 간이 방식으로 심전도를 측정하여 문제 상황을 조금이나마 예측하고 예방할 수 있다면 큰 도움이 되겠지요. 언젠가 병원의 전문 장비만큼 실력이 짱짱한 스마트 워치가 보급될 지도 모를 일입니다.

 

망막 이미지와 목소리에 관련해 말씀해 주신 부분이 굉장히 흥미로워 기억에 남는데요. 요즘은 망막 이미지 하나만으로도 나이, 성별, 흡연여부, 혈압 비만도 등의 건강 정보를 예측을 할 수 있는 수준으로 모델 개발이 이루어져 있다고 합니다. 굉장히 신기했어요. 또 목소리를 이용하여 심장 관련 진환을 예측을 할 수 있다고도 하셨습니다. 목소리라고 하면 기관지 컨디션이나 기분 정도만 짐작해볼 수 있는게 아니냐고들 생각하지만 목소리에는 예상 외로 몸에서 발생하는 이상 신호들이 잘 반영된다고 합니다. 생각보다 예측율이 높은 편이라고 해서 정말 신기했어요. 이렇게 상식을 뛰어 넘는 재밌는 모델링 연구 작업에 저도 참여할 수 있다면 얼마나 좋을지 기대가 되었습니다. 의료 도메인에도 꾸준한 관심을 가지고 포트폴리오를 구축해 나가 봐야 겠다는 욕심이 들었어요.

 

(2) - 2

다음으로 데이터를 활용해 어떤 모델을 개발할 수 있는지에 대해 말씀해 주셨습니다. 가장 큰 연구가 이루어지고 있는 분야는 바로 질병을 예측할 수 있는 모델 개발이라고 합니다. 가격이 비싼 혈관조영상 촬영 없이 상대적으로 저렴한 심장 박동, 심초음파 검사 결과만으로 좌심방의 크기와 대동맥 판막 협착증을 예측하는 모델을 예시로 들어 주셨습니다. 초기에 잡아냈다면 미리미리 관리하여 쉽게 치료할 수 있었던 질병들을 뒤늦게 발견해서 큰 문제가 되는 경우가 많잖아요. 저희 외조모께서도 대장암을 초기에 발견하지 못해 결국 투병하시다가 2년만에 돌아가셨었거든요. 인공지능 모델을 통해 세계적으로 질병 예측의 시기가 앞당겨지고 정확성이 크게 증진될 수 있기를 바라는 마음입니다.

 

또 기존에는 상위 검사만으로 측정이 가능했던 수치들을 하위 검사로 측정 가능할 수 있도록 돕는 모델 개발에 관해서도 언급하셨습니다. 상위 검사는 검사 방법이 복잡한 대신 정확도가 높은 특징이 있습니다. 그만큼 비용이 높을 수밖에 없는데, 특히 미국의 악랄한 의료비에 대해서는 다들 잘 알고 계시지요. 대부분의 상위 검사들을 한국에서는 어렵지 않게 받을 수 있지만 미국에서는 그렇지가 못한 현실이라고 해요. 이렇게 상위 검사만으로 측정 가능했던 수치들을 하위 검사 결과로부터 예측해낼 수 있는 모델들을 개발하는 겁니다. 이게 보편화가 된다면 비용때문에 의료 서비스를 받지 못했던 사회적 약자들에게 커다란 도움이 되겠다는 생각을 했습니다. 저도 뉴욕에 있을 때 갑자기 엄청난 복통이 찾아와 응급실에 갔다가 아픈 것보다도 병원비를 걱정하느라 마음 고생을 했던 경험이 있거든요. 질 좋은 의료 서비스를 모두가 평등하게 받을 수 있는 세상을 만드는 데 저도 도움이 될 수 있으면 좋겠습니다.

 

그 밖에 인공지능 모델 개발을 위해 데이터 레이블링하는 작업에 큰 시간과 비용이 소요되는데, 이런 레이블링을 대신하는 모델을 역시 활발하게 개발이 이루어지고 있다고 알려 주셔서 흥미로웠습니다. 인공지능 모델 개발을 돕는 모델이라니! 미래에는 인공지능끼리 주르륵 체인을 이루면서 인간의 개입이 전혀 필요하지 않은 세상이 언젠가 오겠다는 귀엽고도 무서운 생각을 해 봤어요. 그 안에 나의 역할이 무언가 있기를 바랄 뿐입니다.

 

(3)

마지막으로 메이요 클리닉에서 개발하고 있는 인공지능 모델 예제를 몇가지 알려 주셨습니다. 간단히만 정리해 보겠습니다.

 

[1] 정상 심장 리듬에서 심방세동을 예측하는 모델

  • 심방세동은 간헐적으로 발생하며 특이한 증상이 없는 경우가 많아서 진단에 어려움이 많다.
  • 심방세동의 정확한 진단 및 예측을 위해서는 환자가 병원에 긴 시간 내원해야 한다. 대체로 환자가 24시간동안 몸에 리드줄을 부착하고서 수집한 데이터로 판단을 내리는 방식으로 진단이 이루어졌다고 한다.
  • 메이요 클리닉에서 보관하고 있는 18만명이 넘는 환자들의 데이터, 65만개가 넘는 ECG 기록을 가지고 단시간 측정한 정상 리듬에서도 심방세동을 예측해낼 수 있는 인공지능 모델을 개발하였다.
  • 기존의 진단이 여러 전문 인력의 수작업으로 이루어져 시간과 자원이 과하게 소모되었으나 이 과정들을 인공지능으로 대체할 수 있게 되면서 시간과 자원 비용을 크게 절약하게 되었다.

[2] 비대성 심근병증 (HCM) 분류

  • 비대성 심근병증은 심장 근육이 비정상적으로 두꺼워져 형태가 변형되고 기능이 악화되는 질환으로, 특히 미국에서는 HCM으로 돌연사하는 프로, 아마추어 운동선수가 많아서 사회적 이슈라고 한다.
  • 메이요 클리닉에서 저렴한 심장 박동 검사 결과만을 가지고 HCM을 예측할 수 있는 모델을 개발하였다. 심장 박동 검사는 비용이 저렴할 뿐만 아니라 다양한 시설에 보편화가 많이 되어 있어서 접근성이 무척 높은 하위 검사이다.
  • 테스팅 어큐러시가 0.95 - 0.97으로 무척 높았는데, 미국 뿐만 아니라 다른 여러 나라들에서도 이벨류에이팅을 해 보니 역시 높은 어큐러시 결과가 나왔다고 한다.

[3] ECG(심전도)를 이용한 좌심실 이완기능 평가

  • 좌심실의 이완 기능은 심장 기능 평가에 있어서 아주 중요한 사항이다. 좌심실 이완 시 높은 filling pressure는 다양한 심혈관 질환과 관련되는 악조건이다. 현재 미국에서 70세 이상의 노인의 70%가 불완전한 좌심실 이완 기능을 가지고 있다는 통계 결과가 있다고 한다.
  • 좌심실 이완 기능 평가는 혈관조영상으로 판단하는 것이 가장 이상적이다. 그러나 혈관조영상 촬영은 굉장히 어렵고 복잡하며 비용이 높은 상위 검사이다. 이를 대체하기 위해 심초음파 결과를 이용하는 경우가 많은데, 정확성이 매우 떨어진다고 한다.
  • 메이요 클리닉에서 ECG(심전도) 검사 결과를 통해 좌심실 이완 기능을 평가하는 인공지능을 개발하였다. 검사 결과로 Grade 1-2-3 세 단계의 등급을 매기게 되는데, 등급이 높을 수록 위험한 상태를 의미한다.

 

결론

이번 이은정 강사님의 세미나는 심혈관 질환의 예측과 진단에 데이터와 인공지능이 어떻게 활용되고 있는지 그 실제 사례를 알아볼 수 있는 무척 좋은 학습 기회였습니다. 평소 '미래에는 인공지능이 의사를 대체할 것이다!'는 무시무시한 이야기를 들어만 보았지, 실제로 병원에서 어떤 식으로 개발이 되고 활용이 되고 있는지 그 개별 사례를 알아보기가 쉽지는 않았거든요. 강사님께서 실제 사례와 경험을 토대로 강의해 주신 덕분에 앞으로 심혈관 질환 뿐만 아니라 다른 분야에서도 인공지능 적용 사례를 찾아보기가 수월해질 것 같습니다.

 

이런 의료 도메인에서의 인공지능의 개발은 질 높은 의료 서비스를 더 많은 사람들이 받을 수 있도록 돕습니다. 그만큼 사회적, 인도적으로 큰 의미가 있다고 생각해요. 과학 기술 발달의 바람직한 예라고 할 수 있겠죠. 특히 저는 초등교육에 제 20대 모두를 바쳤던 만큼 어린이와 청소년의 신체건강, 정신건강에 특히 큰 관심을 가지고 있는데요. 학생 개인정보 보호를 위해 자세한 사례를 여기에 세세히 밝힐 수는 없지만, 타고난 유전병으로 인해 자유로운 활동이 어려운 학생을 가르쳐 보았고, 신체적 장애를 가지고 있어 신체활동에 제약이 있는 학생도 가르쳐 보았고, 자폐 스펙트럼을 가지고 태어나 친구를 사귀기 어려워하는 학생도 가르쳐 보았습니다. 가정환경이 어려워 필요한 만큼 의료 서비스를 받지 못하는 친구도 있었는데 제가 도울 방법이 제한적이라 참 안타깝고 미안했었어요. 이렇게 다양한 어려움을 가지고 있는 학생들을 도울 수 있는 인공지능 모델에는 무엇이 있을지 앞으로 계속해서 고민해 보고, 관련 데이터를 찾아보고 분석해 보고자 합니다.

 

질의응답 시간에 이은정 강사님께 어린이를 대상으로 인공지능 모델을 개발해 본 경험이 있으신지 질문을 드렸었는데요. 어린이들은 신체적으로 어른과 무척이나 다르기 때문에 어른을 대상으로 개발한 모델을 어린이들에게 동일하게 적용하기는 어렵다고 하셨습니다. 따라서 영유아나 어린이를 대상으로 한 모델의 경우 성인을 대상으로 개발한 성공적인 모델을 가지고 수정 보완하여 만들어내는 경우가 많다고 하셨어요. 좋은 답변이 되었습니다. 추후에 시간을 내어 구체적인 사례를 찾아보기로 하였습니다.

 

마지막으로 이렇게 개발된 인공지능 모델이 완전히 전문인력을 대체하고 있는 상황은 아니라고 말씀을 해 주셨습니다. 의료진이 진단을 하고 판단을 내리는 데 근거가 되는 하나의 수단으로 인공지능 모델의 예측 결과를 활용하고 있다고 하셨어요. 데이터를 분석하고 모델을 개발할 줄 아는 능력 있는 데이터 사이언티스트는 이렇게 원하는 분야의 전문 인력과 협업할 수 있구나! 좋은 자극이 되었습니다. 열심히 노력해서 저 역시 이렇게 세상의 발전에 기여하고 다른 이들에게 영감이 되는 전문가가 되겠다고 다짐하며, 이번 세미나 리뷰를 마칩니다.

 

 

 

읽어주셔서 감사합니다.

 

 

 

프로그래머스의 MySQL 코딩테스트 연습문제 '노선별 평균 역 사이 거리 조회하기' 를 풀었습니다. 서브쿼리와 ORDER BY에 대해 정리해두기 좋은 문제인 것 같아 블로그 포스팅을 해 보도록 하겠습니다.

 

https://school.programmers.co.kr/learn/courses/30/lessons/284531

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

먼저, 1차로 제출한 정답코드입니다. (오답)

SELECT ROUTE, 
        CONCAT(ROUND(SUM(D_BETWEEN_DIST), 1), "km") AS TOTAL_DISTANCE,
        CONCAT(ROUND(AVG(D_BETWEEN_DIST), 2), "km") AS AVERAGE_DISTANCE
FROM SUBWAY_DISTANCE
GROUP BY ROUTE
ORDER BY TOTAL_DISTANCE DESC

 

테스트 케이스는 통과했으나 정답으로 제출하면 결과가 틀리다고 나와 저를 당황스럽게 하였습니다. 무엇이 문제였을까요? 문제 조건을 다시 한 번 살펴보겠습니다.

1) 총 누계 거리와 평균 역 사이 거리의 컬럼명은 각각 TOTAL_DISTANCE, AVERAGE_DISTANCE 
2) 총 누계거리는 소수 둘째자리에서, 평균 역 사이 거리는 소수 셋째 자리에서 반올림 한 뒤 단위(km)를 함께 출력
3) 결과는 총 누계 거리를 기준으로 내림차순 정렬

 

왠지 CONCAT으로 스트링으로 만들어버린 값을 정렬하면서 3번에서 문제가 생기지 않을까 싶었어요. 그래서 1차로 제출했던 코드에서 CONCAT 함수를 제외한 전체를 서브쿼리로 사용해 보기로 했습니다. 

 

(SELECT ROUTE, 
        ROUND(SUM(D_BETWEEN_DIST), 1) AS TOTAL_DISTANCE,
        ROUND(AVG(D_BETWEEN_DIST), 2) AS AVERAGE_DISTANCE
FROM SUBWAY_DISTANCE
GROUP BY ROUTE
ORDER BY TOTAL_DISTANCE DESC) BF

 

먼저 CONCAT을 빼버린 부분을 전체 ( ) 괄호로 묶어서 임시 테이블 BF로 만들어 자기참조합니다.

그럼 2차로 제출한 정답코드를 보여드리겠습니다.

SELECT BF.ROUTE,
       CONCAT(BF.TOTAL_DISTANCE, "km") AS TOTAL_DISTANCE,
       CONCAT(BF.AVERAGE_DISTANCE, "km") AS AVERAGE_DISTANCE
       
FROM (SELECT ROUTE, 
        ROUND(SUM(D_BETWEEN_DIST), 1) AS TOTAL_DISTANCE,
        ROUND(AVG(D_BETWEEN_DIST), 2) AS AVERAGE_DISTANCE
FROM SUBWAY_DISTANCE
GROUP BY ROUTE
ORDER BY TOTAL_DISTANCE DESC) BF

 

합계: 100.0 / 100.0 (정답)

 

FROM 뒤에 임시 테이블(이너 테이블) BF를 넣어 줍니다. TOTAL_DISTANCE를 기준으로 내림차순 정렬은 이미 임시 테이블 BF에서 숫자형태로 완료가 된 상태입니다. 이렇게 정렬이 완료된 임시 테이블에서 CONCAT을 이용해 "km"을 붙이고 스트링화 해주었더니 정답으로 인정이 되었습니다.

 

 

서브쿼리와 정렬에 대해 생각해볼 수 있는 좋은 문제였습니다.

 

 

 

 

 

작년에 의원면직(사직)을 한 후 꽤 긴 시간이 흘렀습니다. 이제는 전 동료가 된 교사 친구들이 SNS에 올리는 새 학기 소식을 보면 '강 건너 불구경이 따로 없구먼' 하는 표정으로 흐뭇하게 미소를 짓게 됩니다.

 

 

 

나는 준비된 상태로 사직하지 못했습니다. 이 일을 그만두고 난 뒤의 미래에 대해서 꾸준히 고민했지만 답을 알 수 없었습니다. 결국 해답을 찾지 못한 채로 의원면직을 해 버렸습니다. 용기와 치기 사이, 무모와 대담 가운데 어딘가에 있었던 결정이었습니다. 지금 돌아보면 그때 열심히 고민만 했던 게 문제였습니다.

 

 

 

이전에 글 쓴 대로, 첫 3개월은 미친 듯이 수능 공부를 했습니다. 그땐 내가 할 수 있는 게 그것밖에 없었습니다. 어떠한 분야로 진출한다는 것은, 괜찮은 대학에서 관심 있는 학과를 전공하고 관련 분야로 취직한다는 것, 그게 내가 알고 있는 전부였기 때문입니다.

 

 

 

결과적으로 나쁘지 않은 성적을 받았음에도 대학에 가지 않았습니다. 누군가는 시간을 버렸다고 말할 수도 있겠습니다. 그렇지만 그동안 잊고 살았던 고등수학과 천문학을 공부하며 내가 논리와 알고리즘을 좋아하는 사람이라는 것을 알게 되었고, 소프트웨어공학 분야로 진출하겠다는 결심을 하게 됐고, 나는 반드시 해낼 수 있다는 자기 확신을 얻었습니다. 내가 만약 실리를 따져 가면서 수능 공부를 하지 않았더라면 그런 결심을 할 수 있었을까요.

 

 

 

나는 수능 다음날부터 파이썬 기초 문법을 시작으로 본격적으로 코드를 독학하기 시작했습니다. 비전공자도 개발을 배울 수 있는 콘텐츠는 세상에 넘쳐흐르고 있었습니다. 나의 생각과 논리가 코드로 고스란히 기록에 남는 일은 짜릿했습니다.

 

 

 

그러고 보니, 내가 교사 일을 왜 좋아했는지도 다시금 돌아보게 되었습니다. 학교 현장은 학생에 관한 수많은 데이터의 정글입니다. 초등교사는 흘러 넘치는 학생들의 데이터를 수집하고 분석하고 통찰하며 학급을 이끌고 교육과정을 운영해야 합니다. 그중에서도 나는 특히 학생을 이해하는 일을 잘해서 학생과 학부모들에게 인기가 좋았습니다. 모든 일에는 원인과 결과가 있다는 나의 원칙이 있었기 때문에 가능한 일이었습니다. 어른의 시각에서 이해하기 어려울 수도 있었던 아이들의 행동들을 나는 늘 이해했습니다. 그 일이 어렵지 않았습니다. 아이들의 크고 작은 갈등해결은 생각보다 간단합니다. 데이터를 수집하고 분석하여 의미 있는 결과를 도출하는 일과 다름없어요. 내가 할 일은 별게 없습니다. 귀를 기울이고 원인과 결과를 파악한 다음 교육자, 보호자, 어른으로서 나의 생각을 뽑아내 조언합니다. 아이들은 자신의 이야기에 귀를 기울이고 이해하는 어른이 있다는 사실 하나만으로도 반성하고 성장하더라고요. 그런 아이들을 보면서 제가 오히려 배우곤 했습니다.

 

 

 

교육과정과 학습목표, 성취기준을 분석하고 창의적으로 수업을 구성하여 이끌어가는 일도 좋았습니다. 초등학교 담임에게는 학급 교육과정 운영의 자율성이 주어지는 편입니다. 나는 내 입맛대로 학급을 이끌어 나가는 일이 즐거웠습니다. 아이들의 수업 태도와 참여도를 능동적으로 관찰 평가하면서 수업을 개선하고 새로운 교수 학습 기법을 적용해 보는 등 다양한 시도를 멈추지 않았습니다. 나의 수업은 학생들 사이의 대화로 넘쳐흘렀습니다.  내가 가르친 학생들은 요즘 같은 삭막한 시대에 꼭 필요한 의사소통 역량을 조금은 배워갔을 것입니다.

 

 

 

비록 새로운 가치와 성장을 찾아 학교를 떠나게 되었으나 교사로 일하는 동안 정말 행복했어요. 말장난같이 들릴 수 있겠지만 내가 만약 학교에서 일하지 않았다면 학교를 떠날 일도 없었겠지요. 나는 도전과 실패에 대한 두려움을 떨치고 학교를 떠나기로 결정한 스스로가 이제는 자랑스러워요. 그리고 학교를 떠날 수 있도록 나에게 가르침을 준 학생들에게 고맙습니다.

 

 

 

제목에 진로를 결정했다고 썼지요. 나는 데이터 사이언스와 AI 엔지니어링 분야에 뜻을 품었습니다. 처음에는 모두가 그렇듯 웹개발을 위주로 공부를 시작했는데, 궁금한 분야를 파고 파고 파다 보니 여기까지 도착했습니다. 지금은 텐서플로우 코드와 함께 딥러닝을 공부하는 데 몰두하고 있습니다. 처음엔 이것이 외계어가 아니면 뭐란 말인가, 싶을 만큼 어렵고 막막했어요. 그런데 어려운 만큼 결국엔 이해하고 소화해 냈을 때의 쾌감은 이루 말할 수 없을 만큼 짜릿합니다. 나는 이게 정말 재밌어요. 엄청난 재능이 있는 것 같지는 않은데, 30대에는 여기에 내 모든 걸 쏟아봐도 좋겠다는 생각이 들 만큼의 열정이 있어요.

 

 

 

작년에 수능 준비를 하면서 수학 선택 과목으로 미적분을 열심히 공부했었거든요. 간당간당하게 1등급도 받았었고요. 그때 미적분을 공부해 둔 게 딥러닝을 공부하는 지금 도움이 되네요. 어떤 도전이든 나에게 손해 될 것은 없다는 것을 다시금 확인합니다.

 

 

 

또 재밌는 건, 제가 이번에 사이버대학에 편입을 했어요. 작년에 수능까지 봐 놓고서 사이버대학에 들어가다니 웃기지요? 독학으로 공부를 하다 보니 전공자들은 학부에서 어떤 것들을 배우는지 궁금했거든요. 나중에 혹시 공대 대학원에 진학해서 석사과정을 밟을 수도 있으니까 공학 학사를 따 두면 도움도 될 것 같았고요. 하지만 오프라인 대학을 다니는 기회비용을 감당하고 싶진 않았기 때문에 사이버대학교 3학년으로 편입을 했어요. 이번 학기에는 수업 4개를 듣는데, 수업 하나하나가 기대했던 것 이상으로 알차서 정말 만족하고 있습니다.

 

 

 

특히 빅데이터 개론을 들으며 학문적 기반을 다지고, 데이터와 인공지능을 다룰 때 정말 정말 중요한 통계학 수업을 들으며 부족했던 수학 지식을 보충할 수 있는 게 좋습니다. 실무에 필요한 코드 작성은 내 취향에 맞게 독학하고, 거기다 조금만 시간을 더 내어 집에서 간편히 대학 수업을 들을 수 있다니, 정말 편리하고 행복합니다. 내가 만약 네임밸류에 집착하며 오프라인 대학을 고집했다면 이렇게 시간을 알뜰히 활용할 수 없었을 거예요. 배움에는 왕도가 없습니다.

 

 

 

이전에 말한 것처럼 해외 진출에 대한 열망은 아직도 건재합니다. 그런데 조금 바뀐 점이 있다면, 지금 당장 해외로 무조건 나가고 말겠다는 생각은 사라졌습니다. 일단 열심히 공부하고 배울 생각입니다. 좋은 기회가 온다면 국내 기업에서도 경력을 쌓고 싶어요. 그런 다음 나가도 늦지 않을 것 같습니다. 아니면 글로벌하게 일할 수 있는 외국계 기업에 취업하는 것도 좋겠지요. 글로벌 역량을 키우기 위해 모든 독학은 영어 콘텐츠로 진행하고 있습니다. 강의 하나를 들어도 영어로 된 강의를 보고, 책 하나를 봐도 원서로 봅니다. 조금 더 기본기가 탄탄해지면 논문 리딩과 분석도 해보려고 합니다.

 

 

 

말은 뻔지르르하게 했지만, 사실 저는 그냥 공부하고 있는 백수입니다. 그렇지만 지금 행복합니다. 언제든 취업은 하겠지요. 그게 올해가 되든 내년이 되든 지금은 내가 할 수 있는 것에 집중하려고 합니다. 열심히 공부하고 적극적으로 기록하고 있습니다. 브런치에도, 앞으로 더 자주 글을 쓰겠습니다. 의원면직을 고민하는 많은 교사들, 또 직무전환을 고려하는 많은 직장인들에게 저의 이야기가 조금이나마 위로가 되었으면 좋겠습니다.

 

 

 

또 찾아올게요! 감사합니다.

 

 

 

 

 


 

 

[붙임]

11월 말부터 3월까지 4달 동안 공부한 것들을 순서대로 정리해 봅니다.

 

  • 파이썬 기본 문법
  • 파이썬 Flask 라이브러리
  • HTML, CSS, Boostrap5
  • Git, Github
  • 자바스크립트
  • MySQL
  • 정보처리기사 필기(합격)
  • 파이썬 Numpy, Pandas, Matplotlib, Seaborn 라이브러리
  • Linux 커맨드라인
  • R
  • OPIC AL / TOEIC 970
  • 프로그래머스/해커랭크 코딩테스트 문제해결 (현재진행 중, 아직 많이 부족해요)
  • 고려사이버대학(편입) - 확률과 통계, 소프트웨어공학, 빅데이터개론, 파이썬 강의 수강 중
  • 파이썬 Tensorflow와 함께 Neural Network 이론 학습 (현재진행 중, 많이 부족하지만 즐겁게 하고 있습니다.)

 

https://brunch.co.kr/@suriring/46

 

사직 후 진로를 결정하다

초등교사 의원면직 04 | 작년에 의원면직(사직)을 한 후 꽤 긴 시간이 흘렀습니다. 이제는 전 동료가 된 교사 친구들이 SNS에 올리는 새 학기 소식을 보면 '강 건너 불구경이 따로 없구먼' 하는 표

brunch.co.kr

 

 

 

 

 

  의원면직을 결심하고 나서 나에게 주어진 가장 큰 과제는 이후 행로에 대해 방향을 잡는 것이었습니다. 그 큰 틀이 세워져야 세부적인 계획도 세울 수 있을 테니까요. 내가 원하는 것은 무엇인가, 나는 어떤 사람인가를 꾸준히 탐색하려고 노력했습니다. 부모님과 대화도 주기적으로 했고, 스스로를 돌아보기도 했으며, 전문가의 상담을 받기도 했어요.

 

 

 

 

 

 

나는 올해 대학수학능력시험에 응시했습니다. 갑자기 수능이라니, 뜬금없어 보일 수 있겠지만 지금 내가 할 수 있는 최선은 수능이라고 판단이 되었어요. 

 

 

 

학교 일은 재밌었지만 공교육은 교사 개인의 노력의 결과가 눈에 수치화되지 않으며 그에 따라 보상도 받을 수 없는 구조여서 이과적 성향이 짙은 나에게는 충분한 동기부여가 되지 않았습니다. 무능력에 따른 책임을 고스란히 내가 모두 떠안아야 한대도 상관없으니 앞으로는 내 성향에 맞는 이공계나 과학 분야의 일을 선택하고 싶었어요. 

 

 

 

또 학교가 답답했던 나는 더 넓은 세상에서 살고 싶었습니다. 기회가 온다면 반드시 해외로 진출할 수 있는, 또 내가 그런 기회를 만들어갈 수 있는 직업을 갖고 싶었어요. 학교에서는 영어를 아무리 잘해봤자 결국 원어민 뒤치다꺼리나 하게 되었던 게 큰 불만이었고, 짧지만 강렬했던 뉴욕 생활을 통해 글로벌 인재로 거듭나고 싶은 욕구도 커져 있었거든요.

 

 

 

그래서 그게 뭔지 잘은 모르겠지만 일단 수학 과학을 할 줄 알아야 뭐든 할 수 있지 않을까 싶었습니다. 그래서 수능 공부를 하면서 고등 수학 과학을 리마인드도 하고 내가 어떤 과목을 왜 흥미로워하는지 살펴도 보기로 했어요. 만약 수능 점수가 기대 이상으로 잘 나온다면 내가 동물을 좋아하니 수의대를 가면 어떨까? 하고 막연하게 생각하기도 했습니다. 어릴 때부터 컴퓨터 프로그램 다루는 걸 너무 좋아하고 잘했어서 소프트웨어 공학에도 관심이 있었습니다.

 

 


 

 

 

 

세 달간 매일 15시간씩 수능 공부를 했어요. 수능 대신 소프트웨어 개발 공부를 할까 고민도 했지만, 수능은 때와 시기가 있으니 수능이 우선이었습니다. 11년 만에 돌아간 수능판은 완전히 달라져 있어서 정말 너무너무 낯설었어요. 그렇지만 적응하는 데 여유 부릴 시간도 없어 닥치는 대로 미친 듯이 공부만 했습니다.

 

 

 

수의대 얘기가 나왔으니 말인데, 나는 살면서 공부를 못해본 적은 없습니다. 그러나 나는 자기 객관화가 잘 되어 있는 사람입니다. 내가 정시로 메디컬을 노리기엔 참 애매하고 부족한 사람이라는 걸 잘 알아요. 게다가 10년 동안 손 놓고 있던 공부를 두세 달 공부해서 전국 1% 안에 들 수 있을 리가요. 그럼 결국 안 될 일이니 적당히 공부했냐 하신다면 그건 또 아닙니다. 그와 상관없이 모든 걸 쏟았어요. 칼같이 매일 여섯 시에 일어났고 밥 먹을 시간도 아껴가며 공부에 매진했습니다. 나를 테스트하고 싶었어요. 앞으로 몇 년 더 수능에 도전하며 수의대 진학을 노릴 것인가? 막상 건드려보니 수학 과학에 흥미도 능력도 없으므로 이공계 진출은 포기하고 기획이나 마케팅 등 새로운 분야를 탐색할 것인가? 다 떠나서, 새로운 분야에 뛰어들어 공부할 의지와 열정이 있기는 한가? 머릿속을 맴도는 수없는 질문에 스스로 답하고 싶었어요. 나는 이번 입시를 통해 짧고 굵고 빠르게 판단하기로 했습니다.

 

 

 

성적은 괜찮고도 아쉬웠습니다. 미적분과 영어에서 1등급을 받았습니다. 근의 공식도 잊어버려서 중학교 수학부터 출발해야 했던 걸 생각하면, 그리고 투자한 기간과 대비하면 현실적으로 괜찮은 결과였습니다. 부모님께서는 기대 이상의 성적을 보시곤 기뻐하셨습니다. 내가 아쉬워서 재도전할 거라고 생각하셨을 거예요. 근데 말이죠.... 공부하는 동안 모든 과목 중에서 생명과학 공부가 정말 더럽게 싫었어요. 재미도 없었어요. 그러니 수의대에 가겠다고 이 짓을 몇 년 더 하고 싶지도 않았어요. 몇 년 더 공부한다고 꼭 붙을 수 있는 곳도 아니고 말이죠. 명예롭고 안정적인 전문직을 가지고 싶다고 스스로를 설득하고 싶지 않았습니다.

 

 

 

나를 가장 즐겁게 했던 과목은 수학과 지구과학이었습니다. 고등학생 때 지구과학을 선택하지 않았던 터라 스스로도 놀랐습니다. 우주과학 공부가 그렇게 재밌더라고요. 나는 확실히 깔끔하게 떨어지는 논리와 계산, 알고리즘이 좋았습니다. 그러니 소프트웨어로 가야겠다고 결론지었어요. 이렇게 후회나 미련 없이 깔끔하게 판단을 내릴 수 있었던 건 누구보다 열심히 했기 때문입니다. 매일 6시에 일어나 밥 먹을 시간도 아껴가며 15시간씩 공부한 세 달이 전혀 아깝지 않았습니다. (아, 그리고 우주과학은 관련 서적을 찾아 읽고 다큐멘터리와 영화도 찾아보는 등 계속해서 취미로 즐기고 있습니다.)

 

 

 

그래서 수능 다음날 아침부터 바로 개발 공부를 시작했습니다. 국비지원제도 덕분에 요즘 너도 나도 개발에 뛰어들었다가 중도 포기하는 사람이 참 많은 것을 잘 알고 있습니다. 하지만 지난 세 달 동안 미친 듯이 공부에 매진하는 스스로를 보면서... 나는 분명 개발 공부도 열심히 꾸준히 할 수 있겠다는 자기 확신을 얻었습니다. 그 덕분에 도전을 시작할 수 있었어요. 지금은 책과 인터넷 강의를 통해 독학으로 입문 기초를 닦으면서 어떤 개발 분야가 나에게 맞을지 찬찬히 알아보고 있는 중으로, 백엔드도 재밌고 프런트엔드도 재밌어서 풀스택으로 가야 할지 등등 이런저런 즐거운 고민을 하고 있습니다. 또 수능 점수가 괜찮으니 늦깎이 새내기(일명 헌내기)로 입학을 해서 학위를 따 볼지, 아니면 4년을 다 다시 다니기는 좀 너무하니 편입을 또 준비해야 할지와 같은 고민도 하고 있습니다.

 

 

 

앞으로는 개발자가 되기 위해 고군분투하는 과정을 틈틈이 브런치에 기록하도록 하겠습니다. 세상의 모든 도전은 아름답습니다. 그리고 도전하는 나는 아름답습니다. 나는 나를 응원해요. 세상의 모든 도전하는 사람들에게 응원과 격려의 메시지를 전해 봅니다.

 

 

 

 

 

  일을 관둔다고 했을 때 모든 사람이 공통적으로 물었습니다. 학교 일이 그렇게 안 맞아요? 그러면 나는 답했습니다. 아뇨, 일은 정말 재밌었어요. 배운 것도 많고요. 더 이상 애들을 못 본다고 생각하면 마음도 아프고 많이 아쉽기도 해요. 그런데 여기서 평생 일할 생각은 없을 뿐이에요.

 

 

  학교가 끔찍이 싫은 것도 애들과 학부모를 상대하는 일이 적성에 더럽게 안 맞는 것도 아니었습니다. 내가 마주한 가장 큰 문제의식은 앞으로 학교에서의 내 모습이 그려지지 않는다는 것이었어요. 나는 학교에서라면 먼 미래는커녕 당장 5년 뒤의 가까운 미래에조차 원하는 게 없었거든요. 승진을 해서 관리자(교감, 교장)가 되고 싶지도 않았고, 대학원에서 교육학을 연구해서 석박사를 따고 싶지도 않았고, 그렇다고 장학사가 되거나 교육청으로 진출해서 공교육 체계 수립에 기여하고 싶은 마음도 없었어요. 계속 고민했습니다. 그렇다면 나는 여기서 앞으로 어떻게 성장할 것인가?

 

 

  우리나라 학교는 나뿐만 아니라 그 누구에게도 열정을 불태울 수 있을 만한 공간이 아닙니다. 교사가 시간과 노력을 아무리 투자해도 따라오는 보상이 고작 개인적 만족감쯤에 그치고 말기 때문이에요. 신규 발령을 받고 1-2년만 일해도 쉽게 깨달을 수 있습니다. '학교는 열심히 하면 손해를 보는 곳'이라는 사실을. 안타까운 현실이에요. 만약 나에게 결혼과 출산, 육아가 인생의 큰 목표였다면 이야기가 달랐을 지도 모르겠습니다. 학교는 분명, 기혼자에게는 굉장히 메리트가 있는 직장입니다. 나는 확고한 비혼인은 아니긴 합니다만 가정을 일구는 걸 인생에 꼭 이뤄야 할 과업으로 여기진 않습니다.

 

 

<프렌즈>의 레이첼이 센트럴 퍼크의 웨이트리스를 때려치운 덕분에 원하는 패션 일을 하며 행복해질 수 있었던 것, 기억 하시나요?

 

 

  30대를 눈앞에 둔 지금 나에게 필요한 것은 목표의식과 도전의식을 심어주고 성장할 수 있는 새로운 환경인 것 같습니다. 조금 더 나의 열정을 불태우게 만드는 일을 하고 싶어요. 부끄럽지만 그게 무엇인지는 아직 확실히 하지 못했습니다. 아무것도 정해진 바 없이, 심지어 꼭 하고 싶은 다른 일을 확정하지도 못한 채로 의원면직을 저질렀어요. 누군가는 나를 멍청하다고 생각할 수도 있겠지만 이상하게 후회는 안 돼요. 

 

 

 

 

  다음 편으로는 일을 그만둔 젊은 공립 초등교사의 장래 고민, 나의 적성과 새로운 커리어 방향을 뒤적이는 글을 써보도록 하겠습니다.

 

 

 

 

 

https://brunch.co.kr/@suriring/37

 

학교를 그만둔 이유

초등교사 의원면직 02 | 일을 관둔다고 했을 때 모든 사람이 공통적으로 물었습니다. 학교 일이 그렇게 안 맞아요? 그러면 나는 답했습니다. 아뇨, 일은 정말 재밌었어요. 배운 것도 많고요. 더 이

brunch.co.kr

 

 
 

 

 

  오늘 마지막 출근을 했습니다. 짐을 정리하고 인수인계를 하느라 조금 더 바빴을 뿐 평소와 다를 것 없는 보통의 하루였습니다.

 

 

 

 

 

  임용고시를 통과한 정교사가 사직을 하는 일은 가뭄에 콩 나듯 드물어요. 일을 그만둔다고 해도 교대 졸업장으로 할 수 있는 뾰족한 다른 일이 있는 것도 아니거니와, 마음이 바뀌어 복직을 하려면 임용고시를 다시 봐야 하기 때문입니다. 누구도 그런 모험을 하고 싶어 하지 않습니다. 특히 말 잘 듣는 교사들이라면 더욱요.

 

 

 

  나도 남들과 다를 바 없는 같은 고민을 했습니다. 일을 그만두고 후회하지 않을 수 있을까? 이만한 직업이 또 있을까? 내가 다른 일을 한다면 뭘 할 수 있을까? 치열한 고민 끝에 내린 결론은 결국 사직이었습니다. 학교를 떠나 후회하는 것도 무섭지만, 그게 무서워 학교에 평생 갇혀 있는 삶은 더 무서웠습니다. 그렇다면 떠나는 게 맞겠죠. 오히려 결심을 하고 나니 모든 게 선명해집니다.

 

 

 

  결정을 내리는 데에는 부모님과의 대화가 가장 큰 도움이 되었습니다. 아빠는 직장 생활을 하는 평생 동안 가슴 한편에 '내 일'을 하는 꿈을 꾸었다고 했습니다. 그러나 부양해야 할 가족이 있어 쉽게 결단을 내리지 못했다고. 그러니 젊고 자유로운 나는 무엇이든 새로 도전해도 된다고, 늦었다고 생각하지 말라며 차분하고 진지하게 말해주셨습니다. 서른이나 된 나를 애기라고 하셨어요. 아빠의 경험에서 우러나온 진심 어린 이야기들이 나에게는 아주 커다란 위로와 용기가 되었습니다.

 

 

 

 

 

 

 

 

  그렇게 나는 안정적인 삶을 포기하고 불투명한 미래를 선택하기로 했습니다. 내 앞에 펼쳐질 모든 고난과 역경을 온몸으로 받아들일 마음의 준비를 기꺼이 하면서요. 긴장도 되고 설레기도 합니다. 불안하지 않다면 거짓말이겠지만, 옳은 선택을 했다는 믿음에는 변함이 없습니다.

 

 

 

  앞으로 우여곡절이 있을 때마다 아이들과 쌓은 예쁜 추억들을 하나씩 꺼내어 보면서 힘을 내어 보도록 할게요.

 

 

+ Recent posts