https://programmers.co.kr/learn/courses/30/lessons/72410

 

코딩테스트 연습 - 신규 아이디 추천

카카오에 입사한 신입 개발자 네오는 "카카오계정개발팀"에 배치되어, 카카오 서비스에 가입하는 유저들의 아이디를 생성하는 업무를 담당하게 되었습니다. "네오"에게 주어진 첫 업무는 새로

programmers.co.kr

문제에서 요구하는 순서대로 구현해나가면 쉽게 풀이가 가능.

단 문자열을 좀 다룰 줄 알아야 했다.

풀면서 이번에 좀 알고가야겠다 싶은 메소드들을 정리해봤다.

def solution(new_id):
    can_ch = set(['-', '_', '.'])
    
    new_id = new_id.lower()
    answer = ''
    
    for ch in new_id:
        if ch.isalnum() or ch in can_ch:
            answer += ch
            
    while ".." in answer:
        answer = answer.replace("..", ".")
        
    answer = answer.strip(".")

    if len(answer) == 0:
        answer = "a"
    elif len(answer) >= 16:
        answer = answer[:15]
        if answer[-1] == '.':
            answer = answer[:14]

    while len(answer) <= 2:
        answer += answer[-1]
    return answer

1. isalnum() 메소드

문자열이 숫자 또는 알파벳으로만 구성돼있다면 True리턴. "1", "a", "123ab"이런 거 죄다 True뱉는다는거임.

비슷하게 숫자로만 구성돼있다면 True리턴하는 isdigit(), 알파벳으로만 구성돼있다면 True를 리턴하는 isalpha()도 있다.

 

2. replace() 메소드 (참고 : replace된 문자열을 리턴함. 원본 자체를 replace해주는 건 X)

문자열 내의 특정 문자열들을 모조리 임의의 문자열로 치환해주는 기능. 첫 파라미터가 바뀔 문자열, 두 번째 파라미터가 바꿀 문자열이다. 세 번째 파라미터는 옵셔널로 몇 개의 문자열을 바꿀지를 결정해준다.

주의할 점은 replace를 통해 새롭게 꽃단장을 한(?) 문자열에도 내가 바꾸고 싶어하는 문자열이 남아있을 수 있다.

예를 들어 "..."이란 문자열에서 ..을 .으로 바꿔야 한다고 해보자.

example_string = "..."
example_string = example_string.replace("..", ".") // ..

 사실 위 문자열에서 ..은 0 ~ 1번 인덱스, 1 ~ 2번 인덱스 이렇게 2개가 있는 상황으로 서로 1번 인덱스가 겹치는 상황인데 replace는 이런 상황을 방지?하기 위해 ..을 찾았다면 그 다음 위치부터 ..을 찾는 것 같다. 즉 0 ~ 1번 인덱스가 ..이니 2번 인덱스부터 ..을 다시 찾는것. 따라서 위 상황에선 0 ~ 1번 인덱스의 ..만 교체대상이라 판단한 후 교체해주는데, 교체된 후 새로 태어난 ..에 대해선 ..이 있는지 없는지 그런 건 검사하지 않는다는 것! 

 아마 내 예상으론 앞에서부터 탐색해나가며 교체대상 문자열을 마주칠때마다 바꾸는 게 아니라 위 방법처럼 "교체대상 찾으면 그 다음 인덱스부터 찾아나가기" 방식으로 바꿔야할 애들만 다 찾아놓은 후 한꺼번에 바꾸는 식으로 하는 것 같다.

 

3. strip()메소드

문자열 양끝에 특정 문자가 있으면 그 녀석들을 제거해주는 메소드. 아무 인자도 전달하지 않으면 공백문자를 제거하는 것이 디폴트고 임의의 문자를 전달하면 그 문자들을 제거한다. 참고로  lstrip은 왼쪽 끝, rstrip은 오른쪽 끝을 제거함. 참고로 양 끝을 제거하긴 하는데, 제거한 후에도 양 끝에 파라미터로 전달받은 문자가 있으면 다시 제거하는 걸 반복한다.

어떠한 데이터들이 주어졌다. 이해를 돕기 위해 이 데이터들은 각각 1개의 원인 x와 1개의 결과 y를 가진다고 하자. 그럼 다음과 같이 좌표평면에 나타낼 수 있다.

그리고 이 데이터(검은 점들)을 대표하는 선이 있다고 하면 어떨까?

이 선을 이용한다면, 우리가 가지고 있지 않은 데이터가 있다고 해도 그 데이터의 x값을 이용해 y값을 예측해낼 수 있다. 

 그러나 과연 어떤 선이 '이 데이터들을 대표하는 선'이라 할 수 있느냐는 문제에 직면하게 된다. 이 선도 저 선도 어제 그은 그 선도 다 대표하는 선이라고 갖다 붙일 수 있을텐데 말이다. 이 때, 이 데이터들을 대표하는 선을 다음과 같이 정의한다면 어떨까.

 

"실제 데이터와의 오차들의 합이 가장 작은 선"

 

이렇게 되면 무수히 많은 선들 중 이 데이터를 대표하는 선은 딱 하나가 된다. 실제 데이터과의 오차란, 같은 x에 대해 내가 그은 선에서의 y값과 실제 데이터의 y값과의 차이를 의미한다. 따라서 임의의 직선 y = ax + b가 있을 때, 실제 데이터들과의 오차들의 합이 가장 작게 되는 a와 b를 찾게 된다면 이를 통해 임의의 x값에 대한 y값을 예측 가능하다. 이를 선형 회귀라 하며 영어로는 Linear Regression이라 한다.

 

참고 : 실제 데이터와의 오차들의 합이 가장 작으면 실제 데이터와의 오차 평균이 가장 작은 선이라고 할 수 있다. 또한 엄밀하게 말하자면 오차들은 양수가 될 수 있지만 음수가 될 수도 있으므로 오차합을 따지는 건 적절치 않다. 따라서 오차의 제곱들의 평균이 제일 작게 되는 선이라고 표현하는게 정확하다. 아니면 뭐 오차의 절댓값의 합이 가장 작은 이렇게 편하게 가도 되고..

 

그럼 관건은 이 a와 b값을 도대체 어떻게 찾느냐는 것이다. 최소 제곱법이란 공식을 통해 기울기 a와 y절편 b를 바로 구할 수 있는 방법이 있지만 세상은 그리 녹록치 않다. 방금까지는 일차함수 y = ax + b꼴만을 얘기했으니 원인에 해당하는 값이 1개인 경우만 본 거지만 실제 세상은 하나의 원인만으로 결과가 만들어지지 않기 때문이다. 즉 최소 제곱법으로 a와 b를 직빵으로 구하는 방법을 쓰기엔 처리해야할 입력값들이 너무 많은 것. 

 

그럼 도대체 어떻게 a, b를 찾으라는 건데? 그건 바로

 

"일단 선 하나 그어봐. 그리고 수정해 나가자고"

 

이거다. 가설을 하나 세운 후 이 가설이 주어진 요건을 충족하는지 따져보고, 조금씩 수정해보며 변화가 긍정적인 쪽으로 수정해나가며 오차가 최소가 될 때까지 반복해보는 것. 이것을 하는 대표적인 방법으로 경사하강법이 있으며, 이는 다음 글에서 소개하겠다.

클로저(Closure)

자바스크립트에서 어떤 함수와 그 함수가 참조할 수 있는 값들로 이루어진 환경을 하나로 묶은 것을 의미한다. 다르게 표현하면 함수와 그 함수가 선언된 어휘적 환경의 조합이다. 다음 예시를 살펴보자.

출처 :&nbsp;https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures

가만 보면 뭔가 이상~하다. MakeFunc함수가 실행되면서 지역변수 name을 만들었다. 그리고 실행 중 만든 함수 displayName에서 name을 참조하는 건 이상하지 않다. 근데 이 displayName을 리턴한 이후에도 계속해서 name을 참조하는 듯한 현상이 발생한다! MakeFunc의 실행이 끝나면 지역변수였던 name에 더 이상 접근할 수 없으리라 예상하는 것이 당연한 수순같지만 그렇지 않은 것.

 

이는 바로 자바스크립트가 클로저라는 것을 형성하기 때문이다. 이렇게 함수가 정의된 당시에 참조할 수 있었던 변수들을 계속 참조할수 있는 것을 클로저라고 한다. 위 예시의 경우 myFunc는 MakeFunc가 실행될 때 생성된 displayName이란 함수의 인스턴스에 대한 참조인데, displayName의 인스턴스가 변수 name과 함께 살았던(?) 때의 환경을 유지하기 때문에 이런 현상이 가능한 것. 당연한 소리지만 name변수가 자체가 안 죽고 살아 숨쉬는 것이 아니므로 console.log(name)을 하면 에러가 난다. 

 

좀 더 흥미로운 예제를 살펴보자. 

출처 :&nbsp;https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures

실로 골때리는 예제가 아닐 수 없다. 그와 동시에 재밌는 예제이다. 단일인자 한 개를 받아 새 함수를 리턴하는 makeAdder를 정의했는데, 이 새로 정의된 함수도 단일인자를 받아서 x, y, z의 합을 리턴한다. 중요한 것은 이 예제가 클로저를 형성한다는 것! add5는 5를 인자로 makeAdder를 호출해 결국 x = 5, y = 100이란 클로저를 이룬 상태에서 x + y + z를 리턴하는 함수 인스턴스를 참조하는 녀석이고, add10은 10을 인자로 makeAdder를 호출해 x = 10, y = 100이란 클로저를 이룬 상태에서 x + y + z를 리턴하는 함수 인스턴스를 참조하는 녀석이다. 

 


클로저 활용 - 캡슐화

그럼 이 클로저를 활용하는 예시를 살펴보자. 바로 객체 프로퍼티 등의 캡슐화에 이를 이용할 수 있다. 바로 다음처럼.

createUser함수 내부에서 user객체가 생성될 때 _email이란 지역변수를 참조할 수 있던 클로저를 형성한 채로 있기 때문에 user1._email처럼 직접 접근해 수정할려고 하는 dog억지 땡깡을 막을 수 있는 것.

 

footer와 그 위 요소간에 위처럼 선을 줄려고 하는 상황.

footer에게 border-top으로 선을 주거나 그 위 태그한테 border-bottom을 줘서 선을 만들어야지~하고 생각했는데 그렇게 하면

앗! 이렇게 스크롤이 생겨버린다!!

화면 꽉 채울려고 body, html태그들한테 height: 100%멕이고 body에 기본으로 있는 margin도 없앴는데 이런 억지스크롤이 왜 생기지? 했는데 알고 보니까 border에 선을 줬기 때문..

 

border두께도 전체 높이에 영향을 준다. 그래서 손톱만큼 border를 넣어줘도 억지스크롤이 생김. 

암튼 해결책은 box-shadow! 이걸로 선을 만든 효과를 주면 스크롤이 생기지 않는다.

box shadow 사용법은 https://www.codingfactory.net/10628 참고

근데 저 글에서 y-position이 음수면 위쪽에 만들어진다고 써있는데 양수여야 위쪽에 만들어진다. 이점도 참고~

파일들을 기능별로 분리하는 작업을 모듈화라고 하고, 각각의 파일들을 모듈이라고 부른다. ES6부터 이 모듈화를 할 수 있는 문법이 등장했는데 그것이 바로 export와 import이다. 어떻게 쓰는지 알아보자.

 

모듈 스코프

모듈이 되는 파일들은 독립적인 스코프를 가져야 하며, 모듈 파일이 갖는 스코프를 모듈 스코프라고 한다. 이는 모듈 파일 안에서 선언된 변수나 함수 등은 그 파일 안에서만 사용 가능해야 한다는 것을 의미한다. 일반적으로 HTML문서에서 <script>태그로 단순히 js파일을 불러오면 모듈 스코프가 적용되지 않지만, <script>태그에 type="module"이란 속성값을 주면 그 파일에 모듈 스코프를 줄 수 있다. 이 속성값을 통해 브라우저가 해당 스크립트가 모듈이란 것을 인식한다고 함.

참고 : 모듈은 로컬 파일에서 동작하지 않고, HTTP 또는 HTTPS 프로토콜을 통해서만 동작.
웹 서버 등을 통해 열어야지만 모듈이 정상적으로 동작함(vscode의 경우 live server등을 통해 이용 가능)

 

export, import

모듈스코프를 갖고 있으면 다른 파일에서 그 모듈 파일의 변수&함수 등을 쓸 수가 없음. 이 경우 특정 문법을 통해 모듈 파일의 변수나 함수를 사용 가능한데, 이를 가능하게 하는 것이 export이다. 이 export키워드를 통해서 모듈 파일에서 "너는 다른 데서도 쓸 수 있게 해줄게"라고 지정해주는 것이 가능한 것임. 그러나 단순히 export했다고 해서 다른 파일에서 아무런 제한없이 막 export된 애들을 쓸 수 있는 게 아니라, export된 변수나 함수 등을 쓰고자 하는 파일에서 import를 통해 한 번 더 불러오는 과정이 필요하다.

 

간단한 예시를 보이겠다.

a.js에서 export키워드를 통해 title변수와 func라는 함수를 export 즉 외부에서 쓸 수 있도록 지정했다. 이 export키워드가 붙지 않았다면 외부에서 쓸 수 없다. 이제 다른 js파일, 예를 들면 b.js에서 다음과 같이 import해서 이들을 쓸 수 있다.

title과 func를 a.js로부터 갖고 와 쓰겠다! 라는 의미로 해석하면 된다. 즉 a.js에서 export된 것들 중 내가 쓰길 원하는 애들만 따로 골라 그 목록을 중괄호 {}안에 써서 갖고 올 수 있다는 의미. 객체에서의 destructuring문법과 비슷하다고 볼 수 있다.

 

방금 전에선 export할 때 내보내고 싶은 변수나 함수 앞에 하나하나 export키워드를 붙이는 방법들을 살펴봤다. 근데 사실, export는 다음과 같은 방법들로도 사용 가능하다.

즉 export { name1, name2, ..., nameN}의 방법을 쓰면 내보낼 변수나 함수 앞에 하나하나 export를 붙일 필요 없이 한꺼번에 export할 수 있고, as키워드를 통해 이름을 바꿔서 내보낼 수도 있다는 것이다.

 

import는 다음과 같은 방법으로도 가능하다.

import할 모듈에서 export된 애들을 내가 원하는 이름의 객체로 바인딩해 가져올 수도 있고, export할 때 as로 이름 바꿔주던 것처럼 import할때도 이름 바꿔서 가져올 수도 있다. 그리고 import만 하면 (세 번째 방법) export된 애들 모두를 export된 이름으로 쓸 수 있게 되는 줄 알았으나 그게 아니었다. 단순히 그 특정 모듈 파일을 가져와 한 번 실행하고 끝나는 듯하다. 

 

음..함수에서 인자로 객체를 넘기는 상황에선, 함수 작성시 파라미터로 destructuring문법을 쓰면 인자로 받는 객체에서 원하는 property값들만 가져올 수 있었다. export한다는 것도 export하는 애들을 객체 형태로 묶어서 전달하는 것이고, 그렇기 때문에 import할때 destructuring문법처럼 원하는 애들만 가져올 수 있는 게 아닌가 생각한다. 이건 그냥 본인 뇌피셜임.

 

 

Named Exports, Default Exports

방금 전과 같이 여러 대상을 export하는 방식을 Named Exports라고 한다. 그러나 default키워드를 통해 하나의 대상만을 export하는 것도 가능한데, 이를 Default Export라고 한다.

 

모듈파일 내에서 default키워드는 딱 한 번만 사용 가능하며, export뒤에 default가 붙으면 반드시 하나의 대상만 내보낼 수 있게 된다. import하는 파일에서는 이렇게 내보내진 값을 default로 받을 수 있는데 as키워드를 통해 이름을 붙여서 받아야 한다. 

근데, 이 Default export방식의 장점은 import할 때 default값에 붙여준 이름을 중괄호 밖으로 빼주어 더 편하게 작성할 수 있다는 점이다. 즉 b.js에서 다음과 같은 방식으로 import가 가능해진다. 

즉 중괄호 여부로 default exports와 named exports를 구분할 수 있는 것. 

참고로 default exports와 named exports는 함께 사용 가능하다.

근데 이렇게 같이 섞어 쓰면 난잡하니까..보통 export할 대상이 여러 개면 named방식을, 한개일때만 default방식을 사용한다.

+ Recent posts