원형 버퍼, 순환 버퍼, 링 버퍼는 하나의 고정 크기 버퍼를 마치 끝과 끝이 연결된 것처럼 사용하는 자료 구조예요.
원형 버퍼는 처음에는 비어 있고, 미리 정해진 길이를 가져요. 예를 들어 다음은 요소 7개짜리 버퍼예요:
[ ][ ][ ][ ][ ][ ][ ]
버퍼 가운데에 1을 쓴다고 해봐요. (원형 버퍼에서는 정확히 어디에서 시작하는지는 중요하지 않아요.)
[ ][ ][ ][1][ ][ ][ ]
그다음 요소 두 개, 2와 3을 1 뒤에 이어 붙인다고 해봐요:
[ ][ ][ ][1][2][3][ ]
이때 버퍼에서 요소 두 개를 꺼내면, 버퍼 안에서 가장 오래된 값이 꺼내져요. 이 경우 꺼내지는 두 요소는 1과 2이고, 버퍼에는 3만 남아요:
[ ][ ][ ][ ][ ][3][ ]
버퍼에 요소가 7개 있으면 버퍼는 완전히 가득 찬 상태예요:
[5][6][7][8][9][3][4]
버퍼가 가득 차면 오류가 발생해서, 빈 자리가 생길 때까지 더 이상 쓸 수 없다는 것을 클라이언트에 알려줘요.
버퍼가 가득 찼을 때 클라이언트는 강제 쓰기로 가장 오래된 데이터를 덮어쓸 수도 있어요. 이 경우 요소 두 개, A와 B를 추가하면 3과 4를 덮어써요:
[5][6][7][8][9][A][B]
3과 4가 A와 B로 바뀌었으니 이제 버퍼에서 가장 오래된 데이터는 5예요. 마지막으로 요소 두 개를 꺼내면 5와 6이 반환되면서 버퍼는 이렇게 돼요:
[ ][ ][7][8][9][A][B]
빈 공간이 있기 때문에 클라이언트가 다시 덮어쓰기로 C와 D를 저장하면, 7과 8이 있는 자리가 아니라 이전에 5와 6이 저장되어 있던 자리가 사용돼요. 7은 여전히 가장 오래된 요소이고, 버퍼는 다시 가득 찼어요.
[C][D][7][8][9][A][B]
코드에서 예외를 직접 정의하고 raise해야 할 때가 있어요. 이럴 때는 오류의 원인이 무엇인지 알려 주는 의미 있는 오류 메시지를 항상 포함해야 해요. 그래야 코드를 더 읽기 쉽고, 디버깅할 때도 훨씬 도움이 돼요.
사용자 정의 예외는 새로운 예외 클래스를 만들어서 만들 수 있는데, 이런 클래스는 보통 Exception의 하위 클래스예요(자세한 내용은 classes를 참고해요).
오류의 원인이 특정 예외 유형에서 파생된 것임을 알고 있는 경우에는, Exception 클래스 아래에 있는 built in error types 중 하나를 상속받도록 선택할 수 있어요. 오류를 발생시킬 때도 의미 있는 메시지는 여전히 포함해야 해요.
이 연습 문제에서는 사용자 정의 예외 두 개를 만들어야 해요. 하나는 원형 버퍼가 가득 찼을 때 발생시키거나 "던지는" 예외이고, 다른 하나는 비어 있을 때를 위한 예외예요. 적절한 예외를 직접 정의하고, 그 예외를 raise하고, 적절한 오류 메시지를 포함해야만 테스트를 통과할 수 있어요.
built-in exception을 직접 정의하려면, 그 예외를 상속받는 class를 만들어요. 사용자 정의 예외를 메시지와 함께 발생시킬 때는, 메시지를 exception 타입의 인자로 적어요:
# subclassing the built-in BufferError to create BufferFullException
class BufferFullException(BufferError):
"""Exception raised when CircularBuffer is full.
message: explanation of the error.
"""
def __init__(self, message):
self.message = message
# raising a BufferFullException
raise BufferFullException("Circular buffer is full")
Exercism에 가입하고 Python 트랙을 개념 17개연습 문제 146개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.
이 영상에서는 원형 버퍼를 살펴봐요. 원형 버퍼가 무엇인지, 어디에 쓰이는지, 어떤 구현 방식들이 있는지 알아봐요. 큐, 정적 배열과 동적 배열, 불변 자료 구조, 그리고 재미있는 에이전트 기반 구현까지 다뤄요.