본문 바로가기
코딩테스트 준비/프로그래머스

[프로그래머스][C++] 신규 아이디 추천

by 스테디코디스트 2023. 10. 20.
반응형

<문제 소개>


 

<소스 코드>

#include <string>
#include <vector>

using namespace std;

string solution(string new_id) 
{    
    for(int i = 0; i < new_id.size(); i++)
    {
        if(new_id[i] >= '0' && new_id[i] <= '9');
        else if(new_id[i] >= 'a' && new_id[i] <= 'z');   
        // 1단계. 대문자를 소문자로 치환
        else if(new_id[i] >= 'A' && new_id[i] <= 'Z')
        {
            new_id[i] = new_id[i] - 'A' + 'a';
        }
        // 2단계. 특수문자 제거('-', '_' '. 제외)
        else if(new_id[i] != '-' && new_id[i] != '_' && new_id[i] != '.')
        {            
            new_id.erase(new_id.begin() + i);
            i--;
            continue;
        }
    }    
    
    // 3단계. 마침표가 2번 이상 연속되는 경우 -> 1개로 압축
    for(int i = 0; i < new_id.size(); i++)
    {
        if(new_id[i] == '.')
        {                
            while(i + 1 < new_id.size())
            {                    
                // 마침표가 이어지는 경우
                if(new_id[i + 1] == '.')
                {
                    // 다음 마침표를 지움
                    new_id.erase(new_id.begin() + i + 1);
                    continue;
                }
                else break;                                         
            }            
                
            // 4단계. 마침표가 처음이나 끝에 위치한 경우
            if(i == 0 || i == new_id.size() - 1)
            {
                new_id.erase(new_id.begin() + i);
                i--;
                continue;
            }            
        }      
    }
    
    // 5단계. 빈 문자열인 경우
    if(new_id.size() == 0)
    {
        // a 대입
        new_id = "a";
    }
    
    // 6단계. 16자 이상인 경우
    if(new_id.size() >= 16)
    {
        string curID = new_id;
        new_id = ""; // 초기화
        
        // 15자리만 다시 넣어줌
        for(int i = 0; i < 15; i++)
        {
            new_id += curID[i];
        }
        
        // 끝에 마침표가 위치한 경우 
        // -> 2개이상 연속된 마침표는 앞서 지웠기 때문에 끝 부분에 연속으로 마침표가 나올 일은 없음        
        if(new_id[new_id.size() - 1] == '.')
        {
            // 마침표 삭제
            new_id.erase(new_id.end() - 1);
        }
        
    }
    
    // 7단계. 2자 이하인 경우
    if(new_id.size() <= 2)
    {
        // 마지막 단어
        char lastWord = new_id[new_id.size() - 1];
        
        // 길이가 3이 될때까지 반복해서 붙임
        while(new_id.size() != 3)
        {
            new_id += lastWord;
        }
    }
    
    return new_id;
}

 

<소스코드 2>

#include <string>
#include <string.h>

using namespace std;

string solution(string new_id) 
{
    for (char& ch : new_id) // new_id의 각 원소를 반복
    {
        // | 연산자 = OR 연산자
        // A와 a는 아스키코드 번호로 32차이가 남
        if ('A' <= ch && ch <= 'Z') ch |= 32; 
    }

    string ret; // 추천할 id
    
    for (char& ch: new_id) 
    {
        // 각 문자가 소문자이거나 숫자이거나 특수문자 -_. 중에 하나인 경우에만 ret에 추가
        if ('a' <= ch && ch <= 'z' || '0' <= ch && ch <= '9' || strchr("-_.", ch)) 
        {
            ret += ch;
        }
    }

    new_id = ret; // 다음 탐색을 위해 new_id를 바뀐 문자열 ret으로 바꿔줌
    ret.clear(); // ret은 초기화하고 다시 규칙을 검사한 후 넣음 -> 이 경우에 이후 과정에서 crash 발생 가능성이 있음
    
    for (char& ch: new_id) 
    {
        // ret이 비어있지 않고, 마지막문자가 마침표이고 현재 문자도 마침표인 경우라면 마침표 뒤에 마침표를 넣는 것임 -> 문자를 입력하지 않고 다음 반복으로 넘어감 
        if (!ret.empty() && ret.back() == '.' && ch == '.') continue;
        
        // 문자 입력
        ret += ch;
    }

    // 맨 앞이 마침표인 경우 삭제
    if (ret.front() == '.') ret.erase(ret.begin());
    
    // 맨 뒤가 마침표인 경우 삭제
    if (ret.back() == '.') ret.pop_back();

    // 빈 문자열인 경우 a 대입
    if (ret.empty()) ret = "a";
    
    // 16보다 크기가 큰 경우 15자로 만듦
    if (ret.size() >= 16) ret = ret.substr(0, 15);
    
    // 줄인 문자의 마지막이 마침표가 된 경우 해당 마침표를 지워줌
    if (ret.back() == '.') ret.pop_back();
    
    // 문자열의 크기가 2보다 작거나 같아질 때까지 가장 마지막 문자를 복붙
    while (ret.size() <= 2) ret += ret.back();

    return ret;
}

<풀이과정 1>

1. new_id의 각 원소를 돌면서 각 문자들을 확인한다.

2. 각 문자가 숫자, 소문자인 경우에는 넘어가고, 대문자인 경우에는 아스키코드의 특성을 이용해 소문자로 치환해준다.

3. 그 이외의 경우 중 '-', '_', '.' 이렇게 3가지 특수문자를 제외한 나머지 문자들이 나오는 경우에 erase를 이용해 해당 문자들을 제거해 주고, erase로 인해 반복자 i가 원래 문자가 아닌 다음 문자를 가리키고 있기 때문에, 다음 반복으로 넘어갈 때 다음 문자를 가리킬 수 있도록 1을 감소시켜준다.

4. 이렇게 1, 2단계의 과정을 마치고 for문이 완료되면 다시 같은 for문을 사용하고 3,4단계를 수행한다.

5. 따로따로 진행하는 이유는 같은 for문에서 한 단어씩 진행하게 되면, 예를들어 다음 특수문자가 지워지면 이전의 마침표가 마지막 문자가 되지만 이전 마침표는 이미 검사가 끝났으므로 지워지지 않을 수 있다. 이 경우와 같이 1,2 단계를 마치지않고 3,4단계를 진행하면 예외사항이 발생할 수 있기 때문에 이를 방지하기 위해 따로 진행하였다.

6. 다시 돌아와서 앞선 for문을 마쳐서 특수문자를 모두 지운 이후, 다시 for문을 돌면서 현재 검사하는 문자가 마침표인지 검사하고, 마침표인 경우에는 먼저 3단계인 다음 마침표인지 확인하는 작업을 진행한다.

7. 다음 단어가 문자열을 넘어가지 않을때까지 반복하면서 다음 단어가 마침표인지 확인하고 아니라면 반복을 빠져나간다.

8. 하지만 만약 다음 단어도 마침표라면 해당 마침표를 지우고, 다시 반복을 진행하여 다음이 마침표가 아닐때까지 반복하여 마침표가 2개 이상 연속되지 않도록 만들어준다.

9. 위의 과정을 마쳐서 연속되는 마침표를 하나로 만들어 주었다면 이제 해당 마침표의 자리를 파악하여 처음이나 끝인 경우라면 마침표를 삭제해준다. 이 때 이전 3번 과정에서처럼 삭제한 이후에는 다음 반복을 진행해야 하므로 다음 반복으로 넘어갈 때 다음 문자를 가리킬 수 있도록 반복자를 1만큼 감소시켜준다.

10. 위의 과정이 모두 끝나면 이제 5,6,7단계가 남았는데 이는 반복문을 빠져나온 뒤 각각 따로 if문을 이용해 진행해준다.

11. 5단계인 빈 문자열인 경우를 판단하기 위해 현재 문자열 new_id의 크기가 0이라면 빈 문자열이므로 이 경우에는 new_id에 a를 대입해준다.

12. 다음으로 6단계도 마찬가지로 new_id의 크기를 이용해 16자 이상인지 판단하였고, 임시 문자열 curID를 이용해 현재 new_id를 저장한 뒤 new_id를 초기화시키고 curID의 원소들을 돌면서 15번째 문자까지 순차적으로 넣어서 new_id를 15자로 만들어준다.

13. 그리고나서 15자의 마지막이 마침표인 경우가 남았기 때문에 해당 경우를 판단하여 마침표라면 지워준다.

14. 이제 마지막 단계인 7단계도 size()함수를 이용해 크기가 2자 이하인지 판단하고, 마지막 단어를 lastWord라는 변수에 저장하여 현재 문자열 new_id의 크기가 3이 될때까지 반복해서 new_id의 뒤에 lastWord를 붙여준다.

15. 지금까지 바꾼 new_id를 리턴해준다.

 

<풀이과정 2>

소스코드에 과정을 넣음


<코멘트>

경우의 수가 나눠져 있어서 쉬울 줄 알았지만 오히려 더 헷갈리는 느낌이었다.

원래는 4단계까지 한 반복문 내에서 하려했는데 1-2단계의 결과에 따라 3-4단계의 결과가 바뀔 수 있어서 따로 해주어야 했다. 그리고 여러가지 예외사항들이 많아서 헷갈렸다.

 

다른 사람의 풀이를 봤는데 극찬받는 코드가 있어서 그 코드도 가져와 분석해보았다.


<제출결과>