THE DEVLOG

scribbly.

CS공부: 데이터 스트럭처

2023.03.22 10:13:30

몇몇 코딩테스트에서 비트연산자, 문자열조작 등 기본적인 언어 사용법이 나와서 정리할 필요가 있어 정리한다.

비트 연산자

@nulbo [TIL / JavaScript] 비트 연산자에 잘 정리되어 있다.

비트의 출력

num.toString(2)을 이용하면 2진법의 비트로 이루어진 문자열을 반환한다. (원본을 변환하지 않는다.)

let num = 10;
console.log(num.toString(2)); //1010

이러한 비트를 직접 조작하는 연산이 비트연산자이다.

비트의 입력

한편 숫자형에 '0b'라는 접두사를 붙이면 숫자를 이진수로 입력할 수 있다.

let num = 0b1010;
console.log(num); //10

2진수(binary): 0b (숫자 0과 알파벳 b)
8진수(octal): 0o (숫자 0과 알파벳 o)
16진수(hexadeciaml): 0x (숫자 0과 알파벳 x)
출처

즉 0b 접두사와 toString(2)를 이용하면 비트로 입력 및 출력할 수 있다. (0b접두사를 붙여서 출력하는 법? 그런 건 없다. ㅇㅇ)

let num = 0b1010;
console.log(num.toString(2)); //1010

논리 연산자 (AND, OR, XOR, NOT)

& (AND)

두 비트 모두 1이면 결과는 1, 그 외에는 0이 됩니다.
0101
0011
-> 0001

let a = 0b0101; //5
let b = 0b0011; //3

let res = a & b;
console.log(res.toString(2).padStart(4, 0)); // 0001
console.log(res); // 1

| (OR)

두 비트 모두 1이면 결과는 1, 그 외에는 0이 됩니다.
0101
0011
-> 0111

let a = 0b0101; //5
let b = 0b0011; //3

let res = a | b;
console.log(res.toString(2).padStart(4, 0)); // 0111
console.log(res); // 7

^ (XOR)

두 비트가 다르면 1, 같으면 0

0101
0011
-> 0110

let a = 0b0101; //5
let b = 0b0011; //3

let res = a ^ b;
console.log(res.toString(2).padStart(4, 0)); // 0110
console.log(res); // 6

~ (NOT)

부정 연산자이다. 0과 1을 뒤집는다.
비트 연산자가 32비트로 이루어진다는 점을 이용하여 문자나 소수를 정수로 표현하는 데에 쓰기도 하며, 'Number(str)'보다 연산 속도가 빠르다.

let a = 1b0101; //5
let res = ~a;
console.log(res.toString(2)); // -110
console.log(res); // -6

let b = 0b0011; //3
res = ~b;
console.log(res.toString(2)); // -100
console.log(res); // -4

let str = "30";
console.log(typeof ~~str); //number

시프트 연산자와 바이너리 서치

과거에 적어놓았던 내용을 복-붙

부분집합. {O|X, O|X, O|X, O|X} 의 모든 부분집합을 구하려면?

  • 비트 연산자
    a = 60, b = 13 이라 가정한다.
    a = 0011 1100
    b = 0000 1101

& AND 연산. 둘다 참일때만 만족 (a & b) = 12 → 0000 1100
| OR 연산. 둘 중 하나만 참이여도 만족 (a | b) = 61 → 0011 1101
^ XOR 연산. 둘 중 하나만 참일 때 만족 (a ^ b) = 49 → 0011 0001
~ 보수 연산. (~a) = -61 → 1100 0011
<< 왼쪽 시프트 연산자. 변수의 값을 왼쪽으로 지정된 비트 수 만큼 이동(끝에 0이 하나 더 붙는 거임) a << 2 = 240 → 1111 0000
>> 오른쪽 시프트 연산자. 변수의 값을 오른쪽으로 지정된 비트 수 만큼 이동 a >> 2 = 15 → 0000 1111

이번에는 시프트(shift) 연산을 알아보겠습니다.

먼저 왼쪽 시프트(<<)입니다.

>>> bin(0b1 << 1)
'0b10'
>>> bin(0b1 << 2)
'0b100'
>>> bin(0b1 << 3)
'0b1000'
위에서 1이 점점 왼쪽으로 이동하는 것을 볼 수 있습니다.

다음은 오른쪽 시프트(>>)입니다.

>>> bin(0b1010 >> 1)
'0b101'
1이 오른쪽으로 한 자리씩 밀린 것을 볼 수 있습니다. 더 많이 시프트하면 어떻게 될까요?

>>> bin(0b1010 >> 2)
'0b10'
>>> bin(0b1010 >> 3)
'0b1'
>>> bin(0b1010 >> 4)
'0b0'

점점 밀려서 결국 0이 되었네요.


시프트 연산자로 왼쪽으로 한 칸 이동할 때 마다 2배가 되는 효과가 생긴다.
반대로 오른쪽으로 한 칸씩 이동하면 숫자가 줄어든다.
그럼 2를 곱하고 나누면 되지 않나?라고 생각할 수 있는데, 큰 수에서 비트단위로 코드를 짜야하는 경우가 생기는데, 이 때 시프트연산자를 사용하면 큰 생각없이 비트들을 정리할 수 있어서 편하다.

1 << n : 비트 원소가 n개일 때 부분집합의 갯수. 즉 2의 n승

i&(1<<j): i가 10110일때 1<<0, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5 는 1, 10, 100, 1000, 10000, 100000 (0이 늘어난다. 즉 2배씩 늘어난다.) 이고, 이를 &으로 대입하면 0, 10, 100, 0, 1000을 반환한다. 이를 불리언 값으로 대입하면 f, t, t, f, t가 나온다. 이를 활용해 '1이 특정 자릿수에 있는지'를 판단할 수 있게 된다.

print(bin(5)) # 0101
print(bin(14)) # 1110
print(bin(5 & 14)) # 0100 -> 4

정규 표현식

정규표현식을 사용하는 String의 인스턴스 메서드

  • match(정규표현식) : 문자열에서 정규표현식을 충족하는 문자들을 배열로 반환한다. 없는 경우 빈 배열을 반환한다. let test = 'ABC is UpperCase of "Abc" or "abc"';, console.log(test.match(/[A-C]/g)); //['A', 'B', 'C', 'C', 'A']
  • search(정규표현식): 문자열에서 정규표현식을 충족하는 첫번째 문자를 반환한다. 없는 경우 -1을 반환한다.
  • replace(regexp|substr, newSubstr|function) : 특정 패턴을 가장 앞 하나 새로운 패턴으로 바꾸어서 새로운 문자열을 반환합니다.
  • replaceAll(regexp|substr, newSubstr|function) : 특정 패턴을 모두 새로운 패턴으로 바꾸어서 새로운 문자열을 반환합니다.

RegExp

RegExp()로 새로운 정규표현식 객체를 만들 수 있다.
/패턴/플래그의 조합, /^abc/gi으로 구성된다.
const regexp1 = new RegExp(/^abc/i); // ES6
const regexp2 = new RegExp(/^abc/, 'i');

문자 클래스

https://ko.javascript.info/regexp-character-classes
\d : 0~9사이의 문자
\s : 공백. 스페이스, \t, \n, \v, \f, \r
\w : \a-z\, \A-Z\, _ 포함

\D: 숫자가 아닌 문자
\S: 공백이 아닌 문자
\W: 비 라틴 문자나 공백 문자 등 특수문자

패턴

위치

^패턴 : 해당 패턴으로 시작할때 매치
패턴$ : 해당 패턴으로 끝날 때 매치
\특수문자 : 이스케이프. \ 뒤의 특수 문자를 정규표현식이 아닌 일반 문자로 인식
. : 모든 문자
[문자들] : 대괄호가 문자 하나를 의미. 예를 들어, [A-C][0-3][g-i]는 A0g, A0h, B1i, C3g 등의 문자열과 일치합니다.
시작문자-끝문자 : 문자의 범위. 가령 [A-C]는 A부터 C
^ : 부정을 의미. [^A-C]는 A부터 Z를 제외한 모든 문자.
(서브1|서브2) : 소괄호 안에는 파이프로 분리한 문자들을 넣을 수 있음.

수량자

* : 별표 앞 문자가 패턴에 없을 수도 있고 여러개 있을 수도 있음. 가령 a*baaab, abb와도 일치함.
+ : 플러스 앞 문자가 패턴에 하나 이상 있음. 가령 a+baaab, ab와는 일치하지만 b와는 일치하지 않음.
? : 물음표 앞 문자가 패턴에 하나 이하 있음. 가령 a?bb, ab와는 일치하지만 aab와는 일치하지 않음.
{} : 중괄호 안에 있는 숫자의 범위만큼 일치. [a-z]{3,}는 소문자가 3개 이상, [abc]{1,2}는 a 혹은 b 혹은 c가 1개 혹은 2개 연결됨.

*, +, {n, }는 탐욕적 수량자이다. 최대한 많은 문자를 모두 찾아낸다.
탐욕적 수량자?는 게으른 수량자이다. 최소한 적게 일치하는 패턴. 가령 r.*은 r로 시작하는 최대한 긴 문장이지만, r.*?은 r로 시작하는 가장 짧은 문자. 즉 가장 처음만나는 r을 의미한다.

플래그

i : 대소문자 구분 없음
g : 패턴과 일치하는 모든 값을 검색
m : 다중행 모드. 템플릿 리터럴에서 유용함.
s : \n.문자에 포함될 수 있도록.
u : 유니코드
y : sticky