루나의 TIL 기술 블로그

파이썬 - 가변 인수(*args), 키워드 인수(**kwargs)

|

전 포스팅에서 키워드인수, 디폴트 인수에 대해 간단하게 다루었는데 이번에는 가변인수와 가변 키워드인수에 대해서 자세히 살펴본다.

가변 인수 (variable length arguments) *args

가변인수는 인수가 몇 개 들어올 지 모르는 상태에서 여러 개가 들어와도 처리할 수 있도록 하는 인수이다.

def f(x, *args):
    ...

별표를 붙여서 가변인수임을 나타내고 이름은 마음대로 정해도 된다.

아래 함수를 살펴보면 고정 인수(또는 위치 인수)가 1개, 그리고 가변인수가 사용되었다.
그러면 함수는 반드시 넘겨야하는 고정 인수를 나열하고, 나머지는 튜플 형식으로 한꺼번에 받는다.

def function(a, *args): 
    print(a, args) 

function(1) # 1 () 
function(1,2) # 1 (2,) 
function(1,2,3,4,5) # 1 (2, 3, 4, 5) 
function(1,2,3,4,5,'a','b',[6,7,8],{'a':3, 'b':8}) 
# 1 (2, 3, 4, 5, 'a', 'b', [6, 7, 8], {'a': 3, 'b': 8})

출처: 매일 꾸준히, 더 깊이

가변인수는 개수가 정해져있는 위치 인수 뒤에 써주어야하고 1개만 사용할 수 있다.

언패킹

x = [10, 20, 30]
print_numbers(*x)
10
20
30

리스트(튜플) 앞에 *를 붙이면 언패킹(unpacking)이 되어서 print_numbers(10, 20, 30)과 똑같은 동작이 된다. 말 그대로 리스트의 포장을 푼다는 뜻이다. 출처: 코딩 도장

키워드 인수 (Keyword Arguments)

키워드 인수는 복습하자면 인수에 이름을 붙여준 것이다.

키워드 인수를 사용하면,

  1. 키워드 인수를 사용하면 디폴트 값을 가진 인수를 빼고 쓸 수 있다.
  2. 읽기 쉽게 인수의 위치를 바꿀 수 있다.
  3. 이름을 붙여줌으로서 각 인수가 나타내는 것이 무엇인지 더 분명히 알 수 있다.

아래 함수는 output_file 과 contents 문자열을 받아서 압축파일을 만드는 함수이다.

def write_gzip_file(output_file, contents):
    with GzipFile(None, 'wt', 9, output_file) as gzip_out:
        gzip_out.write(contents)

위치 인수 대신 키워드 인수를 사용하면 아래와 같이 쓸 수 있다.

def write_gzip_file(output_file, contents):
    with GzipFile(fileobj=output_file, mode='wt', compresslevel=9) as gzip_out:
        gzip_out.write(contents)

첫번째 인수는 디폴트 값으로 None을 가지고 있는 파일이름 인수이다. 파일 객체 또는 파일이름 둘 다가 아닌 둘 중 하나만 GzipFile에 넘겨주면 되기 때문에 첫번째 인수로 쓰였던 파일이름이 필요하지 않다. -> 잘 모르겠음 compresslevel도 디폴트값이 9이기 때문에 써주지 않아도 된다.

def write_gzip_file(output_file, contents):
    with GzipFile(fileobj=output_file, mode='wt') as gzip_out:
        gzip_out.write(contents)

우리가 이름지어진 인수(키워드 인수)를 사용했기 때문에 두 개의 인수를 생략하고 인수들을 읽기 좋은 순서로 재배치해줄 수 있었다. fileobj가 mode보다 중요하기 때문에 앞에 써주었다.

*의 사용 : 위치 인수말고 키워드 인수만 입력받고 싶을 때

위치인수가 아닌 키워드 인수만 받고 싶을 때는 *뒤에 아무것도 붙이지 않고 써주면 된다.

from random import choice, shuffle
UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
LOWERCASE = UPPERCASE.lower()
DIGITS = "0123456789"
ALL = UPPERCASE + LOWERCASE + DIGITS

def random_password(*, upper, lower, digits, length):
    chars = [
        *(choice(UPPERCASE) for _ in range(upper)),
        *(choice(LOWERCASE) for _ in range(lower)),
        *(choice(DIGITS) for _ in range(digits)),
        *(choice(ALL) for _ in range(length-upper-lower-digits)),
    ]
    shuffle(chars)
    return "".join(chars)

이 함수는 이름이 써져있는 인수(키워드 인수)만 받을 수 있다.

>>> random_password(upper=1, lower=1, digits=1, length=8)
'oNA7rYWI'
>>> random_password(upper=1, lower=1, digits=1, length=8)
'bjonpuM6'
>>> random_password(1, 1, 1, 8)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: random_password() takes 0 positional arguments but 4 were given

출처 : https://treyhunner.com/2018/04/keyword-arguments-in-python/

가변 키워드 인수 (variable length keyword arguments) **kwargs

파이썬은 다른 프로그래밍 언어와 달리 함수가 받아들이는 인수의 이름을 알 수 있다. (키워드 인수를 쓸 수 있다)

def f(x, y, **kwargs):
    ...

‘**‘오퍼레이터는 함수로 하여금 여러 개의 키워드 인수를 받아들이도록 한다.

def f(x, y, **kwargs):
    # x -> 2
    # y -> 3
    # kwargs -> { 'flag': True, 'mode': 'fast', 'header': 'debug' }

주어진 인수들은 **뒤에 쓰인 인수의 이름에 딕셔너리 형태로 저장된다.(키:값 두 개가 있어야 하니까)

def foo(a, b, *varargs, **kwargs):
    #가변인수와 함께 쓸 때는 가변인수 뒤에 키워드 가변인수를 쓴다.  
    print(a, b, varargs, kwargs)

## Call foo
foo(2, 9, 12, 34, x=3, name="bob")

## 출력값
2 9 (12, 34) {'x': 3, 'name': 'bob'}

복잡한 인수일수록 뒤로 가는 것 같다.

디폴트 인수 (Default Arguments)

디폴트 인수는 값을 넣지 않고 호출했을 때 특정 값이 출력되도록 하는 인수이다.

위코드 - 디폴트인수

출처: 위코드

함수를 정의할 때 default value parameter를 non-default value parameter 앞에 정의하면 안되는 이유1

def multiply(a=2,b):
    print(a*b)

multiply(2)

메소드를 위와 같이 정의하면 SyntaxError: non-default argument follows default argument 가 발생한다. 그 이유는 만약에 메소드를 호출할 때 인자값을 1개만 전달하면 생략된 인자가 a 인지 b 인지 알 수가 없기 때문이다. 따라서 b 에도 기본값을 지정해주거나 기본값을 지정해준 인자를 맨 뒤로 이동시켜 메소드를 정의하면 문제를 해결할 수 있다.

def multiply(a,b=2):
    print(a*b)

>> multiply(2)
4

출처: 우공이산 블로그

함수를 정의할 때 default value parameter를 non-default value parameter 앞에 정의하면 안되는 이유2

def remove_x(word, x='i'):
	return ''.join(word.split(x))

remove_x('mississippi')
'msssspp'

첫번째 함수는 내가 받을 인수가 word에 해당한다는 것이 확실하다.

def remove_x(x='i', word):
	return ''.join(word.split(x))
SyntaxError: non-default argument follows default argument

두번째 함수는 내가 받은 인수가 i를 디폴트로 가지는 x인수 인지 word인수 인지 확실하지 않다.

위코드 리플릿 과제 - Wecolor Picker 컴포넌트

|

구현 화면

파란색 색상카드

코드펜

See the Pen by Luna YooYoung Ko (@lunayyko) on CodePen.

HTML

<!DOCTYPE html>
<html>

<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width">
	<title>repl.it</title>
	<link href="style.css" rel="stylesheet" type="text/css" />
	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" />
	<link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet">
</head>

<body>
	<div class="colorBox">
		<div class="colorSquare">
			<span>#709fb0</span>
      </div>
      <div class="bottom">
        <button class="heartBox">
          <i class="fas fa-heart"></i>
          &nbsp&nbsp
          <strong>451</strong>
        </button>
        <div class="days"> 
          <strong>3days</strong>
        </div>
      </div>
    </div>
  </body>
</html>

CSS

* {
  box-sizing:border-box; 
}
body{
  font-size:24px;
  font-family: 'Open Sans', sans-serif;
}
.colorBox{
  width: 520px;
  height:580px;
  background-color:#EBEFF3;
  border-radius:20px;
  margin: 0 auto;
  padding:40px;
}
.colorSquare span{
  display:none;
  position:absolute;
  top:360px;
  background-color:#578291;
  padding: 4px 12px;
}
.colorSquare:hover span{
  display:block;
  opacity:1;
  color:white;
}
.colorSquare{
  position:relative;
  background-color:#709fb0;
  color: #709fb0;
  width: 440px;
  height:420px;
  border-radius:20px;
}
.bottom{
  display: flex;
  justify-content: space-between;
  padding-top:24px;
}
button {
  border: 2px solid lightgrey;
  padding:12px 20px;
  border-radius:12px;
  font-size:24px;
}
.days{
  align-self:center;
}

위코드 리플릿 과제 - Weegle 검색바

|

전에 일하면서 익혔던 grid로 만들어보았다.

구현 화면

구현화면

코드펜

HTML

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>repl.it</title>
    <link href="style.css" rel="stylesheet" type="text/css" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"/>
    <link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet">
  </head>
  <body>
    <div class="wrapper">
      <div class="logo">
        <img src="https://user-images.githubusercontent.com/61774575/95163201-34411c00-075c-11eb-9987-d6301acb4dab.png">
      </div>
      <div class="search-box">
        <div class="icon grey"> <i class="fas fa-search"></i></div>
        <div class="grid-item"> <input></input></div>
        <div class="icon"> <i class="fas fa-keyboard"></i></div>
        <div class="icon blue"><i class="fas fa-microphone"></i></div>
      </div>
      <div class="tag-boxes">
        <div class="tag-box">Weggle 검색</div>
        <div class="tag-box">I'm feeling lucky</div>
      </div>
      <div class="lang">Weggle 제공 서비스 : <a href="#">English</a></div>
    </div>
  </body>
</html>

CSS

* {
  box-sizing:border-box; font-family: 'Open Sans', sans-serif;
}
.wrapper{
  display:grid;
  grid-template-rows: 3fr 1fr 1fr 1fr; 
  align-items: center; 
  justify-content: center; 
  gap: 12px; 
}
.logo{
  display:grid; 
  justify-content: center; 
}
.logo img{
  width:272px;
}
.search-box{
  display:grid;
  width:480px;
  height:32px;
  grid-template-columns: 2em 1fr 2em 2em;
  border: 1px solid lightgrey;
  border-radius: 16px;
  align-content: center;
  padding: 0 4px;
}
input {
  border: none;
}
.icon {
  margin: 4px 8px;
}
.tag-boxes{
  display: grid;
  grid-template-columns: 88px 120px;
  justify-content:center;
  gap: 12px;
}
.tag-box{
  font-size:12px;
  color:lightslategray;
  background-color:#F4F4F4;
  border-radius: 8px;
  padding:8px;
  text-align:center;
}
.lang {
  width: 160px;
  font-size: 12px;
  margin: 0 auto;
}
.blue{
  color:#5288EC
}
.grey{
  color:grey;
}

HTML/CSS - Semantic Web 과 Semantic Tag

|

시맨틱 요소란 무엇인가

시맨틱 요소는 브라우저와 개발자에게 둘 다 에게 분명한 의미를 전달하는 요소이다.
시맨틱이 아닌 요소에는 예를 들어 <div> 와 이 있다. 이 요소들은 안에 어떤 내용이 들어있는지에 대한 정보를 전혀 전달하지 않는다.

시맨틱 요소의 예로는 <form>, <table>, <article> 등이 있는데 이름을 보면 안에 내용이 어떤 것일지 분명하게 알 수 있다.

HTML에서의 시맨틱 태그들

부분 별로 어떤 내용인지 설명하기위한 시맨틱 요소들은 아래와 같은 것들이 있다.

기본 시맨틱 태그

이외에도 이런 것들이 있다.

article : 독립적이고 안에 개별적인 내용을 포함한 부분
aside : 페이지 컨텐츠 옆에 들어가는 부분
details: 사용자가 보거나 숨길 수 있는 부가적인 세세한 내용 figcaption : figure 요소의 캡션 figure : 일러스트, 도표, 사진, 코드 등의 개별적인 컨텐츠
footer: 글이나 섹션의 미주 부분 header : 글이나 섹션의 맨 위의 제목 부분
main : 메인 컨텐츠
nav: 네비게이션 링크
section: 컨텐츠를 포함한 웹사이트의 한 부분
summary: details 요소에서 보여지는 헤딩
time: 날짜 혹은 시간
출처: w3school

시맨틱 웹

시맨틱 웹이란 이런 식으로 컨텐츠에 무슨 내용이 포함되어있는지 자동으로 컴퓨터가 알게함으로서 정보를 더 잘 분류하고 보여질 수 있도록 하자는 아이디어이다.

시맨틱 웹은 컴퓨터가 정보 자원의 뜻을 이해하고 논리적 추론까지 하는 차세대 지능형 웹이다. 지금의 웹은 특정 검색어를 치면 불필요한 문서가 모두 나와 일일이 찾아 보아야 하는데 지능형 웹은 다르다. 단어의 유사성과 상관관계 등을 파악해서 원하는 결과물만 찾아 보여준다. 출처: 차세대 지능형 시맨틱 웹 & 온톨로지

시맨틱 태그의 예시

예를 들어서 <img> 태그를 사용하면 이 컨텐츠가 이미지라는 것을 컴퓨터가 알 수 있지만 css를 통해서 background-img: url로 이미지를 보여주게 되면 컴퓨터는 해당 요소에 이미지가 들어있는지 알 수 없다.

HTML/CSS - CSS 레이아웃

|

CSS를 처음 배웠을때 레이아웃을 잡는게 가장 어려웠던 기억이 난다.
grid, flex등 여러가지 새로운 방법들이 등장했지만 크로스브라우징 때문에도 그렇고 한글로 된 학습 자료가 많지않아서 아직 display와 position가 가장 많이 쓰인다.

position : relative, absolute, fixed

기준이 되고자 하는 부모 요소에게 relative을 주고 자식에게 absolute를 줌으로서 자식 요소가 부모 요소를 기준으로 상대적으로 움직일 수 있게 한다.

꼭 반대여야될 것 같은데 아니어서 처음에 외울때 헷갈릴 수 있다.
처음 외울때는 부모가 어린 자식(absolute)에게 유연하게 대응(relative)한다고 생각하면 좋을 것 같다.

position의 사용 출처: w3school

position:fixed 는 말그대로 움직이지 않도록 고정하는데 nav를 상단에 고정시키는 등의 작업을 할 때 사용한다.

display : inline, inline-block, block

display의 사용

display:inline은 요소를 텍스트처럼 취급해서 안에 들어있는 컨텐츠만큼 공간을 차지한다. display:block은 가로로 갈 수 있는 끝까지 공간을 차지한다.
display:inline-block은 요소를 블럭처럼 취급하지만 옆에 공간이 남으면 다음 요소를 끼워넣는 것을 허락한다.

float : right, left

float 속성은 요소를 한 쪽으로 띄워서 정렬하는데 쓰인다.
주의 : 절대 위치를 준 요소는 float속성을 무시한다.

float속성을 가진 요소는 주변 요소들이 이를 따라 흘러가게 만든다.
이를 방지하려면 다음 요소에 clear 속성을 주어야한다.

float에서 clear의 사용

프로그래머스 level 1 폰켓몬

|

걸린 시간 : 2시간

문제 설명

당신은 폰켓몬을 잡기 위한 오랜 여행 끝에, 홍 박사님의 연구실에 도착했습니다. 홍 박사님은 당신에게 자신의 연구실에 있는 총 N 마리의 폰켓몬 중에서 N/2마리를 가져가도 좋다고 했습니다. 홍 박사님 연구실의 폰켓몬은 종류에 따라 번호를 붙여 구분합니다. 따라서 같은 종류의 폰켓몬은 같은 번호를 가지고 있습니다. 예를 들어 연구실에 총 4마리의 폰켓몬이 있고, 각 폰켓몬의 종류 번호가 [3번, 1번, 2번, 3번]이라면 이는 3번 폰켓몬 두 마리, 1번 폰켓몬 한 마리, 2번 폰켓몬 한 마리가 있음을 나타냅니다. 이때, 4마리의 폰켓몬 중 2마리를 고르는 방법은 다음과 같이 6가지가 있습니다.

첫 번째(3번), 두 번째(1번) 폰켓몬을 선택
첫 번째(3번), 세 번째(2번) 폰켓몬을 선택
첫 번째(3번), 네 번째(3번) 폰켓몬을 선택
두 번째(1번), 세 번째(2번) 폰켓몬을 선택
두 번째(1번), 네 번째(3번) 폰켓몬을 선택
세 번째(2번), 네 번째(3번) 폰켓몬을 선택

이때, 첫 번째(3번) 폰켓몬과 네 번째(3번) 폰켓몬을 선택하는 방법은 한 종류(3번 폰켓몬 두 마리)의 폰켓몬만 가질 수 있지만, 다른 방법들은 모두 두 종류의 폰켓몬을 가질 수 있습니다. 따라서 위 예시에서 가질 수 있는 폰켓몬 종류 수의 최댓값은 2가 됩니다.
당신은 최대한 다양한 종류의 폰켓몬을 가지길 원하기 때문에, 최대한 많은 종류의 폰켓몬을 포함해서 N/2마리를 선택하려 합니다. N마리 폰켓몬의 종류 번호가 담긴 배열 nums가 매개변수로 주어질 때, N/2마리의 폰켓몬을 선택하는 방법 중, 가장 많은 종류의 폰켓몬을 선택하는 방법을 찾아, 그때의 폰켓몬 종류 번호의 개수를 return 하도록 solution 함수를 완성해주세요.

입출력 예

num result
[3,1,2,3] 2
[3,3,3,2,2,4] 2
[3,3,3,2,2,2] 2

사고과정

처음에 문제를 꼼꼼히 안 읽고 유니크한 포켓몬의 조합을 구하라는 얘기라고 생각했다.
주어진 포켓몬 리스트에서 중복을 제거하고 n개에서 2/n개를 뽑는 조합의 갯수를 출력한다.

사고과정

from itertools import combinations
#조합(중복을 허용하지 않고 순서를 고려하지 않는다)
def solution(nums):
  n = len(nums)//2
  nums = set(nums)
  return list(combinations(nums, n)):

조합의 가짓수가 유니크한 포켓몬의 종류보다 작으면 빈 배열이 반환된다.
그래서 조합의 가짓수가 2/n보다 작거나 같으면 조합의 가짓수를 반환하는 것을 추가했다.

from itertools import combinations

def solution(nums):
    n = len(nums)//2
    nums = set(nums)
    if len(nums) <= n:
        return len(nums)
    else:
        return len(list(combinations(nums, n)))

[3,1,2,3]의 경우 결과값이 3으로 나오고 답이 2인데 틀렸다고해서 다시 읽어보니 조합의 가짓수가 아닌 최대 폰켓몬의 수를 반환하는 것이었다.

그렇다면 좀 더 단순하게 생각할 수 있을 것 같은데, 유니크한 포켓몬의 갯수가 n보다 크다면 무조건 n값을 출력할 수 있겠다.

이렇게 되면 모든 경우가 커버되어서 조합을 사용하지 않아도 된다.

def solution(nums):
    n = len(nums)//2
    nums = set(nums)
    if len(nums) <= n:
        return len(nums)
    else:
        return n

모범답안

def solution(ls):
    return min(len(ls)/2, len(set(ls)))

유니크한 폰켓몬의 갯수와 가져갈 수 있는 폰켓몬의 갯수 중 작은 것을 출력한다.

주요 포인트 및 생각해볼 점

쉽게 해결할 수 있는 능력을 기르고 싶다

프로그래머스 level 1 시저암호

|

걸린 시간 : 30분

문제 설명

어떤 문장의 각 알파벳을 일정한 거리만큼 밀어서 다른 알파벳으로 바꾸는 암호화 방식을 시저 암호라고 합니다. 예를 들어 “AB”는 1만큼 밀면 “BC”가 되고, 3만큼 밀면 “DE”가 됩니다. “z”는 1만큼 밀면 “a”가 됩니다. 문자열 s와 거리 n을 입력받아 s를 n만큼 민 암호문을 만드는 함수, solution을 완성해 보세요.

  • 공백은 아무리 밀어도 공백입니다.
  • s는 알파벳 소문자, 대문자, 공백으로만 이루어져 있습니다.
  • s의 길이는 8000이하입니다.
  • n은 1 이상, 25이하인 자연수입니다.

입출력 예

s n result
"AB" 1 "BC"
"z" 1 "a"
"a B z" 4 "e F d"

사고과정

아스키코드와 관련함수를 이용해야할 것 같아서 바로 검색을 해봤다.

chr() 함수는 숫자를 인자로 주면 아스키 코드에 해당하는 문자를 반환하며 (아스키 코드 -> 문자)
ord() 함수는 문자의 아스키 코드를 반환한다. (문자 -> 아스키 코드)
출처 : 데이터사이언스 블로그

print( ord('A'), ord('Z'), ord('a'), ord('z')) 
# 65, 90, 97, 122 대문자 소문자 알파벳의 아스키코드값
answer = []

for i in range(len(s)):
  if s[i].isupper: #대문자 아스키 코드값이 65이상 90이하
    if ord(s[i])+n > 90: #n만큼 움직인게 Z를 넘어가면
      answer.append(chr(ord(s[i])-26+n)) 
      #26을 빼서 한 바퀴 돌려준다
    else:
      answer.append(chr(ord(s[i])+n))
  else s[i].islower: #소문자 아스키 코드값 97이상 122이하
    if ord(s[i])+n > 122:
      answer.append(chr(ord(s[i])-26+n))
    else:
      answer.append(chr(ord(s[i])+n))
  else:
    answer.append(' ')
return ''.join(answer)

모범답안

def caesar(s,n):
  s = list(s)
  for i in range(len(s)):
    if s[i].isupper():
      s[i]=chr((ord(s[i])-ord('A')+n)%26 + ord('A'))
      #26으로 나눈 나머지만큼을 A(65)에 더해준다!
    elif s[i].islower():
      s[i]=chr((ord(s[i])-ord('a')+n)%26 + ord('a'))
  return "".join(s)

프로그래머스 level 1 소수 찾기

|

걸린 시간 : 3시간

문제 설명

1부터 입력받은 숫자 n 사이에 있는 소수의 개수를 반환하는 함수, solution을 만들어 보세요.
소수는 1과 자기 자신으로만 나누어지는 수를 의미합니다. (1은 소수가 아닙니다.)

입출력 예

n result
10 4
5 3

사고과정

2부터 해당 숫자까지 나누어보고 나누어지지 않으면 소수로 판단한다.

제출답안

def isPrime(n): 
    for i in range(2,n): 
        if n % i == 0: 
            return False 
    return True 

def solution(num):
    ans = []
    for i in range(2, num+1):
        if isPrime(i):
            ans.append(i)
    return len(ans)

시간초과로 정확성테스트 10,11,12번와 효율성 테스트를 전부 실패했다.

소수 판별하는 부분을 제곱근 이용하는 방법으로 바꿨는데 런타임에러로 실패한다.

def isPrime(n): 
    if n == 1 :
        return False
    for div in range(2,int(math.sqrt(n))+1):
        if n%div == 0:
            return False
    return True

질문하기에 보니까 에라스토테네스의 체를 써야 통과할 수 있다고한다.

def solution(n):
	# 2부터 n까지의 숫자 배열 만들기
    num_set = set(range(2, n+1))

    for i in range(2, n+1): # 2~n까지의 수 i 에 대해
        if i in num_set: #i 가 num 안에 있으면
            num_set -= set(range(i*2, n+1, i))
             #i의 배수set(i의 2배수 부터 n까지수 중 i 만큼 간격있는 수들)를 num에서 빼주기
    answer = len(num_set)
    return answer

주요 포인트 및 생각해볼 점

어제 소수 리스트 만들기를 했으면서 오늘 더 간단한 소수 찾기를 하는데도 오래걸렸다.
어제 에라스토테네스의 체를 익혀서 내것으로 만들지 않은 까닭이다.
하지만 어제는 소수를 찾는 것만으로도 공부가 되었다고 생각했다.

프로그래머스 level 1 소수 만들기

|

걸린 시간 : 2시간

문제 설명

주어진 숫자 중 3개의 수를 더했을 때 소수가 되는 경우의 개수를 구하려고 합니다. 숫자들이 들어있는 배열 nums가 매개변수로 주어질 때, nums에 있는 숫자들 중 서로 다른 3개를 골라 더했을 때 소수가 되는 경우의 개수를 return 하도록 solution 함수를 완성해주세요.

입출력 예

nums result
[1,2,3,4] 1
[1,2,7,6,4] 4

사고과정

세 개의 숫자를 조합으로 뽑아서 더한 뒤 소수인지 판별한다.

제출답안

from itertools import combinations
#조합(중복을 허용하지 않고 순서를 고려하지 않는다)
def isPrime(n): #소수인지 판별하는 함수
    for i in range(2,n): #2부터 n까지 돌면서
        if n % i == 0: #n이 나누어지면 
            return False #거짓
    return True #나누어지지 않으면 참 반환

def solution(nums):
    sum_nums = []
    prime_nums = []
    for i in combinations(nums, 3): #숫자 3개를 뽑는다
        sum_nums.append(sum(i)) 
        #세 숫자의 합을 sum_nums에 넣는다.
        #주의 : 같은 숫자여도 다른 조합이기 때문에 세어준다. (set 사용 X)

    for num in sum_nums:
        if isPrime(num): #소수라면
            prime_nums.append(num)
            #prime_nums에 넣는다
       
    return len(prime_nums)
    #prime_nums의 갯수를 반환한다

주요 포인트 및 생각해볼 점

소수를 판별하는 algorithm은 시간복잡도에 따라 이런 방법들이 있었다. 출처: 코요님의 벨로그

  1. 그냥 나누기 (O(X))
  2. 제곱근까지 살펴보기 (O(X^1/2))
  3. 에라토스테네스의 체 (O(NloglogN))

소수를 판별하는 algorithm을 쓰는 과정에서 전에 약수를 구할 때 썼던 제곱근까지 살펴보는 개념이 나와서 뿌듯했다. 에라토스테네스의 체는 어떤 수의 배수를 모두 제거해나가면서 소수를 찾는 과정이다.

에라토스테네스의 체

소수만들기에서 사용했던 소수를 판별하는 함수(isPrime)와 소수리스트를 출력하는 함수(solution)를 한 개의 함수로 만들고 싶었는데 잘 안됐다.

오늘은 위코드 의 첫날이었는데 앞으로 함께 팀프로젝트를 진행하고 동기들과 친해질 생각에 기대가 된다.

프로그래머스 level 1 예산

|

걸린 시간 : 1시간

문제 설명

S사에서는 각 부서에 필요한 물품을 지원해 주기 위해 부서별로 물품을 구매하는데 필요한 금액을 조사했습니다. 그러나, 전체 예산이 정해져 있기 때문에 모든 부서의 물품을 구매해 줄 수는 없습니다. 그래서 최대한 많은 부서의 물품을 구매해 줄 수 있도록 하려고 합니다.

물품을 구매해 줄 때는 각 부서가 신청한 금액만큼을 모두 지원해 줘야 합니다. 예를 들어 1,000원을 신청한 부서에는 정확히 1,000원을 지원해야 하며, 1,000원보다 적은 금액을 지원해 줄 수는 없습니다.

부서별로 신청한 금액이 들어있는 배열 d와 예산 budget이 매개변수로 주어질 때, 최대 몇 개의 부서에 물품을 지원할 수 있는지 return 하도록 solution 함수를 완성해주세요.

입출력 예

d budget result
[1,3,2,5,4] 9 3
[2,2,3,3] 10 4

사고과정

예산신청 리스트를 정렬해서 작은 수부터 더해준다.
더한 값이 예산보다 커질때까지 더해주고 카운트를 출력한다.

def solution(d, budget):
    d.sort()
    count = 0
    sum = 0
    while sum < budget:
        for i in d:
            sum += i
            count += 1
    return count

이렇게 하면 [1,3,2,5,4], 9가 입력되었을 때 3이 나와야하는데 5가 나온다. sum을 for문 안에서 돌린 결과가 바깥의 while문에 적용되기를 바라는 것은 말이 안 되는 것 같다.

def solution(d, budget):
    d.sort()
    count = 0
    sum = 0
    for i in d:
        count += 1
        sum += i
        if sum == budget:
            return count
        if sum > budget:
            return count-1
        if sum < budget:
            if count == len(d):
                return count

코드를 이렇게 수정했는데 이번에는 테스트케이스 2,6,18,19번이 실패한다.
질문하기를 봤더니 부서가 하나인 경우도 생각해야한다고 한다.
juu님의 코드에 따라 아래 부분을 보충했다.

제출답안

def solution(d, budget):
    d.sort()
    count = 0
    sum = 0
    for i in d:
        count += 1
        sum += i
        if sum == budget:
            return count
        if sum > budget:
            return count-1
        if sum < budget:
            if count == len(d):
                return count

뭔가 개선의 여지가 많이 보이는 코드같다.

모범 답안

def solution(d, budget):
    d.sort()
    while budget < sum(d):
        d.pop()
    return len(d)

깔끔하고 좋은 코드라고 생각하는데 댓글에서는 sum()이 반복되어 시간 복잡도가 O(n^2)가 되니 매번 원소 값을 하나씩 빼는 편(O(n))이 낫겠다고 한다.

주요 포인트 및 생각해볼 점

시간복잡도 설명을 두번쯤 들은 것 같은데 실제로 적용해보기는 쉽지 않다.