본문 바로가기
카테고리 없음

문자열과 친해지기1

by 경자꿈사 2024. 10. 4.

오늘은 지금까지 사용했던 루비의 문자열에 대해 정리를 해보는 시간을 가지려고 한다.
문자열은 String 클래스의 인스턴스, 즉 객체인데 보통 new 메서드를 사용해서 생성하기보다는 "Ruby"처럼 그냥 리터럴 방식으로 생성하여 사용한다.

문자열은 말 그대로 '문자'들이 연속적으로 나열된 것으로서 '문자'를 요소로 갖는 배열과 유사한 형태로 볼 수 있다.
그래서인지, 문자열과 배열은 서로 같은(또는 유사한) 기능을 갖는 같은 이름의 메서드들이 여럿 있다.

우선 문자열의 길이를 알려주는 length 메서드와 size 메서드가 있는데, 배열 역시 같은 의미의 length 메서드와 size 메서드를 가지고 있다.
size 메서드는 length 메서드의 별칭(alias)으로 두 메서드는 같은 기능을 하므로 어느 것을 사용해도 상관없다.

루비에서 alias 키워드를 사용해서 어떤 메서드의 별칭을 만들 수가 있는데 아래 그림을 보면 Foo 클래스의 인스턴스 메서드인 foo에 대해 say_hello 라는 별칭을 만들고 있다.
Foo.instance_methods(false)를 사용하여 Foo 클래스가 정의한 인스턴스 메서드 목록을 보면 alias를 사용하여 별칭으로 만든 say_hello 메서드도 포함되어 있는 것을 볼 수 있다.
당연히 Foo 객체에 대해 say_hello 메서드를 호출하면 hello 메서드와 동일한 동작을 하게 된다.

어떤 메서드에 별칭을 붙여 다른 이름으로도 호출할 수 있도록 하면, 코드가 좀 더 자연스럽게 읽히도록 만드는 이름의 메서드를 상황에 따라 선택해서 사용할 수 있다는 장점이 있다.
그리고 메서드의 이름을 변경하지 않고도 메서드의 기능을 확장할 수가 있는데, 이것에 대해서는 다른 글에서 조금 자세히 다루어 보려고 한다.
그러나 모든 것이 지나치면 오히려 해가 되듯, 하나의 메서드에 너무 많은 별칭을 붙이는 것은 바람직하지 않다. 
코드 작성 시 어떤 이름의 메서드를 사용해야 할지 고민하게 만들거나(나는 length 와 size 둘 중에서도 가끔 고민을 한다^^), 한 프로그램의 소스 안에서 아무런 규칙 없이 여기저기서 다른 이름의 메서드를 사용하여 오히려 가독성을 떨어뜨리게 만들 수도 있다.

문자열의 length 메서드는 문자열을 구성하는 문자가 영어 알파벳이든 아라비아 숫자든 한글이든 상관없이 그냥 문자 하나를 1로 계산하여 결과를 돌려주지만, bytesize 메서드를 사용하면 해당 문자열이 실제 컴퓨터 상에서 처리될 때 표현되는 바이트 값들의 크기를 계산해 돌려준다. 그리고 그러한 바이트 값들을 bytes 메서드를 통해 확인할 수 있다.
아래 그림을 보면 문자열 "Ruby"는 영어 알파벳 문자로만 이루어져 있는데, 영어 알파벳 문자 하나는 컴퓨터에서 1바트 크기의 값으로 처리되므로 size 메서드의 결괏값과 bytesize의 결괏값이 서로 같은 걸 볼 수 있다.
그 아래 "Ruby루비" 문자열에는 영어와 한글이 함께 포함되어 있고, size 메서드는 6을 반환하지만 bytesize 메서드는 10을 반환하는 걸 볼 수 있다.
bytes 메서드의 결과를 보면 'Ruby'에 대한 바이트 값(82, 117, 98, 121)은 앞서 봤던 "Ruby" 문자열에 대한 bytes 메서드의 결과와 동일함을 알 수 있고, 한글 '루비'는 6개의 바이트 값으로 이루어져 있음을 알 수 있다.
encoding 메서드는 해당 문자열의 인코딩 정보를 알려주는데, str이 참조하는 문자열 "Ruby루비"의 현재 인코딩이 UTF-8 임을 알 수 있다.
즉, 컴퓨터 상에서의 처리를 위해 한글 '루비'는 UTF-8 이라는 인코딩 체계에 따라 6개의 바이트 값으로 변환된다고 보면 된다.
실제, 파일로 저장할 때도 irb에서 봤던 바이트 값들로 저장이 되는지 직접 확인해 보도록 하자.

아래 그림처럼 먼저, File 객체의 write 메서드를 사용하여 문자열 "Ruby루비" 를 파일에 써 보자.

write 메서드는 결괏값으로 파일에 쓴 바이트 크기를 돌려주는데, irb 창에 10이 찍힌 걸 볼 수 있다.

puts 메서드는 파일에 쓸 때 대상 문자열과 함께 개행 문자를 같이 기록하기 때문에, 여기서는 print 메서드를 사용하였다.

이제 파일의 내용을 16진수 표현으로 볼 수 있는 문서 편집기에서 열어 보자. 아래 그림은 Notepad++ 라는 편집기에서 제공하는 기능을 통해 파일을 열어 본 화면이다.

우측에 '루비' 라는 한글이 깨져서 보이긴 하지만, 일반 텍스트 모드로 보게 되면 문자열 "Ruby루비" 가 잘 저장된 게 보인다.

그림을 보면 앞서 bytes 메서드의 결괏값과 다른 값들이 보이는데, bytes 메서드는 바이트의 값을 10진수로 보여주기 때문이다.
10진수를 16진수로 나타낸 값을 알고 싶으면 해당 정수에 대해 16을 인수로하여 to_s 메서드를 호출하면 된다.
아래 그림처럼 "Ruby루비" 문자열의 bytes 메서드 결괏값을 16진수로 표현해 보면 실제 앞의 Notepad++ 그림에서 보이는 값과 동일함을 알 수 있다.

문자열이 빈 문자열인지를 확인하려면 length 메서드나 size 메서드의 결괏값이 0인지를 검사해도 되지만, 그냥 쉽게 

empty? 메서드를 사용해도 된다.

배열에도 empty? 메서드가 있는데, 배열에는 그에 더해 요소가 하나 이상 있는지를 알려주는 any? 라는 메서드도 있다.
any? 메서드는 인수 없이 호출하면 nil 이나 false가 아닌 요소가 하나라도 있으면 true를 반환해 주고, 인수를 주고 호출하면 (인수가 nil이나 false라 할지라도) 요소 중에 인수와 같은 값이 하나라도 있으면 true를 반환한다.
그리고 블록을 주고 호출하게 되면 블록의 결괏값이 참이되는 요소가 하나라도 있으면 true를 반환한다.

배열에서 특정 위치의 요소를 참조할 때 0부터 시작하는 값인 인덱스를 인수로 하여 연산자 메서드 []를 주로 사용하는데, 문자열 역시 연산자 메서드 []를 사용하여 특정 인덱스의 문자를 참조할 수 있다.
그리고 []에 두 번째 인수를 지정하여 특정 인덱스부터 원하는 길이의 부분 문자열을 가져올 수 있고, 또한 인덱스 범위(Range 객체)를 사용해서도 부분 문자열을 가져올 수 있다.

아래 그림에서 보이는 것처럼 slice 메서드를 사용해서도 [] 메서드와 동일하게 원하는 위치의 문자 또는 부분 문자열을 가져올 수 있다.
그런데, [] 메서드와 slice 메서드는 대상 문자열을 변경하지 않고 부분 문자열을 찾아서 돌려주는 반면에 slice! 메서드는 부분 문자열을 돌려주는 것은 같지만 대상 문자열에서 해당 부분 문자열을 제거한다.
메서드 이름 끝에 !가 붙은 메서드들은 해당 메서드의 동작 방식을 잘 확인한 후에 사용하도록 하자!

아래 그림에서 보이는 것처럼 문자열도 배열과 마찬가지로 연산자 메서드 []= 을 사용하여 문자열을 수정할 수 있는데 하나씩 살펴 보도록 하자.
문자열 "Ruby"를 참조하는 변수 str에 대해 str[0] = "ABC" 을 실행하면 str변수가 참조하는 문자열은 인덱스 0의 문자 'R'이 'ABC'로 바뀌어 문자열 "ABCuby" 가 된다.
그다음 str[1, 2] = "xyz" 을 실행하면 인덱스 1부터 길이가 2인 부분 문자열 'BC'가 'xyz'로 바뀌어 문자열 "Axyzuby"가 되고, 다시 str[1, 3] = "" 을 실행하면 인덱스 1부터 길이가 3인 부분 문자열 'xyz'가 ''로 바뀌어 문자열 "Auby"가 된다.
마지막으로 str[1..-1] = "BC" 을 실행하면 인덱스 1부터 마지막까지에 해당하는 부분 문자열 'uby'가 'BC'로 바뀌어 str변수가 참조하는 문자열은 "ABC"가 된다.


특히, 문자열의 앞과 끝에 다른 문자열을 추가하고 싶을 때는 []= 메서드를 사용해도 되지만, 어떠한 기능인지 메서드 이름만으로도 명확히 알 수 있는 prepend 나 concat 메서드를 사용하는 것도 괜찮은 방법이다.
그리고 연산자 메서드 << 을 사용하면 코드도 간결해지고, 의미도 이해하기 쉽기 때문에 문자열 뒤에 다른 문자열을 이어 붙일 때는 << 을 기본으로 사용해도 좋을 것 같다.

그리고 문자열의 특정 위치에 다른 문자열을 삽입하고 싶을 때도 []= 메서드를 사용해도 되지만, 아래 그림처럼 insert라는 전용 메서드를 사용하는 것도 괜찮은 방법인다.
코드를 어떤 스타일로 작성할지는 (혼자든 누구와 함께든 상관없이) 규칙을 정하고 가능한 규칙 그대로 일관되게 작성하면 된다.

이번 글은 여기서 마무리하고 다음 글에서 문자열의 또 다른 유용한 메서드들을 더 살펴보도록 하겠다.

See you again~~