비트마스크란 아시다시피 정수를 이진수로 나타내서 연산하는 방식입니다.
장점으로는 다음과 같습니다.
- 다른 자료 구조에 비해 수행 시간이 더 빠르다.
- 비트 연산자를 사용하여 코드가 더 간결해 진다.
(가독성은 떨어지지 않나...)- 비트마스크를 사용하여 더 작은 메모리로 사용할 수 있다.
이제 비트 연산자는 어떤게 있는지 간략히 알아봅시다.
AND 연산 (a & b) = 둘 다 켜져있을 경우
OR 연산 (a | b) = 둘 중 하나라도 켜져있는 경우
XOR 연산 (a ^ b) = 둘 중 하나만 켜져있는 경우
NOT 연산 (~a) = 켜져있는건 끄고, 꺼진건 켠다.
SHIFT 연산 (a>>1) = 비트들을 왼쪽 혹은 오른쪽으로 원하는 만큼 움직인다.
이제 다들 새록새록 기억나셨을 겁니다. (설명이 부족하다면 구굴링을...)
자 이제 얼른 응용해봅시다!
어떤 한 사용자가 이번주 수요일, 토요일, 일요일에 약속이 있다고 합니다.
직장에 연차를 쓰고 가야하는데 직장 휴뮤일은 토요일, 일요일이고 이때는 따로 연차를 쓸 필요가 없습니다.
간단히 보자면
약속 날 - 수, 토, 일
직장 휴무일 - 토, 일
여기서 어떤 요일에 연차를 써야하고 총 무슨 요일에 쉬게될지를 구해야합니다.
이럴 경우 비트마스크로 풀이하면 편리합니다.
일단 요일에 대한 enum값을 다음과 같이 작성해 줍니다.
enum Schedule: UInt, CaseIterable {
case monday = 0b0000001
case tuesday = 0b0000010
case wednesday = 0b0000100
case thursday = 0b0001000
case friday = 0b0010000
case saturday = 0b0100000
case sunday = 0b1000000
var string: String {
switch self {
case .monday: return "월요일"
case .tuesday: return "화요일"
case .wednesday: return "수요일"
case .thursday: return "목요일"
case .friday: return "금요일"
case .saturday: return "토요일"
case .sunday: return "일요일"
}
}
}
print(Schedule.monday.rawValue) // 1
print(Schedule.tuesday.rawValue) // 2
print(Schedule.wednesday.rawValue) // 4
print(Schedule.thursday.rawValue) // 8
print(Schedule.friday.rawValue) // 16
print(Schedule.saturday.rawValue) // 32
print(Schedule.sunday.rawValue) // 64
어떤 요일에 연차를 써야하고 총 무슨 요일에 쉬게될지에 대한 풀이는 다음과 같습니다.
let workClosed = Schedule.saturday.rawValue | Schedule.sunday.rawValue // 직장 휴무일
let myAppointment = Schedule.wednesday.rawValue | Schedule.saturday.rawValue | Schedule.sunday.rawValue // 나의 약속
let holidayReport = myAppointment - workClosed
let noWorkDays = workClosed | myAppointment
for week in Schedule.allCases {
if (holidayReport & week.rawValue) > 0 {
print("\(week.string)에는 연차를 쓰셔야 합니다.")
}
if (noWorkDays & week.rawValue) > 0 {
print("\(week.string)에는 일하지 말고 푹 쉬십쇼.")
}
}
결과
수요일에는 연차를 쓰셔야 합니다.
수요일에는 일하지 말고 푹 쉬십쇼.
토요일에는 일하지 말고 푹 쉬십쇼.
일요일에는 일하지 말고 푹 쉬십쇼.
이렇게 간편하고 쉽게 결과를 뽑아낼 수 있습니다.
코딩테스트에만 쓰이는거 아냐? 실무에서는 안쓰일거 같은데?
충분히 그렇게 생각하실 수도 있습니다.
비트마스크를 사용하면 메모리 절약이나 퍼포먼스가 높아지긴하겠지만 실무에서 나오는 대부분의 경우에는 큰 차이를 보일 경우는 많지 않을 것이고
코드 가독성에도 그리 좋지 않기 때문입니다.
하지만 몇몇 꼭 필요할 경우가 있습니다.
- 방대한 처리일 경우
- 통신 시 과도한 페이로드 절약
- 테이블 설계 시 제1정규형에 위반하지 않기위해
1번은 뭐 당연한거고...
2번도 뭐 알것 같기도하고...
3번은 ???
1번 설명은 스킵하고 2번부터 설명하겠습니다.
위에서 설명했던 예제로 설명 드리자면
사용자가 쉬어야할 날을 서버로 보낼 때
{ holiday: ["월", "화", "수", "목", "금", "토", "일"] }
대강 이런식일겁니다.
이걸 비트마스크 방식으로 보낸다면?
{ holiday: 127 }
딱봐도 엄청난 페이로드 절약입니다.
요즘 폰이 좋아서 자잘한 메모리 관리는 안한다고 쳐도 통신 데이터 관리는 해야겠죠?
제1정규형이 뭐지?!
이런식으로 테이블 셀에 여러개의 데이터를 가지는 경우를 하지 말자는 내용입니다.
이런 경우에는 holiday라는 테이블을 하나 더 생성해서 처리하고는 합니다.
근데 겨우 이것때문에 테이블 하나 더 생성하는 것도 좀 귀찮고...
join해서 처리하는 것도 쿼리 성능에 좀 문제가 생기고 난감합니다.
이럴 때 비트마스크를 사용해줍니다!
아주 깔끔하고 보기 좋습니다.
비트마스크 값의 파싱은 서버에서하지 말고
클라이언트 단에서 그대로 받아서 파싱해주는편이 더 좋겠죠?
그래서 저희도 비트마스크에 대해서 알아둬야하는 겁니다!
이상 비트마스크 응용에 대한 설명이었습니다~ 🙌