Sister Nosilv story

[TIL 19th] 벡터의 주소를 가리키는 포인터를 사용하면 낭패본다

by 노실언니

[문제1] 숫자와 숫자영단어가 섞여있는 문자열을 숫자로 변환하여 리턴하기

[1. 내 풀이] case, find, replace 사용

#include <string>
using namespace std;

int solution(string s) {
    switch (s.size()){
        case 1:
        case 2:
            return stoi(s);
        case 3:
            if(s[2]>'1' && s[2]<'9') return stoi(s);
            else if(s[2]=='o') return 1;
            else if(s[2]=='t') return 2;
            else if(s[2]=='s') return 6;
        default:
            size_t idx;
            while(true){
                idx = s.find("zero");
                if(idx==string::npos) break;
                s.replace(idx,4,"0");
            }
            while(true){
                idx = s.find("one");
                if(idx==string::npos) break;
                s.replace(idx,3,"1");
            }
            while(true){
                idx = s.find("two");
                if(idx==string::npos) break;
                s.replace(idx,3,"2");
            }
            while(true){
                idx = s.find("three");
                if(idx==string::npos) break;
                s.replace(idx,5,"3");
            }
            while(true){
                idx = s.find("four");
                if(idx==string::npos) break;
                s.replace(idx,4,"4");
            }
            while(true){
                idx = s.find("five");
                if(idx==string::npos) break;
                s.replace(idx,4,"5");
            }
            while(true){
                idx = s.find("six");
                if(idx==string::npos) break;
                s.replace(idx,3,"6");
            }
            while(true){
                idx = s.find("seven");
                if(idx==string::npos) break;
                s.replace(idx,5,"7");
            }
            while(true){
                idx = s.find("eight");
                if(idx==string::npos) break;
                s.replace(idx,5,"8");
            }
            while(true){
                idx = s.find("nine");
                if(idx==string::npos) break;
                s.replace(idx,4,"9");
            }
    }
    return stoi(s);
}

길이가 1이거나 2인 문자열은 반드시 숫자만 있기 때문에 case문으로 선처리했다.

특정 영단어가 없어질 때까지 영단어를 숫자로 변환하는 과정을 zero~nine 까지 총 10묶음 반복하는 코드이다.

[1. 내 풀이(개선)] case, find, replace 사용 + array 와 index 활용

zero~nine을 array로 저장하면 array[0] = "zero" ~ array[9] = "nine" 이기 때문에 열덩이의 while문을 하나의 for문으로 묶을 수 있겠다는 생각을 했다. 

#include <string>

using namespace std;

int solution(string s) {
    switch (s.size()){
        case 1:
        case 2:
            return stoi(s);
        case 3:
            if(s[2]>'1' && s[2]<'9') return stoi(s);
            else if(s[2]=='o') return 1;
            else if(s[2]=='t') return 2;
            else if(s[2]=='s') return 6;
        default:
            string engNum[] = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"};
            for(int i=0; i<10; i++){
                size_t idx;
                while((idx=s.find(engNum[i]))!=string::npos){
                    s.replace(idx,size(engNum[i]),to_string(i));
                }
            }
    }
    return stoi(s);
}

[2. 다른 사람의 풀이를 참조한 풀이] regex와 regex_replace 사용

#include <string>
#include <regex>

using namespace std;

int solution(string s) {
    switch (s.size()){
        case 1:
        case 2:
            return stoi(s);
        default:
            string engNum[] = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"};
            for(int i =0; i<10; i++){
                s=regex_replace(s,regex(engNum[i]),to_string(i));
            }
    }
    return stoi(s);
}

 

헤더 파일의 regex 는 정규표현식(Regular Expression)을 뜻한다.

Regular Expression은 문자열에서 특정 패턴을 검색, 매칭, 추출, 검증, 분리 또는 변환하기 위해 사용되는 특수한 식이다.

regex_replace(①검사 받을 문자열, ②특정 패턴, ③변환 문자열);

위 코드에 사용된 함수 regex_replace() 는 문자열① 중에 특정 패턴②이 있는 구간은 모조리 지정해둔 문자열③로 변환하는 함수이다.

#include <regex>
#include <string>

// regex_replace 의 원형
std::string regex_replace(const std::string& str, const std::regex& re, const std::string& replacement);

어떤 풀이가 효율적일까 : 내 풀이?

while( (size_t = s.find("zero")) != string::npos ) { s.replace(size_t, 4, "0"); } 이런 류의 식을

s = regex_replace(s, regex("zero"), "0") 이렇게 쓰면 되는 것이니까 코드상으로는 간단해졌다고 생각한다.

그렇지만,

 간단한 교체는 find와 replace를 사용하는 것이 더 효율적입니다.

 이 경우에는 정규식을 사용할 필요가 없고, 단순히 문자열을 찾아 바꾸는 작업만 필요하기 때문입니다.
 복잡한 패턴을 찾거나 변환하는 경우에는 regex_replace가 더 효율적일 수 있습니다.

 정규식을 이용한 복잡한 패턴을 한 번에 처리할 수 있기 때문입니다.

 그러나 정규식이 추가적인 컴파일 시간과 매칭 비용을 발생시킬 수 있다는 점을 고려해야 합니다.
 따라서 목적에 맞는 방법을 선택하는 것이 중요합니다. - ChatGPT

이런 답을 들으니 regex 계통 함수들은 추가 비용을 발생시키되 복잡한 패턴들을 찾고 싶을 때 효율적이라는 말로 들린다.

하지만, "zero" ~ "nine" 은 복잡한 패턴이 아니고, find, replace로 충분히 쉽게 찾을 수 있다.

그러므로 코드를 간단히 만드는 것은 2번 풀이(regex)이지만 패턴이 어렵지 않기때문에 이번 문제에서는 굳이 regex를 사용할 필요는 없다고 생각한다.

그렇지만 이런 문제가 등장했다는 것은 아무래도 regex를 활용해야 쉽게 풀리는 복잡한 패턴의 문제도 등장하지않을까?


[문제2] 문자열 내 마음대로 정렬하기

내 구상은 아래와 같다.

- 단어를 n을 기준으로 쪼개서 앞은 front에, 뒤는 back에 담기

- back 변수의 특별함 : 단어의 뒤쪽을 back에 담을 때, 해당 단어의 front를 가리키는 포인터도 만들어서 할당하기(linked-list처럼)

- back 속 단어들을 sort 함

- back 단어와 back의 포인터를 사용하여 해당 back에 해당하는 front를 이어붙이기

 

 

벡터를 가리키면 낭패본다

#include <string>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;

class BackPoint{
public:
    string backStr;
    string* frontPtr;
    BackPoint(string s, string* p) : backStr(s), frontPtr(p) {}
    void change(string s, string* p){
        this->backStr=s;
        this->frontPtr=p;
    }
};

bool compareByFirstColumn(const vector<string>& a, const vector<string>& b) {
    return a[0] < b[0];  // 첫 번째 원소 기준으로 오름차순 정렬
}


vector<string> solution(vector<string> strings, int n) {
    sort(strings.begin(), strings.end());   // 인덱스n의 문자가 동일한 경우 사전순이므로 선정렬함
    vector<string> frontStr;                // 앞 문자열을 저장해두는 곳
    
    vector<BackPoint> back;
    for(int i=0; i<strings.size(); i++){
        frontStr.push_back(strings[i].substr(0,n));
        back.push_back(BackPoint(strings[i].substr(n), &frontStr[i]));  // 뒷문자열과 자신의 앞 문자열을 가리키는 곳
        cout << "fS주소: "<<&(frontStr[i])<< endl;
        cout << (back[i].frontPtr) << *(back[i].frontPtr) << '+' << back[i].backStr<< endl;
    }
    for(auto i : frontStr) {cout <<i<<endl;}
    for(int i=0; i<frontStr.size(); i++) {cout << "fS주소: "<<&(frontStr[i])<< endl;}
    // 슬라이싱한 뒤 문자열을 기준으로 오름차순 정렬
    for(int i=0; i<back.size()-1; i++){
        for(int j=i+1; j<back.size(); j++){
            if(back[i].backStr>back[j].backStr){
                cout << back[i].frontPtr << *(back[i].frontPtr)<< '+'<< back[i].backStr << endl;
                BackPoint temp(back[i].backStr, back[i].frontPtr);
                cout << "temp" << *(back[i].frontPtr)<<'+'<<back[i].backStr << endl;
                back[i].change(back[j].backStr, back[j].frontPtr);
                back[j].change(temp.backStr, temp.frontPtr);
            }
        }
    }
    
    for(int i=0; i<strings.size(); i++){
        cout << (back[i].frontPtr) << *(back[i].frontPtr) << '+' << back[i].backStr<< endl;
    }
    
    
    vector<string> answer;
    for(int i=0; i<strings.size(); i++){
        cout << *(back[i].frontPtr) << '+' << back[i].backStr<< endl;
        answer.push_back(*(back[i].frontPtr) + back[i].backStr);
    }
    
    return answer;
}
반응형

블로그의 정보

노력하는 실버티어

노실언니

활동하기