다양한 방법이 있을 거 같은데, 나는 (하드코딩 + 선형탐색)이라는 단순한 방식으로 lookup table 함수를 만들었다.
정답 코드
from functools import reduce
def lut(c):
groups = ["ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ"]
for i, g in enumerate(groups):
if c in g:
return i + 3
print(reduce(lambda x, y: x + lut(y), input(), 0))
lut의 결과인 정수값들의 총합을 구하기 위해 functools의 reduce를 처음 써봤다. JavaScript의 reduce와 비슷하게 동작한다.
import sys
next(sys.stdin)
s = list(next(sys.stdin))[:-1]
print(sum(int(i) for i in s))
간단한 문제이지만, 따로 블로그로 기록을 남기게 된 이유는 아래 문구를 사용하면서 새로 배운 게 있기 때문이다.
sum(int(i) for i in s)
먼저 VS Code에서 sum 함수에 대한 설명을 보면, 첫째 인자로 iterable을 받는다는 것을 알 수 있다.
그말인즉슨 sum() 안의 int(i) for i in s 가 iterable이라는 뜻이다. 엄밀히 말하면 generator이다.
여태까지는 x for x in y 형태의 표현이 generator이다라는 것을 생각 안하고 사용해왔다.
이러한 x for x in y 형태의 generator 표현법을 "Generator Expression"이라고 한다.
아래에서 확인 가능하다.
print(type(i for i in range(5))) # 출력: <class 'generator'>
print(i for i in range(5)) # 출력: <generator object <genexpr> at 0x0000014C60D77400>
generator는 iterable이기도 하므로, generator expression도 list(), [], tuple() 등으로 감싸서 사용할 수 있다.
또한 map()등에도 전달할 수 있다.
print(list(i for i in range(5))) # 출력: [0, 1, 2, 3, 4]
print([i for i in range(5)]) # 출력: [0, 1, 2, 3, 4]
print(tuple(i for i in range(5))) # 출력: (0, 1, 2, 3, 4)
print(list(map(lambda x : x * 2, (i for i in range(5))))) # 출력: [0, 2, 4, 6, 8]
중요한 점
x for x in y 형태의 generator expression은 함수의 인자로 전달될 때만 유효한 표현이고, 인자가 아닌 독립적인 형태로 존재하려면 괄호로 감싸서 (x for x in y) 형태여야 한다.
함수의 인자로 전달되는 generator expression은 괄호로 감싸지 않아도 파이썬이 generator expression인 것으로 알아서 처리하는 한다.
반면, 독립적으로 존재하는 generator expression은 괄호로 감싸지 않으면 아래처럼 SyntaxError가 발생하는 것을 확인할 수 있다. 에러 문구에서도 괄호로 감싸라고 친절히 알려주고 있다.
print(map(lambda x : x * 2, i for i in range(5)))
File "<stdin>", line 1
print(map(lambda x : x * 2, i for i in range(5)))
^^^^^^^^^^^^^^^^^^^
SyntaxError: Generator expression must be parenthesized
아래처럼 괄호로 감싸야 에러가 발생하지 않는다.
print(map(lambda x : x * 2, (i for i in range(5)))) # 출력: <map object at 0x0000014C60FA9D20>
스택을 구현하고, 입력되는 명령에 따라 스택에 아이템 추가/삭제/조회 를 수행하는 프로그램을 구현해야 한다.
결과 문자열을 result에 저장하는 아래의 코드를 제출했으나 시간 초과가 발생했다.
시간 초과발생 코드
import sys
commands = [s.rstrip("\n") for s in sys.stdin.readlines()][1:]
stack = []
result = ""
for command in commands:
if command[0] == "1":
num = int(command[2:])
stack.append(num)
elif command[0] == "2":
result += f"{stack.pop() if stack else -1}\n"
elif command[0] == "3":
result += f"{len(stack)}\n"
elif command[0] == "4":
result += f"{0 if stack else 1}\n"
elif command[0] == "5":
result += f"{stack[-1] if stack else -1}\n"
sys.stdout.write(result)
조사해보니, 시간 초과가 발생하는 이유는 다음과 같다.
for 루프에서 매번 +=를 사용해 result 문자열에 append를 할 때마다 완전히 새로운 result 문자열 객체가 생성되기 때문이다.
예를 들어 아래 코드가 수행되면 무슨 일이 벌어질는지 알아보자.
result += f"{stack.pop() if stack else -1}\n"
f"{stack.pop() if stack else -1}\n" 부분은 새로운 임시 문자열을 생성한다.
result += ... 부분의 += 연산은 under the hood에서 다음 동작을 수행한다
이전의 result 문자열과 새로운 임시 문자열을 결합한 새로운 문자열이 메모리 공간에 새로 할당된다.
이전의 result 문자열과 임시 문자열의 메모리 공간이 해제된다.
즉 += 연산이 발생할 때마다 result 문자열 전체에 대한 메모리에 재할당 및 복사 작업이 발생하는 것이다. 이 과정은 result 문자열이 길어지면 길어질 수록 점점 더 비용이 증가한다.
대신에 결합할 문자열들을 list에 모아두었다가, 마지막에 join() 연산을 통해 한번에 결합하는 것이 비용을 아낄 수 있다.
출력할 결과 문자열을 모두 result라는 list에 모아두었다가, 마지막에 "\n".join(rsult)를 출력하도록 했다.
정답 코드
import sys
commands = [s.rstrip("\n") for s in sys.stdin.readlines()][1:]
stack = []
result = []
for command in commands:
if command[0] == "1":
stack.append(command[2:])
elif command[0] == "2":
result.append(stack.pop() if stack else "-1")
elif command[0] == "3":
result.append(str(len(stack)))
elif command[0] == "4":
result.append("0" if stack else "1")
elif command[0] == "5":
result.append(stack[-1] if stack else "-1")
sys.stdout.write("\n".join(result))
추가 최적화를 진행해봤다.
sys.stdin.readlines()는 list를 새로 생성해서 반환하기 때문에, 리스트를 만드는 시간 비용과 메모리 공간 비용을 절감해봐야 겠다고 생각했다.
가면 증후군이란, 업무에서 자신의 능력을 의심하고 성과에 대해서 과소평가하는 것입니다. 자신이 일을 잘한다고 동료들을 속이는 사기꾼이라는 느낌을 받곤 합니다.
아래와 같은 생각을 하고 있다면 가면 증후군에 해당될 수 있다고 합니다:
"나는 개발자가 될만큼 머리가 좋지 않은 것 같아"
"동료들보다 실력이 떨어지는 것 같아"
"일도 해야 되고 개발 공부도 해야 되는데 벅차고 힘들다"
"누군가 나의 진짜 실력을 알아챌까봐 두렵다"
개발자들은 워낙 업무의 지식적 범위가 넓고 개개인의 퍼포먼스가 다른 직종에 비해 좀 더 명확하게 드러나므로, 가면 증후군에 매우 취약한 직종이라고 생각합니다.
저 또한 오랫동안 겪어왔던 문제였고 번아웃 증상을 여러 번 겪었습니다. 일의 난이도는 높고, 너무 많은 일에 허덕였었고, 주변에 뛰어난 동료들이 너무 많아서 더 그랬던 것 같습니다. 제가 일을 쉬게 된 큰 이유 중 하나였던 것 같습니다.
코딩을 잘하는 사람은 정말 많습니다. 그런 사람들이 주목을 많이 받기도 하고, 실제로 좋은 대접을 받는 것도 사실입니다.
하지만 정작 업계에서는 그 실력자들보다 더 많은 사람들이 근무하고 있습니다. 개발자 대부분은 평범한 사람들이고, 회사에서는 실력 있는 사람을 구하지 못해서 문제라고 합니다. 많은 사람들이 현 상태에 지쳐 안주하거나 포기하기 때문에 회사가 원하는 수준까지 도달 못한다고도 생각이 듭니다.
중요한 것은 내 실력을 열심히 기르면서, 내가 할 수 있는 일을 하는 것입니다. 꾸준하게 실력을 기르다 보면, 어느샌가 실력이 쌓여 있을 것이고, 그때 내 실력에 맞는 직장이나 일감을 구하면 된다고 생각합니다.
내 실력에 비해 지나친 연봉 및 처우에 대한 욕심을 버리지 못하면, 번아웃 및 가면 증후군에 걸릴 위험이 크다고 느낍니다.
저는 멘탈이 건강한 실력자가 되는 것이 목표입니다.
2. 끊임없이 공부하고 정리하고 기록해야 내 것이 된다.
개발자로서 알아야 되는 지식은 너무나도 광범위합니다. 수많은 개발자들이 끝없는 학습량에 압도당하는 일이 잦기 때문에 스트레스를 느끼고 저 또한 늘 그랬던 것 같습니다. 공부 자체는 개발자의 업이기 때문에 피할 수는 없습니다.
다행히 요즘은 구글링은 물론이고 LLM에 물어보면서 학습하는 것이 큰 도움이 됩니다. 그래서 질문에 대한 답을 얻는 것이 매우 용이해졌습니다.
그러나 제일 지양해야 하는 점은, 무엇인가에 대한 해답을 얻었을 때 당장의 문제를 넘기기 위해 1회성으로 당장 닥친 일만 처리하고 넘기는 것이라고 생각합니다.
먼저 해답이 왜 해답인지에 대한 이해를 어느정도 확실히 해두고, 어딘가에 정리하고 기록을 해서 보관을 하는 것이 필요합니다. 왜냐하면 개발 지식이라는 것은 조만간 다시 재사용이 필요한 경우가 부지기수이기 때문에, 다음 번에 필요한 순간이 되었을 때 생각이 나는 것이 중요하고, 생각이 안 난다면 어디로 가서 다시 look-up해야 하는지 빨리 기억하는 것이 중요하기 때문입니다. 나만의 cheetsheat 공간이 필요합니다(제 블로그도 이런 비슷한 공간입니다).
당장 공부를 하고 기록한다는 것 자체가 시간이 너무 많이 뺏기기 때문에 비효율적이라고 느낄 수도 있습니다. 하지만 이것을 게을리 하면, 금방 물경력 개발자가 된다는 것을 몸소 느꼈습니다.
저도 계속해서 효율적인 정보 기록 방법을 계속 찾고자 노력 중입니다.
3. 개발 능력에는 코딩만이 아닌 협업과 소통 능력의 비중이 매우 크다.
이것은 학생 또는 취준생 때는 깨닫기 어려운 사실 중 하나입니다. 나 혼자의 페이스대로 열심히 하면 보상이 따라주는 시기이기 때문입니다. 제가 프로그래밍을 시작하게 된 계기 중 하나가, 사람들이랑 너무 얽히는 게 기빨리고 화면만 보는 게 편하다고 생각해서인 것도 있었습니다. 그렇지만 잘못 생각한 것이었습니다. 이제는 어떤 제품이든 솔루션이든 서비스이든 기업에서 상용화하는 것은 개발자가 혼자서 다 개발할 수 있는게 거의 없고 팀워크로 만들어집니다. Git과 Github이 그렇게 중시되는 것도, 형상관리의 이유도 있겠으나, 결국 팀워크를 위한 기반 도구이기 때문입니다. 내가 하고 있는 일을 동료에게 원활하게 전달하고 피드백을 받는 것이 기본입니다. 자기가 무슨 일을 하고 있는지 알아들을 수 있도록 제때 공유해야 합니다.
일단 공식적인 업무 미팅과 보고문서는 모든 직장인한테 그렇듯이 개발자에게도 너무 중요합니다. 저는 주니어 때 이걸 잘 깨닫지 못했었는데요. 그냥 제가 평소에 열심히 일하는 모습을 보고 있을테니, 업무 미팅과 보고문서에서는 대충 임하는 경향이 있었습니다. 말로 하고 문장으로 표현하는 것은 본업인 개발하기도 바쁜데 몹시 귀찮은 일이었기 때문입니다. 그러나 내가 하는 일이 무엇인지 남에게 제대로 설명하지 못하면, 아무리 일을 열심히 했어도 열심히 안 한 게 된다는 걸 깨닫게 되었습니다. 내가 하는 일을 위의 매니저와 동료들이 평가하게 되는데, 내가 무슨 일을 하는지 그분들이 이해를 잘 못하면 좋은 평가를 받기가 어렵기 때문입니다. 업무 설명과 보고 작업이 귀찮지만 이것도 본업이라는 것을 이해해야 합니다.
공식적인 미팅과 보고 외에 업무 일상에서의 소통도 매우 중요합니다. 개발 업무는 요구사항과 구현에 있어서 매우 명확한 의사전달이 중요합니다. 해야 하는 일에 대해서 언어적으로 표현을 잘하는 것은 필수적입니다. Slack과 같은 협업 툴이나 업무메신저를 사용하면서, 말을 간결하고 이해하기 쉽게 쓰는 것의 연습이 필요합니다. "개떡같이 말하면 개떡같이 알아듣는다"가 당연한 이치입니다.
또한 상대방을 배려하는 인성에 우러나오는 언어 사용이 중요하다고 생각합니다. 개발자들 특성상 성격이 매우 드라이하고 기계적인 언어를 쓰는 사람들이 많은데, 이것에 의해 상처받는 사람들이 꽤 많은 것으로 압니다. 특히 요즘에는 코드 리뷰를 통한 협업이 필수인데, 불필요하게 공격적인 리뷰는 갈등의 원인이 될 수 있습니다. 상대방이 주니어일수록 특히 더 배려해서 친절한 문장을 쓰는 것도 연습해야 된다고 생각합니다.
마지막으로 사소하다고 느껴질 수 있겠지만, 동료의 작은 호의에 감사함을 표현하고, 어떤 의견에도 리액션을 잘해주는 것도 필요하다고 생각합니다. 계속해서 좋은 기억으로 남는 동료들은 실력보다는 인성이 좋았던 사람들인 것 같습니다.
4. 취업 전략
개발자로서 취업 전략이란 것은, 수많은 요소에 따라 다르게 가져가야 된다고 느낍니다.
고려해봐야 할 요건들을 생각나는대로 나열해보면..
희망 연봉
신입 or 경력직
경력직의 경우 직급은 어떻게 할지?
회사 규모
대기업 vs 중견 vs 스타트업
사업 도메인
ex) IT서비스, AI, 제조업, 금융, 교육, 커머스, 의료, 엔터테인먼트 등.
분야에 따라 연봉과 문화 등이 매우 상이함. 또한 개발외적으로 배워야할 도메인 지식이 매우 다름.
회사의 성장성 여부(산업 전망)
사용하게 될 기술 스택과 업무 범위
내가 배운 걸 쓰게 될지?
기존 코드 유지보수 업무와 신규 개발 간 비중이 얼마나 될지?
내가 감당 가능한 업무인지?
조직 문화(회사 평판)
Harsh한 성과주의 문화 vs 포용적 문화
Hardcore 야근 불사 vs 워라밸
회사 복지
식대 지원, 교육비 지원, 장비 지원 등.
출퇴근 거리 or 재택근무 여부
체력과 삶의 질에 지대한 영향이 있음.
etc.
취직할 회사를 찾는데 있어서 위의 요건들을 최대한 많이 고려해봐야 한다고 생각합니다.
당장 취직이 급하다고 어디든 합격하는데로 바로 가면 안 된다고 생각합니다. 특히 아쉬운 위치인 신입일 때, 이런 오류를 범할 수 있다고 생각합니다.
중요한 것은, 빠른 합격이 아니라 지속 가능한 회사생활이기 때문입니다. 이것은 대기업이든 스타트업이든, 연봉이 많던 적던 간에 다 마찬가지입니다. 위 요건들을 몇 개 만족하지 못하는 회사에 취직한다면 일을 하면서도 마음이 금방 어려워질 수 있고, 이른 퇴사로 이어질 수 있습니다. 나의 마음이 건강하고 편안하게 오래 다닐 수 있어야, 개발자로서의 역량 향상도 따라올 수 있다고 생각합니다.
당장 대기업이나 잘 나가는 회사를 못 가도 괜찮다고 생각합니다. 더 작은 회사에서 실력 향상을 할 수 있다면, 오히려 경력직으로 이직하는 것이 더 쉬울 것입니다(어느 회사든 가성비 좋은 경력직을 선호합니다). 개발자는 다른 직종보다 개인의 노력으로 실력의 향상이라는 것 더 가능한 직종이기 때문입니다.
저도 이번에 그럴 생각이지만, 여유를 가지고 취직 준비를 하고자 합니다. 쉬엄쉬엄하겠다는 것이 아니라, 내 업무 역량이 어느 정도 갖춰질 때까지 최선을 다하면서, 위 요건들을 제 기준에서 많이 맞출 수 있는 회사를 찾으려고 합니다.
5. 회사 지원 TIP
저는 스타트업에 잠시나마 있으면서 채용 절차에 관여했던 적이 있었습니다(서류 검토 & 면접 참여).
제 주관적인 생각이지만, 서류 단계에서는 일단 시선을 잘 끌어당기는 게 중요한 것 같습니다. 나쁜 의미의 노이즈나 어그로를 끌라는 의미는 아니고, 검토하는 사람에게긍정적인 인상을 남기는 게 중요하다는 뜻입니다. 부트캠프 출신 개발자가 워낙 많기 때문에, 작은 회사라 할지라도 지원 서류는 수십~수백장은 기본으로 들어옵니다. 채용 담당자 입장에서, 많은 지원서를 보지만 그 중에서 정말 눈길이 가는 것은 소수입니다. 일단 진부하거나 올드해보이는 서류는 바로 탈락입니다(나이가 많은 지원자일수록 조심해야 될 것 같습니다).
직장 생활 이력, 프로젝트 경험, 기술 스택 정보 등의 정보가 간결 및 깔끔하고, 핵심이 잘 담긴 지원서들이 살아남았던 거 같습니다. 이런 서류들은 핵심 전달 능력이 좋기 때문에 이해하기 쉽기 때문입니다(일도 잘하겠구나 싶은 느낌을 줍니다).
힙해보이는 포트폴리오는 분명 좋은 영향이 있긴합니다만, 내가 담당하고 기여한 부분이 무엇인지 제대로 이해하고 설명하지 못한다면 면접에서 부메랑으로 돌아올 수 있습니다.
경력을 부풀려서 작성하는 것은 정말 나쁘다고 생각합니다. 일단 그렇게 부풀려서 입사한다 한들, 회사의 기대치만 높여서 일할 때 본인만 힘들 것입니다.
전체적으로는 서류이든 면접이든, 지원자가 개발자로 취직하기 위해 얼마나 진실되고 성실하게 살았는지를 봤던 것 같습니다. 그리고 회사 자체에 대해서 열심히 파악하려고 하는 자세가 보인다면 더 호감이라고 생각합니다.
그리고 특히 면접에서 매우 중요한 요소가 사람의 인상과 성격이었습니다. 이것은 회사의 조직문화 특성에 따라 다르긴 한데요, 똑똑해보이고 번쩍이는 타입을 좋아하는 곳이 있는 반면, 젠틀하고 유들유들한 타입을 좋아하는 곳으로 나뉜다고 생각이 듭니다. 이건 사실 결국 같이 일할 실무진들이 "같이 일하고 싶은 사람"을 선택하는 매우 인간적인 과정이므로, 어느 정도 운이 작용하는 것 같습니다.
채용과정에서 지원자는 많지만, 정작 건실한 지원서는 소수였던 것으로 기억합니다. 이 점을 역이용하면, 남들보다 조금만 더 좋은 평가를 받아도 합격하기가 수월하겠다는 생각을 가지게 되었습니다.
6. 마무리
글을 쓰면서 스스로의 커리어 패스에 대해서 회고할 수 있었습니다. 그러면서 다시 스스로의 마인드셋을 점검할 수 있는 기회도 되었습니다.
핵심은... "꾸준히 실력을 늘리는, 좋은 인성을 갖춘, 건강한 멘탈의 개발자가 되자"인 것 같습니다.
CSS Box Model은 웹 디자인과 개발의 기반적인 개념이다. 웹페이지의 요소(element)들이 어떻게 구조화될지, 요소들 주변의 공간이 어떻게 분배할지를 결정한다. CSS Box Model을 이해하는 것은 요소들의 레이아웃과 모양을 제어하는데 핵심적으로 필요하다.
웹페이지에서 모든 요소는 하나의 직사각형의 "box"라는 것을 먼저 이해해야 한다.
이 box는 안쪽부터 다음 4가지 영역들로 구성된다.
CSS Box Model의 구성 요소
Content
Text, image, 또는 다른 컨텐츠가 디스플레이되는 공간.
width와 height 속성을 변경해서 크기를 제어할 수 있다.
Padding
Content와 Border 사이의 공간(즉 Content를 감싸는 공간).
Border
Padding과 Content를 감싸는 테두리.
Margin
가장 바깥 쪽의 공간(즉 Border 바깥의 빈 공간이며 투명함).
요소와 다른 요소 간의 공간을 만든다.
영역별 상세 속성 설정
Content - width
width는 요소의 너비값을 의미한다.
기본값은 Content 영역의 너비이지만, box-sizing 속성을 사용하면 Padding과 Border의 너비까지도 포함할 수 있다.
width를 미입력시, 값으로 auto가 지정된다. 이는 부모 요소의 너비를 기준으로 계산하는 기능이다.
요소(element)는 <span> tag이다. 해당 요소는 box 클래스로 스타일링된다.
Content는 <span> 안에 위치한 "Hello World!"이다.
Box(즉, 요소)는 다음을 포함한다:
Content: "Hello World!" 텍스트 영역이며, Content 자체의 크기로 크기가 결정된다.
즉 width와 height 속성으로 크기가 결정되지 않으므로, block 요소와는 달리 명시하지 않는다.
Padding: 5px로 설정된 공간이다.
Border: 1px 굵기이며, solid 스타일에 빨간색이다.
Margin: 20px의 Border 바깥 공간이다.
Inline 요소의 경우, left/right margin은 Box Model에 완전히 영향을 미친다. 반면 top/bottom margin은 특이하게 동작한다. top/bottom margin은 inline 요소가 속한 line box에 적용되는데, inline 요소 자체의 높이를 직접 바꾸지는 않는다. (부모 요소가 block container이고, 다른 요소들이 없는 경우와 같이 Inline 요소 주위의 다른 레이아웃 조건에 따라 브라우저가 상하 margin을 반영하기도 한다).
아래 우측 그림에서는 margin이 top/bottom에도 적용된 것 같이 나왔지만, 실제로는 아래 좌측 그림 처럼 left/right에만 적용되었다.
브라우저에 렌더링된 화면과, 4가지 구성요소의 레이아웃은 아래와 같다:
CSS Box Model과 Block 요소 및 Inline 요소 간의 관계
Block 요소 특성 및 CSS Box Model과의 관계:
Block 요소는 수평 공간 전체를 차지한다.
Block 요소는 위아래로 정렬되며, 새로운 줄을 만든다.
Block 요소 예시: <div>, <p>, <h1>~<h6>, <section> 등
CSS Box Model의 모든 4가지 영역(Content, Padding, Border, Margin)이 적용된다.
이는 Block 요소가 Box Model의 4가지 영역을 모두 사용할 수 있고, 그에 따라 요소의 전체 크기가 조정될 수 있다는 것을 의미한다. 이로 인해 요소의 전체 크기는 Content 크기(width와 height로 결정) 외에 Padding, Border, Margin 값으로 결정된 추가 크기를 합하여 결정된다는 뜻이다.
브라우저 기본값 폰트크기 16px를 기준으로 Content의 높이를 약 18px로 어림잡았다.
결론: Inline 요소는 Content 영역의 크기가 명시적으로 width와 height 속성에 의해 결정되지 않고, 요소 내부의 텍스트나 콘텐츠 크기에 따라 자동으로 정해진다. 이외의 Padding, Border, Margin(좌우)는 Inline 요소의 크기에 영향을 미친다.
<link rel="preconnect" href="https://fonts.googleapis.com"> : 브라우저가 fonts.googleapis.com 서버에 미리 연결하도록 요청해서, 서버와의 DNS 조회 및 SSL 연결 시간을 줄여 font를 더 빨리 로드하도록 한다.
fonts.googleapis.com 서버는 폰트 설정(CSS 파일)을 제공하는 서버이다.
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>: fonts.gstatic.com 서버에 미리 연결하고, crossorigin 속성을 설정한다. Font 파일을 빠르게 로드하면서 CORS 문제를 방지하는 역할이다.
fonts.gstatic.com 서버는 Google Fonts의 실제 font 파일을 제공하는 고속 서버이다.
Google은 전 세계에 분산된 서버(CDN - Content Delivery Network)를 사용해 font를 빠르게 제공한다. fonts.gstatic.com은 Google의 CDN에 연결된 서버로, 가까운 위치의 서버에서 font 파일을 다운로드받을 수 있도록 최적화된다.
Font 파일은 CORS를 통해 로드된다. 그래서 crossorigin 속성을 설정해 폰트 파일을 문제없이 가져오도록 해야 한다. Font 파일은 웹 브라우저의 보안 정책상 반드시 CORS 허용 설정이 되어야 다른 출처에서 사용할 수 있기 때문이다.
※ 참고: fonts.googleapis.com에서부터 로드된 CSS 파일의 @font-face의 역할
@font-face는 CSS 규칙 중 하나로, 웹페이지에서 custom font를 사용할 수 있게 해준다. 이 규칙을 사용하면 로컬 font가 아닌 외부 font를 가져와 웹페이지에 적용할 수 있다.
역할
Font 파일 선언: 웹페이지에서 사용할 font 파일을 지정한다. 다양한 형식(woff2, woff, ttf, eot 등)을 지원한다.
Custom font 이름 설정: font에 이름(font-family)을 부여해 나중에 CSS에서 해당 font를 사용할 수 있게 한다.
다양한 font 스타일 정의: font 굵기(font-weight), 스타일(font-style), 문자 범위(unicode-rage, 선택 사항) 등을 지정할 수 있다.
기본 문법
@font-face {
font-family: 'CustomFont'; /* 사용할 font의 이름 */
src: url('CustomFont.woff2') format('woff2'),
url('CustomFont.woff') format('woff'); /* font 파일 경로 및 형식 */
font-weight: normal; /* font 굵기 */
font-style: normal; /* font 스타일 */
}
2. 직접 @font-face를 HTML <style> 태그에 정의해서 Custom Font를 쓰는 방법
@font-face 규칙을 직접 <style> 태그에 입력해서 사용하는 방법이다.
방법 1. 외부 서버에서 제공하는 font file을 사용할 수 있도록 @font-face를 직접 명시하기
<link rel="preload" href="main.js" as="script">: 리소스를 미리 로드하여 성능 최적화
<link rel="prefetch" href="future-page.html">: 리소스를 예비로 로드하여 성능 최적화
<script>: JavaScript 코드를 HTML 문서에 포함하거나 외부 JavaScript 파일을 연결함으로써, 웹 페이지의 동적 기능을 추가하는 데 사용된다.
<head> 내에 <script>를 삽입하는 경우, 주로 문서 로딩 시 필요한 라이브러리나 설정 정보를 포함한다. 다만, <head>에 넣으면 페이지 로딩이 완료되기 전에 JavaScript가 실행되므로 페이지 로딩 성능에 영향을 줄 수 있다. 일반적으로 <body> 끝쪽에 넣는 것이 더 효율적이다.
<script src="script.js" defer></script>: 외부 Javascript 파일인 script.js을 연결한다. defer 속성은 HTML의 로딩이 끝난 뒤에 script를 동작시키도록 한다. defer가 아닌 async가 사용된 경우, JavaScript 파일을 비동기적으로 로드하여 페이지 렌더링에 영향을 미치지 않도록 한다.
<body>: 웹페이지의 본문을 나타내며, 실제로 사용자에게 보여지는 콘텐츠가 포함된다. 즉 페이지의 시각적인 부분 및 상호작용하는 요소들이 들어가는 영역이다. 웹페이지의 모든 내용은 <body> 안에 작성되어야 한다. 브라우저는 <body> 내부의 내용을 해석해서 렌더한다.
<header>: 웹페이지를 소개하는 내용 또는 navigational link를 담는다. 보통 웹사이트의 로고, 제목, 또는 메인 navigation 메뉴를 포함한다.
<h1>Welcome to My Website</h1>: 웹사이트의 title을 디스플레이 하고 있다.
<main>: 웹페이지의 주 콘텐츠(text, image 등)가 위치한다.
<p>This is a basic HTML boilerplate template.</p>: 웹페이지를 묘사하는 단순한 문단이다.
<footer>: 웹페이지의 footer 콘텐츠(저작권, contact 정보, 링크 등)가 위치한다.
파이썬에서 아스키 코드를 숫자로 변환하는 ord() 함수를 사용하면 알파벳을 매핑되는 숫자로 쉽게 변환할 수 있는 점을 이용했다.
그리고 문자열의 각 문자별 index를 쉽게 구하기 위해, enumerate()도 사용했다.
이번 문제부터 본격적으로 input()과 print()를 대신해서, 성능 개선을 위해 앞으로 쭉 sys.stdin과 sys.stdout 모듈 함수를 사용하려 한다.
(정답 코드)
import sys
lines = sys.stdin.read().splitlines()[:-1]
ans = []
for line in lines:
sum = 0
for i, c in enumerate(line, 1):
if c != " ":
sum += i * (ord(c) - ord("A") + 1)
ans.append(str(sum))
sys.stdout.write("\n".join(ans))
그래서 문제에서 주어지는 모든 input 정보를 하나의 리스트에 단순하게 append해버렸다.
그러나 이렇게 하면 무조건 시간 초과가 발생한다.
(시간 초과발생 코드 1)
n, m = map(int, input().split())
pokemon = [""]
for _ in range(n):
pokemon.append(input())
for _ in range(m):
q = input()
if q.isdigit():
print(pokemon[int(q)])
else:
print(pokemon.index(q))
그래서 리스트가 아닌, 해싱이 적용된 딕셔너리를 사용하는 방법으로 수정했다.
그러나 이번에도 (분명 내부적으로 시간 개선은 되었겠지만) 시간 초과가 발생했다.
(시간 초과 발생 코드 2)
n, m = map(int, input().split())
pokedex = {}
for i in range(1, n + 1):
pokemon_name = input()
pokedex[pokemon_name] = i
pokedex[str(i)] = pokemon_name
for _ in range(m):
q = input()
print(pokedex[q])
여기까진 input을 받을 때 오직 input()만 사용한 naive한 코드이다.
나는 그동안 파이썬에서 문자열 input을 받을 때 사용하기 쉽다는 이유로 input() 함수를 써왔다.
그러나 이번 문제를 통해, 그랬다가는 시간 초과가 뜰 수 있는 문제가 있다는 것을 제대로 체감하게 되었다.
다른 블로그 글들을 참고하여, sys.stdin과 sys.stdout 모듈을 사용하는 방법을 택하였다.
(정답 코드)
import sys
n, m = map(int, sys.stdin.readline().split())
pokedex = {}
for i in range(1, n + 1):
pokemon_name = sys.stdin.readline().rstrip()
pokedex[pokemon_name] = str(i)
pokedex[str(i)] = pokemon_name
for _ in range(m):
q = sys.stdin.readline().rstrip()
sys.stdout.write(pokedex[q] + "\n")
추가적으로 시도하진 않았지만, 더 최적화할 수 있는 여지가 충분히 있다는 것도 깨달았다.
현재 위 코드에서는 sys.stdin.readline()의 호출을 input을 한 줄 읽을 때마다 여러번 반복하고 있다. 그러나 문제에서 모든 input을 한번에 입력받을 수 있기 때문에, 굳이 한 줄씩 읽지 않아도 된다.
그리고 현재는 정답 출력도 한 줄씩만 수행하고 있다.
Input을 전부 단 한번의 read로 읽고, 정답을 단 한번의 write로 수행하면 성능이 아주 많이 개선될 것이다.