문제

https://www.acmicpc.net/problem/1436

 

1436번: 영화감독 숌

666은 종말을 나타내는 숫자라고 한다. 따라서, 많은 블록버스터 영화에서는 666이 들어간 제목을 많이 사용한다. 영화감독 숌은 세상의 종말 이라는 시리즈 영화의 감독이다. 조지 루카스는 스타

www.acmicpc.net

 

Reference

https://hongcoding.tistory.com/108

 

[백준] 1436 영화감독 숌 (Python 파이썬)

https://www.acmicpc.net/problem/1436 1436번: 영화감독 숌 666은 종말을 나타내는 숫자라고 한다. 따라서, 많은 블록버스터 영화에서는 666이 들어간 제목을 많이 사용한다. 영화감독 숌은 세상의 종말 이라

hongcoding.tistory.com

https://pearlluck.tistory.com/523

 

[백준][python]1436.영화감독 숌 -완전탐색(브루트포스)

아래의 문제는 '백준'의 알고리즘 문제 내용이며 코드는 직접 푼 내용입니다. 1436.영화감독 숌 문제 및 입출력 입출력예시 나의시도 아ㅜ문제를 완전잘못이해했다. 어쩐지 너무 문제가 어이없다

pearlluck.tistory.com

 

풀이

실로 골때리는데 해결방안이 정말 의외인 문제. 아니 농담 아니고 진짜 그렇다니까?

 

참고로 666 다음이 1666 2666 3666 4666 5666이니까 이 다음은 6666이 올 것 같은가? 놉. 6660 6661 6662… 중간에 순서가 꼬인다. 근데 그때까지 영화가 안 망할 수 있을까 예제에 187 500 나오던데 여기까지 찍기 전에 감독님 죽어요… 

아무튼 처음에 시도했던 건 이거였다.

  1. 1부터 6660000(10000이 제일 큰 수)까지 중 666이 들어가는 수를
  2. 배열에 넣고
  3. 배열의 N-1번째 요소를 반환한다

그래서 이케 했지.

import sys
N = int(sys.stdin.readline())
terminate_list =[]
terminate_end = (666 * 10000) + 1

for i in range(1,terminate_end):
    if str(i).find('666') != -1:
        terminate_list.append(i)

print(terminate_list[N-1])

근데 IndexError가 날 반길 줄은 몰랐지. 그래서 방법을 찾아봤는데 어떻게 하느냐…

그냥 1부터 쭉 더해가면서 666이 들어가는 수를 찾으면 됨.

import sys
N = int(sys.stdin.readline())
count = 0
num = 1
while True: 
    if '666' in str(num):
        count += 1
    if count == N:
        print(num)
        break;
    num += 1

농담 아니고 진짜다.

'BOJ > [BOJ] Python' 카테고리의 다른 글

백준 10989번 풀이  (0) 2022.08.20
백준 2750번 풀이  (0) 2022.08.20
백준 1018번 풀이  (0) 2022.08.20
백준 25304번 풀이  (0) 2022.08.20
백준 3003번 풀이  (0) 2022.08.20

문제

https://www.acmicpc.net/problem/1018

 

1018번: 체스판 다시 칠하기

첫째 줄에 N과 M이 주어진다. N과 M은 8보다 크거나 같고, 50보다 작거나 같은 자연수이다. 둘째 줄부터 N개의 줄에는 보드의 각 행의 상태가 주어진다. B는 검은색이며, W는 흰색이다.

www.acmicpc.net

보드에서 최소한으로 재도색하면서 체스판을 만들 수 있는 가짓수는?

 

Reference

https://bambbang00.tistory.com/43

 

[BAEKJOON]백준 1018번: 체스판 다시 칠하기 파이썬

문제 지민이는 자신의 저택에서 MN개의 단위 정사각형으로 나누어져 있는 M*N 크기의 보드를 찾았다. 어떤 정사각형은 검은색으로 칠해져 있고, 나머지는 흰색으로 칠해져 있다. 지민이는 이 보

bambbang00.tistory.com

 

풀이

체스판은 검흰검흰이거나 흰검흰검으로 반복된다. …앞에앞에 문제에 기물 주운 애랑 합치면 체스 세트 완성인데? 아무튼. 근데 이제 브루트 포스맛이면 8*8을 다 훑어보면서 체크하게 된다.

import sys
N, M = map(int, sys.stdin.readline().split())
board = []
cut_board = []
for _ in range(N):
    row = sys.stdin.readline().strip()
    board.append(row)
# 전체 보드 만들기

for m in range(N-7):
    for n in range(M-7):
        board_index_1 = 0
        board_index_2 = 0
        for o in range(m, m+8):
            for p in range(n, n+8):
                if (o + p) % 2 == 0:
                    if board[o][p] != 'W':
                        board_index_1 += 1
                    if board[o][p] != 'B': 
                        board_index_2 += 1
                else: 
                    if board[o][p] != 'B':
                        board_index_1 += 1
                    if board[o][p] != 'W':
                        board_index_2 += 1
        cut_board.append(min(board_index_1,board_index_2))

print(min(cut_board))

이게 전체 코드. 보드는 원래 보드(줍줍한 보드), 컷보드는 8*8로 자른 상태이다. 근데 뭔 for문이 이렇게 많냐고? 일단 위에 있는거 말고 요 두놈을 보자.

  1. for m in range/for n in range
  2. for o in range/for p in range

두 for문은 각각

  1. 체스보드의 시작점(8*8로 자르는거지 주어진 게 8*8이 아님)
  2. 시작점~시작점+8(range는 a 이상 b 미만)까지를 range로 해서 확인

이렇게 되어 있다. 위에도 썼지만 체스판이 8*8이니까 그걸로 잘라서 만드는거지 주어지는 판이 8*8이라는 법은 없다. 그럼 밑에 If가 네갈래인 이유는요?

if (o + p) % 2 == 0:
    if board[o][p] != 'W':
    	board_index_1 += 1
	if board[o][p] != 'B':
    	board_index_2 += 1
else :
    if board[o][p] != 'B':
    	board_index_1 += 1
	if board[o][p] != 'W':
    	board_index_2 += 1

체스판의 시작점이 검정색일때와 흰색일 때, 각각 인접해야 하는 색이 다르다. 즉 흰색 옆에는 검정, 검정 옆에는 흰색이 와야 해서 if가 저렇게 갈라졌다고 보면 된다. 

'BOJ > [BOJ] Python' 카테고리의 다른 글

백준 2750번 풀이  (0) 2022.08.20
백준 1436번 풀이  (0) 2022.08.20
백준 25304번 풀이  (0) 2022.08.20
백준 3003번 풀이  (0) 2022.08.20
백준 7568번 풀이  (0) 2022.08.19

문제

https://www.acmicpc.net/problem/25304

 

25304번: 영수증

준원이는 저번 주에 살면서 처음으로 코스트코를 가 봤다. 정말 멋졌다. 그런데, 몇 개 담지도 않았는데 수상하게 높은 금액이 나오는 것이다! 준원이는 영수증을 보면서 정확하게 계산된 것

www.acmicpc.net

구매한 각 물건의 가격과 총합이 일치하는지 확인해야 한다.

 

풀이

야 우리집에서 이마트 털러 가도 26만원어치는 못사… 게임팩을 털어왔나 아무튼 그래요… 가랑비에 옷 젖는 줄 모른다는 말이 이럴 때 쓰는 말임…

아무튼 로직이 2단인데

  1. 다 더한다
  2. 비교한다

이게 다다.

import sys
total_price = int(sys.stdin.readline())
what_buy = int(sys.stdin.readline())
# 총합과 물건 종류 수
if_total = 0
for i in range(what_buy):
    price, amount = map(int, sys.stdin.readline().split())
    if_total += price * amount
# 더함
if if_total - total_price == 0:
    print("Yes")
else: 
    print("No")
# 끝

그래서 이게 다다. 엥? if에 왜 if_total – total_price == 0이 들어갔어요? 두개 똑같으면 뺐을때 0이잖음.

import sys
total_price = int(sys.stdin.readline())
what_buy = int(sys.stdin.readline())
# 총합과 물건 종류 수
if_total = 0
i = 1
while i <= what_buy:
    price, amount = map(int, sys.stdin.readline().split())
    if_total += price * amount
    i += 1
# 더함
if if_total - total_price == 0:
    print("Yes")
else: 
    print("No")
# 끝

While은 이거.

import sys
total_price = int(sys.stdin.readline())
what_buy = int(sys.stdin.readline())
# 총합과 물건 종류 수
if_total = 0
while True:
    try:
        price, amount = map(int, sys.stdin.readline().split())
        if_total += price * amount
    except:
        break
# 더함
if if_total - total_price == 0:
    print("Yes")
else: 
    print("No")
# 끝

While True는 트라이 익셉 주면 되는데, 이건 틀린다. 왜냐하면 입력할 개수를 지정해줬기 때문에 While True가 의미가 없기 때문.

'BOJ > [BOJ] Python' 카테고리의 다른 글

백준 1436번 풀이  (0) 2022.08.20
백준 1018번 풀이  (0) 2022.08.20
백준 3003번 풀이  (0) 2022.08.20
백준 7568번 풀이  (0) 2022.08.19
백준 2231번 풀이  (0) 2022.08.19

문제

https://www.acmicpc.net/problem/3003

 

3003번: 킹, 퀸, 룩, 비숍, 나이트, 폰

첫째 줄에 동혁이가 찾은 흰색 킹, 퀸, 룩, 비숍, 나이트, 폰의 개수가 주어진다. 이 값은 0보다 크거나 같고 10보다 작거나 같은 정수이다.

www.acmicpc.net

체스 기물의 수를 입력하면 몇 개가 모자라거나 남는지 출력하기.

 

풀이

일단 체스는 흑백의 킹, 퀸, 룩, 비숍, 나이트, 폰으로 이루어져 있다. 킹 하나, 퀸 하나, 룩/비숍/나이트 둘에 폰 여덟이라 16개. 그나저나 이 문제 if로 가야 하는 거 아님? 왜 여기 있음? 아니 if 안가도 됨…

일단 각 케이스를 보자.

0 1 2 2 2 7 -> 1 0 0 0 0 1
2 1 2 1 2 1 -> -1 0 0 1 0 7

1번 케이스: 킹과 폰이 하나씩 모자람

2번 케이스: 킹이 하나 남고 비숍, 폰이 각각 1개, 7개 모자람

그러니까 입력값과 출력값을 합해서 1 1 2 2 2 8이 나와야 한다.

import sys
king, queen, rook, bishop, knight, pawn = map(int, sys.stdin.readline().split())
king = 1 - king
queen = 1 - queen
rook = 2 - rook
bishop = 2 - bishop
knight = 2 - knight
pawn = 8 - pawn
print(king, queen, rook, bishop, knight, pawn)

그러므로 입력받은 기물의 수를 1 1 2 2 2 8에서 빼면 된다.

'BOJ > [BOJ] Python' 카테고리의 다른 글

백준 1018번 풀이  (0) 2022.08.20
백준 25304번 풀이  (0) 2022.08.20
백준 7568번 풀이  (0) 2022.08.19
백준 2231번 풀이  (0) 2022.08.19
백준 2798번 풀이  (0) 2022.08.19

문제

https://www.acmicpc.net/problem/7568

 

7568번: 덩치

우리는 사람의 덩치를 키와 몸무게, 이 두 개의 값으로 표현하여 그 등수를 매겨보려고 한다. 어떤 사람의 몸무게가 x kg이고 키가 y cm라면 이 사람의 덩치는 (x, y)로 표시된다. 두 사람 A 와 B의 덩

www.acmicpc.net

키와 몸무게를 이용해 덩치 등수를 산출한다.

 

Reference

https://bgspro.tistory.com/61

 

백준 알고리즘 7568: 덩치(Python)

www.acmicpc.net/problem/7568 7568번: 덩치 우리는 사람의 덩치를 키와 몸무게, 이 두 개의 값으로 표현하여 그 등수를 매겨보려고 한다. 어떤 사람의 몸무게가 x kg이고 키가 y cm라면 이 사람의 덩치는 (x,

bgspro.tistory.com

 

풀이

지금은 출석번호를 가나다순으로 매기지만(근데 대학 학번은 또 가나다순이 아님) 예에에에에에에에에전에는 키순으로 매겼었다. 나도 1학년때는 키순이었던걸로 기억함… 그래서 지금은 출석번호가 뒤쪽입니다 그러면 이름이 최씨 하씨 뭐 이런… 자음 순서에서도 뒤쪽이구나 하지만(보통 황씨, 황보씨가 맨 뒤) 예전같았으면 뭘 먹고 키가 그렇게 크셨어요? 했다.

당시에는 키가 비슷해보이는 애들끼리 비교했지만 이게 브루트 포스의 특성(개 노가다)과 맞물리면 이제 대단위 비교가 들어가게 된다. 


일단 입력 인자가 키랑 몸무게 두 개이다. 그러면 어캄? 아 어레이 불러야죠.

import sys
a = int(sys.stdin.readline().strip())
array = [[0 for i in range(a)] for j in range (a)]
for i in array:
    for j in i:
        print(j,end="")
    print()

Array는 이런 식으로 불러오는 게 맞다. 근데 키와 몸무게 두 개만 입력받는거고, 그 리스트를 a(입력값)만큼 층층이 쌓는… 그런 2차원 어레이를 만들거기때문에 컬럼값이 고정된다. 즉

import sys
a = int(sys.stdin.readline().strip())
array = [[0 for i in range(2)] for j in range (a)]
for i in array:
    for j in i:
        print(j,end="")
    print()

입력값이 n일 때, 우리는 2 * n 배열을 만들 것이다. (컬럼/로우)

import sys
a = int(sys.stdin.readline().strip())
array = [[list(map(int, sys.stdin.readline().split()))] for j in range (a)]
for i in array:
    for j in i:
        print(j,end="")
    print()

키랑 몸무게를 입력받을거니까 이렇게 해 주자. 다음으로는 덩치 등수를 매길건데… 일단 예시를 보자.

어? 왜 3등 아니고 5등임? 그건 문제에 나와있다. ‘덩치 등수는 나보다 덩치 큰 사람 k명 +1’이고, E는 5명 중 덩치가 제일 작으니까 자기보다 큰 사람이 4명 있어서 5등이다. 2등 세명은 씁 애매한데? 라고 보면 된다. 문제에서는 키와 몸무게 둘 다 클 때 덩치가 크다고 정의하고 있는데, 한 쪽은 크나 한 쪽이 작은 경우에는 비교하기 애매하기 때문. 체적을 비교하죠 어떻게요 아르키메데스좌 출동 

import sys
a = int(sys.stdin.readline().strip())
array = [list(map(int, sys.stdin.readline().split())) for j in range (a)]
ans = []
for i in range(a):
    count = 0
    for j in range(a):
        if array[i][0] < array[j][0] and array[i][1] < array[j][1]:
            count += 1
    ans.append(count+1)

for k in ans:
    print(k,end=" ")

그니까 나보다 큰 사람이 몇 명인가’만’ 세면 된다. 그 count를 배열에 넣으면 되고, 그 배열을 end=” “로 뽑으면 된다. 엥? 그거 공백 빼면 안돼요? end=””로 하면 숫자 다 붙어서 나온다.

11
1.2 55.5 #텅비드
2.4 333.6 #매시붕
1.8 25.0 #페로코체
3.8 100.0 #전수목
9.2 999.9 #철화구야
0.3 0.1 #종이신도
5.5 888.0 #악식킹
0.6 1.8 #베베놈(베베벱베)
3.6 150.0 #아고용
5.5 820.0 #차곡차곡(이름임)
1.8 13.0 #두파팡(대왕츕팝츕스)
7 4 7 4 1 11 2 10 4 2 7

float로 바꾸고 울비들 덩치 등수 매긴 결과. 순서는 도감 번호다. 울비들 덩치는 철화구야 > 악식킹/차곡차곡 > 매시붕/전수목/아고용 > 텅비드/페로코체/두파팡 > 베베놈 > 종이신도.

'BOJ > [BOJ] Python' 카테고리의 다른 글

백준 25304번 풀이  (0) 2022.08.20
백준 3003번 풀이  (0) 2022.08.20
백준 2231번 풀이  (0) 2022.08.19
백준 2798번 풀이  (0) 2022.08.19
백준 11729번 풀이  (0) 2022.08.19

문제

일단 생성자가 뭔지를 알아야 이 문제를 풀 수 있다. 분해합은 어떤 수와 그 자릿수를 더하는 것으로, 예를 들자면

255 + 2 + 5 + 5 = 267

이런 식으로 더하는 것. 이 때, 255는 267의 생성자이다. 이런 식으로 어떤 수와 그 수의 자릿수를 다 더했을 때 입력한 수가 되면 그게 생성자이다. 그럼 ‘제일 작은 생성자’가 뭔지는 알 것 같은데, 생성자가 없는 수도 있나요? 1이랑 255 없었음.

import sys
a = int(sys.stdin.readline().strip())
sum = 0
for i in range(1,a+1):
    print(i)

sum은 나중가면 다른걸로 바꾼다. 배열 합이 그래서… 아무튼 일단 입력받은 수까지 for문 뺑뺑이를 돌려보자.

import sys
a = int(sys.stdin.readline().strip())
result = 0
for i in range(1,a+1):
    A = list(map(int, str(i)))
    result = sum(A) + i
    if result == a:
        print(i)
        break
    elif i == a:
        print(0)

이게 답인데… 숫자를 배열하해서 각각의 자리수를 리스트에 넣고 그 리스트의 합(sum)을 구해 원래 수와 더하게 된다. 그걸 한땀한땀 정성스럽게 계산하다가 생성자가 나오면 출력하고 땡이고, 계산 다 해봤는데 i가 입력한 수까지 가게 되면 씁 이건 에반데가 된다. 그러면 여기서 여러분들은 한가지 궁금증이 생기게 된다.

 

그런데 break가 굳이 있어야 할 이유가 있나요? 사족 아니예요? 

 

문제에 이런 얘기가 있었다.

 

물론, 어떤 자연수의 경우에는 생성자가 없을 수도 있다. 반대로, 생성자가 여러 개인 자연수도 있을 수 있다.

그리고 생성자가 ‘없는’ 경우를 elif에서 처리했고, ‘가장 작은 생성자’에 대한 처리를 break로 했다고 보면 된다.

1024 # 입력한 값
998
1016

1024는 생성자가 두 개거든. 이런 경우 응애 애기출력초과! 가 여러분을 반길 수 있다.

import sys
a = int(sys.stdin.readline().strip())
result = 0
i = 1

while i <= a:
    A = list(map(int, str(i)))
    result = sum(A) + i
    if result == a:
        print(i)
        break
    elif i == a:
        print(0)
    i += 1

이건 While버전. for문은 1부터 입력값+1이고, while은 i가 입력값과 같아질때까지 계속 한다. if문에 break가 들어가는 건 똑같다.

'BOJ > [BOJ] Python' 카테고리의 다른 글

백준 3003번 풀이  (0) 2022.08.20
백준 7568번 풀이  (0) 2022.08.19
백준 2798번 풀이  (0) 2022.08.19
백준 11729번 풀이  (0) 2022.08.19
백준 2477번 풀이  (0) 2022.08.19

문제

https://www.acmicpc.net/problem/2798

 

2798번: 블랙잭

첫째 줄에 카드의 개수 N(3 ≤ N ≤ 100)과 M(10 ≤ M ≤ 300,000)이 주어진다. 둘째 줄에는 카드에 쓰여 있는 수가 주어지며, 이 값은 100,000을 넘지 않는 양의 정수이다. 합이 M을 넘지 않는 카드 3장

www.acmicpc.net

블랙잭… 진짜로 그냥 블랙잭이다. 카드 장 수와 마지노선, 그리고 카드가 주어질 때 카드 세 장의 합이 마지노선을 넘지 않으면서 제일 큰 수를 구하는 문제다.

 

Reference

https://go-coding.tistory.com/67

 

[Brute Force] 브루트 포스 설명과 간단 코테 풀이

브루트 포스(Brute Force) 알고리즘에서의 브루트 포스(Breute Force)에 관한 이야기 이다 공격기법 부르트 포스에 대한 이야기가 아니다. Brute Force Attack Brute : 난폭한 / Force : 힘 두 의미를 합하면 난폭.

go-coding.tistory.com

https://duwjdtn11.tistory.com/297

 

[Algorithm] [Python] BOJ/백준 - 2798_블랙잭

2798 - 블랙잭 문제 설명 카지노에서 제일 인기 있는 게임 블랙잭의 규칙은 상당히 쉽다. 카드의 합이 21을 넘지 않는 한도 내에서, 카드의 합을 최대한 크게 만드는 게임이다. 블랙잭은 카지노마

duwjdtn11.tistory.com

 

풀이

일단 마지노선 얘기가 왜 나왔냐면… 블랙잭의 룰이 그렇다. 블랙잭은 카드의 합이 21이 넘으면 지는 게임이기 때문. 즉, 카드 세 장의 숫자를 합쳤을 때 마지노선으로 주어진 수보다 크면 진다. 예시 입력에서 5 21이 주어졌을 때 답은 21이지만 실제로 다 계산해보면 24까지 나온다. 근데 24는 21보다 크니까 져요… 

import sys
card,maginot = map(int,sys.stdin.readline().split())
card_list = list(map(int,sys.stdin.readline().split()))
result = 0

for i in range(card):
    for j in range(i+1,card):
        for k in range(j+1,card):
            if card_list[i]+card_list[j]+card_list[k] > maginot:
                continue
            else: 
                result = max(result,card_list[i]+card_list[j]+card_list[k])
#이거 3중for로 되는거 실화? 

print(result)

이걸로 한방컷 실화…

'BOJ > [BOJ] Python' 카테고리의 다른 글

백준 7568번 풀이  (0) 2022.08.19
백준 2231번 풀이  (0) 2022.08.19
백준 11729번 풀이  (0) 2022.08.19
백준 2477번 풀이  (0) 2022.08.19
백준 2447번 풀이  (0) 2022.08.19

문제

https://www.acmicpc.net/problem/11729

 

11729번: 하노이 탑 이동 순서

세 개의 장대가 있고 첫 번째 장대에는 반경이 서로 다른 n개의 원판이 쌓여 있다. 각 원판은 반경이 큰 순서대로 쌓여있다. 이제 수도승들이 다음 규칙에 따라 첫 번째 장대에서 세 번째 장대로

www.acmicpc.net

하노이의 탑에서 이동 경로와 최종 이동 횟수를 출력하시오. (입력: 원판 개수)

 

Reference

https://ko.wikipedia.org/wiki/하노이의_탑

 

하노이의 탑 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 하노이의 탑(Tower of Hanoi)은 퍼즐의 일종이다. 세 개의 기둥과 이 기둥에 꽂을 수 있는 크기가 다양한 원판들이 있고, 퍼즐을 시작하기 전에는 한 기둥에 원판들

ko.wikipedia.org

https://shoark7.github.io/programming/algorithm/tower-of-hanoi

 

'하노이의 탑' 이해하기

'하노이의 탑' 문제를 이해하고 문제 해결을 위한 핵심 통찰을 살핀 뒤 코드로 작성합니다. 이후 탑의 개수에 따른 총 이동 횟수를 구하는 일반항까지 수학적으로 유도합니다.

shoark7.github.io

https://8iggy.tistory.com/100

 

재귀 함수 - 하노이의 탑

읽기 전 불필요한 코드나 잘못 작성된 내용에 대한 지적은 언제나 환영합니다. 개인적으로 사용해보면서 배운 점을 정리한 글입니다. PS 문제를 풀다보니 stack 학습 이후 재귀 풀이에 극도로 취

8iggy.tistory.com

https://soohyun6879.tistory.com/m/190

 

[백준/Python] 하노이 탑 이동 순서

https://www.acmicpc.net/problem/11729 11729번: 하노이 탑 이동 순서 세 개의 장대가 있고 첫 번째 장대에는 반경이 서로 다른 n개의 원판이 쌓여 있다. 각 원판은 반경이 큰 순서대로 쌓여있다. 이제 수도승

soohyun6879.tistory.com

 

풀이

정말 의외로 심플하게 풀렸다. (실화)

 

하노이의 탑? 

퍼즐 게임이다. 막대기 세 개와 크기가 다른 원판이 있고 이걸 맨 끝에 있는 막대로 옮기면 되는데 규칙이 있다.

1. 원판은 한번에 하나씩 옮길 수 있다.
2. 원판은 맨 위에 것만 옮길 수 있다.
3. 큰 원판이 작은 원판 위에 오면 안된다.


참 쉽죠?

def hanoi(number_of_disks_to_move, from_, to_, via_):
    if number_of_disks_to_move == 1:
        print(from_, "->", to_)
    else:
        hanoi(number_of_disks_to_move-1, from_, via_, to_)
        print(from_, "->", to_)
        hanoi(number_of_disks_to_move-1, via_, to_, from_)

하노이의 탑 코드를 보면 이렇게 입력 인자가 4개이다. (원판 개수, 어디서, 어디로, 어디를 통해) 개수 말고 세 개는 시작 기둥, 끝 기둥, 보조 기둥이다. 그럼 입력인자 다이어트가 되나요?

import sys
a = int(sys.stdin.readline())
def hanoi(n,start,to,via):
    if n == 1:
        print('{} {}'.format(start,to))
    else:
        hanoi(n-1,start,via,to)
        print('{} {}'.format(start,to))
        hanoi(n-1,via,to,start)
print(2 ** a - 1)
hanoi(a,1,3,2)

아뇨 그냥 입력인자 중 기둥을 고정값으로 두시면 됩니다. 이동 횟수도 재귀하면서 셀 필요 없이 2^n-1 하면 된다. (n=원판 개수)

'BOJ > [BOJ] Python' 카테고리의 다른 글

백준 2231번 풀이  (0) 2022.08.19
백준 2798번 풀이  (0) 2022.08.19
백준 2477번 풀이  (0) 2022.08.19
백준 2447번 풀이  (0) 2022.08.19
백준 17478번 풀이  (0) 2022.08.19

진행 상황: 회원가입(완료)/로그인(로직만 완료)


전에도 얘기했듯, 쇼핑몰에서 ‘모든 고객의 주문 내역을 볼 수 있는’건 물건을 파는 사람이다. 다른 고객들은 자기 주문만 볼 수 있다. 내가 옆집 김씨가 뭘 시켰는지 모르고, 뒷집 박씨는 내가 뭘 시켰는지 모르는것처럼. 그래서 나중에 페이지 하단에 있는

얘는 관리자 계정으로 로그인해야만 볼 수 있게끔 할 예정이다. 콩둘기 메일의 상태가?

 

회원가입

로그인과 회원가입을 모달창에서 받는다는 얘기는 전에 했는데, 그럼 어떤 정보를 받느냐…

아이디, 비밀번호, 비밀번호 확인, 이름, 전화번호, 이메일, 주소 받는다. 타임스탬프도 있긴 한데, 그건 입력받는 건 아니고 가입년월일이다.

function account_make() {
    let id = $('#userID').val()
    let password = $('#password').val()
    let password_check = $('#password-confirm').val()
    let username = $('#username').val()
    let phone = $('#userphone').val()
    let email = $('#useremail').val()
    let zipcode = $('#userzipcode').val()
    let useraddr = $('#useraddr').val()
    let detailaddr = $('#useraddr-detail').val()
    let phone_valid = RegExp(/^\d{2,3}-\d{4}-\d{4}/)
    let name_space = RegExp(/^[가-힣]{2,8}$/)
    let mail_valid = RegExp(/^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-Za-z0-9\-]+/)
    let password_valid = RegExp(/^[A-Za-z0-9]{8,20}/)

    let date = new Date()
    let Weekday = new Array(7)
    Weekday[0] = 'Sun'
    Weekday[1] = 'Mon'
    Weekday[2] = 'Tue'
    Weekday[3] = 'Wed'
    Weekday[4] = 'Thu'
    Weekday[5] = 'Fri'
    Weekday[6] = 'Sat'
    let year = date.getFullYear()
    let month = date.getMonth() + 1
    let day = date.getDate()
    let yoil = Weekday[date.getDay()]
    let timestamp = year + '-' + month + '-' + day + '-' + yoil


    if (id.length == 0) {
        $('#reg_userid_blank').css('display', 'block')
        $('#userID').css('border', '2px solid #cc0000')
    } else if ($('#fail').css('display') == 'block') {
        $('#reg_userid_needcheck').css('display', 'block')
        $('#userID').css('border', '2px solid #cc0000')
    }

    if (password != password_check) {
        $('#reg_passwd_nomatch').css('display', 'block')
        $('#password').css('border', '2px solid #cc0000')
        $('#password_check').css('border', '2px solid #cc0000')
    } else if (password.length == 0) {
        $('#reg_passwd_blank').css('display', 'block')
        $('#password').css('border', '2px solid #cc0000')
    } else if (password_check.length == 0) {
        $('#reg_passwd_blank').css('display', 'block')
        $('#password-confirm').css('border', '2px solid #cc0000')
    } else if (!password_valid.test(password)) {
        $('#reg_passwd_invalid').css('display', 'block')
        $('#password').css('border', '2px solid #cc0000')
    }

    if (!name_space.test(username)) {
        $('#reg_name-invalid').css('display', 'block')
        $('#username').css('border', '2px solid #cc0000')
    } else if (username.length == 0) {
        $('#reg_name-blank').css('display', 'block')
        $('#username').css('border', '2px solid #cc0000')
    }

    if (!phone_valid.test(phone)) {
        $('#reg_phone-invalid').css('display', 'block')
        $('#userphone').css('border', '2px solid #cc0000')
    } else if (phone.length == 0) {
        $('#reg_phone-required').css('display', 'block')
        $('#userphone').css('border', '2px solid #cc0000')
    }

    if (!mail_valid.test(email)) {
        $('#reg_email-invalid').css('display', 'block')
        $('#useremail').css('border', '2px solid #cc0000')
    } else if (email.length == 0) {
        $('#reg_email-required').css('display', 'block')
        $('#useremail').css('border', '2px solid #cc0000')
    }

    if ($('#pass').css('display') == 'block') {
        $.ajax({
            type: "POST",
            url: "/register",
            data: {
                'timestamp_give': timestamp,
                'userid_give': id,
                'userpasswd_give': password,
                'username_give': username,
                'useremail_give': email,
                'userphone_give': phone,
                'userzip_give': zipcode,
                'useraddr_give': useraddr,
                'userdetailaddr_give': detailaddr
            },
            success: function (response) {
                alert(response['msg'])
                window.location.reload()
            }
        })
    }
    else {
        alert('아이디 중복확인을 하셨나요? ')
    }

}

유효성검사 진짜 개노가다 맞음… 타임스탬프는 가입년월일이라서, ‘이 사람이 가입한 날짜’ 정보를 Day()를 이용해 조합한 다음 자바스크립트쪽에서 같이 보내준다. 근데 우리가 가입할 때 유효성검사 하는 것 중에 그것도 있음. 아이디 중복검사. 그죠? 그니까 OK하는 조건이 크게

  1. 아이디 중복이 없고
  2. 모든 폼을 다 채웠을 때

이다. AND임. 그럼 ID 중복을 어떻게 체크하는가? 그것은 간단하다.

  1. 자바스크립트에서 파이썬으로 아이디를 넘겨준다. (이거 확인좀)
  2. 파이썬은 몽고DB를 찾는다. (이거 찾아봐)
  3. 몽고DB에서 찾은 결과를 확인한다. (없네?)
  4. 파이썬에서 거기에 대한 처리를 하고 그 값을 반환한다. (없어 쓰라그래)
  5. 자바스크립트는 그 값을 토대로 처리한다. (써도 된대)

대충 이런 식이다. 

@app.route('/check_id',methods=['POST'])
def idcheck():
    id_check_result = ''
    id_receive = request.form['idcheck_give']
    id_find = list(db.register.find({'login id':id_receive}, {'_id': False,'login id':True}))
    if len(id_find) != 0:
        id_check_result = 'Fail'
    else:
        id_check_result = 'Pass'
    return jsonify({'idcheck_result': id_check_result})

JS에서 아이디를 넘겨주면 그걸 찾은 결과값을 처리해서 Pass of Fail을 보내준다. 몽고DB에서 조회한 결과가 리스트로 오게 되는데, 중복되는 ID가 있다면 리스트의 길이가 0이 아니게 된다. (없으면 리스트 안에 암것도 없음) 그래서 리스트 길이가 0이면 Pass, 아니면 Fail.

function idcheck() {
    let id = $('#userID').val()
    let id_test = RegExp(/^[A-Za-z0-9_-]{6,20}$/)
    if (!id_test.test(id)) {
        $('#userid_invalid').css('display', 'block')
        $('#userID').css('border', '2px solid #cc0000')
    } else {
        $.ajax({
            type: "POST",
            url: "/check_id",
            data: {'idcheck_give': id},
            success: function (response) {
                let check_result = response['idcheck_result']
                if (check_result == 'Fail') {
                    alert('이미 존재하는 아이디입니다. ')
                    $('#fail').css('display', 'block')
                    $('#pass').css('display', 'none')
                } else if (check_result == 'Pass') {
                    alert('아이디를 사용하셔도 됩니다!')
                    $('#fail').css('display', 'none')
                    $('#pass').css('display', 'block')
                }
            }
        })
    }

}

JS에서는 ID를 보냈을 때 돌아온 결과를 토대로 아이콘을 보여준다. 위 사진을 보면 아이디 입력란에 X가 있는데, 중복되지 않는 아이디를 입력하면 초록색 체크로 바뀐다.

 

아 그러면 끝인가요? ㄴㄴ 한가지가 더 있다. 비밀번호는 중요하기때문에 이게 털리면 X된다고 보면 된다. 그니까 제발 12345 이런거 하지 말자. 아무튼… 비밀번호를 DB에 쌩으로 저장하는 게 아니라 암호화를 해서 저장할건데, 여기서는 hashlib을 써볼거다.

import hashlib

부르자. 

m = hashlib.sha256()
m.update(userpasswd_receive.encode('utf-8'))
userpasswd_receive_hash = m.hexdigest()

그리고 입력받은 비밀번호를 해싱하고 

'password':userpasswd_receive_hash

해싱한 비밀번호를 저장한다. 

짜잔

 

참고로 이렇게 저장하게 되면 비번 까먹으면 망한다. 저걸 원래대로 되돌릴 수가 없거든… 그리고 로그인 할 때도 비밀번호 일치 여부를 확인하기 위해 사용자가 입력한 비밀번호를 해시화한 다음 대조하게 된다.

 

로그인

일단 로그인은 사용자가 ‘로그아웃을 하기 전까지’ 그 상태가 유지된다. 로그아웃 버튼을 누르건 창을 닫건 사용자가 자의로 로그아웃 하기 전까지 유지되는데, 이거는 일단 토큰 관련 처리가 필요하고… 여기서는 로그인 로직만 일단 구현해보자.

바에 이렇게 세 개가 있는데, 얘가 토큰 유무에 따라 로그인과 로그아웃만 보이게 된다. 토큰이 있으면 로그아웃, 토큰이 없으면 로그인.

 

로그인 모달창은 구조도 간단하고, 서버를 거치지 않는 유효성 검사도 공란인가만 보면 된다.

 

아이디랑 비밀번호 일치 여부는 서버를 거쳐야 하기 때문에 여기서는 일단 패스.

function login_user() {
    let id = $('#loginID').val()
    let password = $('#loginPW').val()
    console.log(id, password)
    if (id.length == 0) {
        $('#login-id-required').css('display', 'block')
        $('#loginID').css('border', '2px solid #cc0000')
    }
    if (password.length == 0) {
        $('#login-pw-required').css('display', 'block')
        $('#loginPW').css('border', '2px solid #cc0000')
    }
}

이제 Ajax를 끼얹어보자.

$.ajax({
    type: "POST",
    url: "/login",
    data: {
        'login_id_give': id,
        'login_pw_give': password
    },
    success: function (response) {
        console.log(response)
    }
})

Ajax를 통해 플라스크로 보내는 건 아이디랑 비밀번호가 끝이다. 그러면 서버에서

@app.route('/login',methods=['POST'])
def login_user():
    login_id_receive = request.form['login_id_give']
    login_pw_receive = request.form['login_pw_give']
    m = hashlib.sha256()
    m.update(login_pw_receive.encode('utf-8'))
    login_pw_hash = m.hexdigest()
    id_find = list(db.register.find({'login id':login_id_receive}, {'_id': False,'login id':True}))
    pw_find = list(db.register.find({'password':login_pw_hash}, {'_id': False,'password':True}))

    if len(id_find) == 0:
        msg = '존재하지 않는 아이디입니다!'
    elif len(pw_find) == 0:
        msg = '비밀번호가 일치하지 않습니다! '
    else:
        name_find = list(db.register.find({'login id': login_id_receive}, {'_id': False, 'name': True}))
        name_find = name_find[0]['name']
        msg = '{}님 어서요세요!'.format(name_find)
    return jsonify({'msg': msg})

비밀번호와 아이디를 DB에서 검색해서 일치 여부에 따른 메시지를 반환하게 되고, JS에서는 그 메시지를 그대로 띄우면 된다.

그니까 이런거지.

쇼핑몰이요? 그거 로그인은 둘째치고 SSL 설정 개꼬여서 걍 인스턴스 엎고 만들라고… 아니 nginx 깔았는데 uWSGI에서 막혀서 하루종일 그거 찾았다니까. 아무도 안 알려줘 그걸…

 

깝깝해서 오라클 클라우드도 알아봐야 하나 생각중임 지금..


Reference

https://woojong92.tistory.com/entry/JS-%EB%B0%94%EB%8B%90%EB%9D%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A1%9C-ToDo-List-%EB%A7%8C%EB%93%A4%EA%B8%B0-1-%EA%B8%B0%EB%8A%A5%EC%A0%95%EC%9D%98-%EB%B0%8F-HTMLCSS

 

[JS] 바닐라 자바스크립트로 ToDo-List 만들기 - (1) 기능정의 및 HTML/CSS

목차 기능정의 및 HTML/CSS 할 일 추가하기 할 일 목록에서 할 일 삭제, 완료 처리 구현 할 일 수정하기 전체 완료 처리 및 남은 할 일 개수 하단 버튼 기능 구현 바닐라 자바스크립트를 이용해 todo-l

woojong92.tistory.com

단계별로 시리즈가 나뉘어져 있는데, 잘 따라하면 기본 기능정도는 구현할 수 있다.

 

To-do list의 CRUD

전에 쇼핑몰 얘기 하면서 설명했던 CRUD에 대해 잠깐 짚고 넘어가보자. CRUD는 생성/열람/수정/삭제 네 가지라고 했는데… 아니 거기도 크루드가? ㅇㅇ 있음. 어지간한 프로그램에는 다 있다. 여기서 구현할 최소한의 요소이기도 하고… 전에 유기체의 4대 요소(탄수화물/지질/단백질/핵산) 얘기하면서 비슷한거라고 했는데 ㄹㅇ 비슷하다.

 

그럼 여기서 CRUD는 뭘까? 

  1. Create: 할 일을 추가한다 
  2. Read: 추가한 할 일을 읽는다 
  3. Update: 할 일의 내용이나 상태를 수정한다
  4. Delete: 할 일을 삭제한다 

미리 스포하자면 3번이 제일 빡셌음. 

 

Create

할 일을 목록에 추가하는 기능이다. 일단 거두절미하고 짤로 보자.

아니 뭐야 이걸 어케해요!!! 아이 나도 했음. 

const todoText = document.querySelector('.todo-text')
const todoAdd = document.querySelector('.todo-add')
const todoDelete = document.querySelector('.delete')
const todoEdit = document.querySelector('.modify')
const todoCheck = document.querySelector('.checkbox')
const todoList = document.querySelector('.todo-list')

let todo = []
let id = 0;
//변수!! 

function setTodo(newTodo) {
    todo = newTodo
}

function getTodo() {
    return todo
}

function addTodo(text) {
    const newId = id++
    const newTodo = getTodo().concat({id: newId, isCompleted: false, content: text })
    setTodo(newTodo)
    displayTodo()
}

function displayTodo() {
    todoList.innerHTML = null;
        const allTodo = getTodo()

    allTodo.forEach(function(todo){
        const todoItem = document.createElement('li')
        const todoCheck = document.createElement('div')
        const todoCont = document.createElement('div')
        const todoBtn = document.createElement('div')
        const todoEdit = document.createElement('button')
        const todoDel = document.createElement('button')

        todoItem.classList.add('todo-item')
        todoCheck.classList.add('checkbox')
        todoCont.classList.add('todo')
        todoBtn.classList.add('todo-button-group')
        todoEdit.classList.add('modify')
        todoDel.classList.add('delete')
        console.log(todoDel)
        todoCont.innerText = todo.content
        todoEdit.innerHTML = '<i class="fa-solid fa-pen"></i>'
        todoDel.innerHTML = '<i class="fa-solid fa-trash-can"></i>'

        if(todo.isCompleted) {
            todoItem.classList.add('checked');
            todoCheck.innerHTML = '<i class="fa-solid fa-check"></i>'
        }

        todoList.appendChild(todoItem)
        todoItem.appendChild(todoCheck)
        todoItem.appendChild(todoCont)
        todoItem.appendChild(todoBtn)
        todoBtn.appendChild(todoEdit)
        todoBtn.appendChild(todoDel)
    })
    
}

function init() {
    todoText.addEventListener('keypress',function(e){
        if (e.key === 'Enter') {
            addTodo(e.target.value);
            todoText.value ='';
        }
    })
}

init()

이게 문제의 코드. 근데 이렇게 했더니 추가가 이상하게 되는겨. 그래서 봤지.

<li class="todo-item">
<div class="checkbox"></div>
<div class="todo">삼시세끼 버터먹는 세토 혼내주기</div>
<div class="todo-button-group">
	<button class="modify"><i class="fa-solid fa-pen"></i></button>
	<button class="delete"><i class="fa-solid fa-trash-can"></i></button>
</div>
</li>

HTML 구조가 이렇게 되어 있는데, appendChild를 죄다 리스트에 줘버렸기 때문… 그러니 CSS도 적용이 안되고 개판 5분전인 리스트가 탄생한 것이다. (li가 아이템이고 ul이 리스트)

todoList.appendChild(todoItem)
todoItem.appendChild(todoCheck)
todoItem.appendChild(todoCont)
todoItem.appendChild(todoBtn)
todoBtn.appendChild(todoEdit)
todoBtn.appendChild(todoDel)

그래서 각각 구조에 맞게 appendChild를 다시 설정하니까 됐다. (박수)

 

Update/Delete

업데이트는 두개다. 하나는 상태를 수정하는것(다 했는지 아닌지)이고 하나는 내용을 수정하는 것. 근데 업데이트 어렵다면서요? 네, 내용 수정이요. 그럼 이번에도 짤로 한번 보자. 참고로 이번에 삭제와 함께 추가하는 업데이트는 상태 업데이트다. 

function deleteTodo(todoId) {
    const newTodo = getTodo().filter(todo => todo.id !== todoId )
    setTodo(newTodo)
    displayTodo()
}

function completeTodo(todoId) {
    const newTodo = getTodo().map(todo => todo.id === todoId ? {...todo,  isCompleted: !todo.isCompleted} : todo )
    setTodo(newTodo)
    displayTodo()
}

각 버튼에 이벤트 리스너를 추가할건데, 그 전에 이렇게 함수를 세팅하면 된다. 위에서 []로 선언했는데, 이게 뭔 소리냐면 할 일이 ‘배열’이라는 얘기다. 그래서 삭제는 삭제할 id를 제외한 다른 요소들을 보여주는 것. …근데 completed는 모르것음.

todoCheck.addEventListener('click',() => completeTodo(todo.id))
todoDel.addEventListener('click', () =>  deleteTodo(todo.id))

아무튼 각 버튼에 이벤트 리스너를 추가했다. click은 이 이벤트가 ‘클릭하면’ 실행되게 하는것이라고 보면 된다. Keypress는 키 입력. (추가가 Keypress이다)

function init() {
    todoText.addEventListener('keypress',function(e){
        if (e.key === 'Enter') {
            addTodo(e.target.value);
            todoText.value ='';
        }
    })
}

이게 추가 코드. 잘 보면 keypress가 있고 밑에 e.key === ‘Enter’가 있는데, 키(엔터키)를 누르면 이 이벤트를 실행해줘라 이런 얘기.

 

Update(내용 수정)

일단 이것도 짤로 보자.

일단 내용 수정을 위해서는 두 가지가 있어야 하는데

  1. 버튼을 누르면 수정할 내용 입력을 위한 input창을 호출하고(다른 부분을 누르면 닫고)
  2. 수정한 내용을 반영한다

이렇게 두 개이다. 

function modifyTodo(text, todoId) {
    const currentTodo = getTodo()
    const newTodo = currentTodo.map(todo => todo.id === todoId ? ({...todo, content: text}) : todo)
    setTodo(newTodo)
    displayTodo()
}

그럼 함수 소환해주시고…

function summonTodo(e, todoId) {
    const todoElem = e.target
    const inputText = e.target.innerText
    const todoItemElem = document.querySelector('.modify')
    const todoEdit = document.createElement('input')

    todoEdit.value = inputText
    todoEdit.classList.add('todo-text-edit')
    todoEdit.addEventListener('keypress',function(e){
        if (e.key === 'Enter') {
            modifyTodo(e.target.value, todoId)
            document.body.removeEventListener('click', onClickBody)
        }
    })

    const onClickBody = (e) => {
        if (e.target !== todoEdit) {
            todoItemElem.removeChild(todoEdit)
            document.body.removeEventListener('click', onClickBody)
        }
    }

    todoItemElem.appendChild(todoEdit)
    document.body.removeEventListener('click', onClickBody)
}

위쪽 if문은 엔터키가 눌렸을 때 수정한 내용을 반영하라는 얘기이고, 아래쪽에 const는 입력창이 아닌 다른 곳을 클릭하면 닫으라는 얘기다. todoEdit에 createElement를 주고 appendChild를 쓰면 원하는 곳에 생성할 수도 있다. 

todoEdit.addEventListener('dblclick',(event) => summonTodo(event,todo.id))

클릭하면 자꾸 1+1로 젠돼서 더블클릭 해놨음…


전체 코드

HTML

<html>

<head>
    <title>To-Do List</title>
    <link href="style.css" rel="stylesheet">
    <script src="https://kit.fontawesome.com/dc58858c96.js" crossorigin="anonymous"></script>
</head>

<body>
    <div class="todo-wrapper">
        <div class="todo-title">
            To-Do List
        </div>
        <div class="todo-body">
            <div class="todo-input">
                <input type="text" class="todo-text" placeholder="할 일을 입력하고 엔터키를 빡!!!(너무 세게 치면 컴퓨터 고장나요)">
                <button class="todo-add"><i class="fa-solid fa-plus"></i></button>
            </div>
            <div class="todo-post">
                <ul class="todo-list">
                    <!--<li class="todo-item checked">
                        <div class="checkbox"><i class="fa-solid fa-check"></i></div>
                        <div class="todo">니나브랑 발탄 찜갈비 먹기</div>
                        <div class="todo-button-group">
                            <button class="modify"><i class="fa-solid fa-pen"></i></button>
                            <button class="delete"><i class="fa-solid fa-trash-can"></i></button>
                        </div>
                    </li>
                    <li class="todo-item">
                        <div class="checkbox"></div>
                        <div class="todo">삼시세끼 버터먹는 세토 혼내주기</div>
                        <div class="todo-button-group">
                            <button class="modify"><i class="fa-solid fa-pen"></i></button>
                            <button class="delete"><i class="fa-solid fa-trash-can"></i></button>
                        </div>
                    </li>
                    <li class="todo-item">
                        <div class="checkbox"></div>
                        <div class="todo">알비온 밥주기</div>
                        <div class="todo-button-group">
                            <button class="modify"><i class="fa-solid fa-pen"></i></button>
                            <button class="delete"><i class="fa-solid fa-trash-can"></i></button>
                        </div>
                    </li>-->
                </ul>
            </div>
        </div>
        <p class="info">아, 근데 저장은 안됩니다. </p>
    </div>
    <script src="script.js"></script>
</body>

</html>

 

CSS

@font-face {
    font-family: 'DalseoHealingBold';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2207-01@1.0/DalseoHealingBold.woff2') format('woff2');
    font-weight: 700;
    font-style: normal;
}

* {
    margin: 0;
    padding: 0;
    font-family: 'DalseoHealingBold';
    font-size: 15pt;
    color: #2b2d42;
}

html {
    height: 100%;
}

body {
    display: flex;
    flex-wrap: nowrap;
    justify-content: center;
    background-color: #fcfcfc;
    min-height: 100%;
}

li {
    list-style-type: none;
}

button {
    background-color:transparent;
    border: 0;
}

.todo-wrapper {
    justify-content: center;
    margin-top: 3rem;
    min-width: 600px;
}

.todo-title {
    font-size:3em;
    text-align:center;
    padding:1em;
}

.todo-body {
    background-color: #fefefe;
}

.todo-input {
    display: flex;
    flex-wrap: nowrap;
    flex-direction: row;
    height: 3em;
    border: 1px solid #2b2d42;
    justify-content: center;
    align-items: center;
}

.todo-text {
    width: 80%;
    text-align: center;
    border: 0;
    outline: none;
    font-size: 1.3em;
    border-bottom:2px solid #E2DCC8;
    margin-right:20px;
}

.todo-text-edit {
    text-align: center;
    border: 0;
    outline: none;
    font-size: 1.1em;
    border-bottom:2px solid #E2DCC8;
    max-width:280px;
}

.todo-add {
    width: 1.3em;
    height: 1.3em;
    border-radius: 50px;
    cursor: pointer;
    font-size: 1.3em;
    border:2px solid #E2DCC8;
}

.todo-list {
    display:grid;
    grid-template-columns: repeat(3, 1fr);
    margin-top:1em;
    width:960px;
    gap: 10px;
}

.todo-item {
    background-color:#a9def9;
    padding:5px;
    box-shadow: 3px 3px 5px gray;
    border-radius:5px;
}

.todo-item:nth-child(2n) {
    background-color:#fcf6bd;
}

.checkbox {
    width: 1.5rem;
    height: 1.5rem;
    margin: 0.5rem 0.5rem;
    border-radius: 50px;
    border: 1px solid #2b2d42;
    cursor: pointer;
    text-align: center;
}

.checkbox > i {
    margin-top: 0.2em;
}

.todo-item .todo {
    padding:10px;
    font-size:1.1em;
}

.todo-item.checked .todo {
    font-style: italic;
    text-decoration: line-through;
    color:#354f52;
}

.todo-button-group {
    display:flex;
    flex-direction: row-reverse;
    flex-wrap: nowrap;
}

.modify, .delete {
    width: 1.3em;
    height: 1.3em;
    font-size:1.1em;
}

.info {
    text-align:center;
    margin-top:1.5em;
}

 

JS

const todoText = document.querySelector('.todo-text')
const todoAdd = document.querySelector('.todo-add')
const todoDelete = document.querySelector('.delete')
const todoEdit = document.querySelector('.modify')
const todoCheck = document.querySelector('.checkbox')
const todoList = document.querySelector('.todo-list')

let todo = []
let id = 0;
//변수!! 

function setTodo(newTodo) {
    todo = newTodo
}

function getTodo() {
    return todo
}

function addTodo(text) {
    const newId = id++
    const newTodo = getTodo().concat({id: newId, isCompleted: false, content: text })
    setTodo(newTodo)
    displayTodo()
}

function deleteTodo(todoId) {
    const newTodo = getTodo().filter(todo => todo.id !== todoId )
    setTodo(newTodo)
    displayTodo()
}

function completeTodo(todoId) {
    const newTodo = getTodo().map(todo => todo.id === todoId ? {...todo,  isCompleted: !todo.isCompleted} : todo )
    setTodo(newTodo)
    displayTodo()
}

function modifyTodo(text, todoId) {
    const currentTodo = getTodo()
    const newTodo = currentTodo.map(todo => todo.id === todoId ? ({...todo, content: text}) : todo)
    setTodo(newTodo)
    displayTodo()
}

function summonTodo(e, todoId) {
    const todoElem = e.target
    const inputText = e.target.innerText
    const todoItemElem = document.querySelectorAll('.todo')
    const todoEdit = document.createElement('input')

    todoEdit.value = inputText
    todoEdit.classList.add('todo-text-edit')
    todoEdit.addEventListener('keypress',function(e){
        if (e.key === 'Enter') {
            modifyTodo(e.target.value, todoId)
            document.body.removeEventListener('click', onClickBody)
        }
    })

    const onClickBody = (e) => {
        if (e.target !== todoEdit) {
            todoItemElem.removeChild(todoEdit)
            document.body.removeEventListener('click', onClickBody)
        }
    }

    todoItemElem[todoId].appendChild(todoEdit)
    document.body.removeEventListener('dblclick', onClickBody)
}

function displayTodo() {
    todoList.innerHTML = null;
        const allTodo = getTodo()

    allTodo.forEach(function(todo){
        const todoItem = document.createElement('li')
        const todoCheck = document.createElement('div')
        const todoCont = document.createElement('div')
        const todoBtn = document.createElement('div')
        const todoEdit = document.createElement('button')
        const todoDel = document.createElement('button')

        todoItem.setAttribute("data-id","todo.id")
        todoCheck.addEventListener('click',() => completeTodo(todo.id))
        todoDel.addEventListener('click', () => deleteTodo(todo.id))
        todoEdit.addEventListener('dblclick',(event) => summonTodo(event,todo.id))

        todoItem.classList.add('todo-item')
        todoCheck.classList.add('checkbox')
        todoCont.classList.add('todo')
        todoBtn.classList.add('todo-button-group')
        todoEdit.classList.add('modify')
        todoDel.classList.add('delete')
        todoCont.innerText = todo.content
        todoEdit.innerHTML = '<i class="fa-solid fa-pen"></i>'
        todoDel.innerHTML = '<i class="fa-solid fa-trash-can"></i>'

        if(todo.isCompleted) {
            todoItem.classList.add('checked');
            todoCheck.innerHTML = '<i class="fa-solid fa-check"></i>'
        }

        todoList.appendChild(todoItem)
        todoItem.appendChild(todoCheck)
        todoItem.appendChild(todoCont)
        todoItem.appendChild(todoBtn)
        todoBtn.appendChild(todoEdit)
        todoBtn.appendChild(todoDel)
    })
    
}

function init() {
    todoText.addEventListener('keypress',function(e){
        if (e.key === 'Enter') {
            addTodo(e.target.value);
            todoText.value ='';
        }
    })
}

init()

Profile

Lv. 34 라이츄

요즘 날씨 솔직히 에바참치김치꽁치갈치넙치삼치날치기름치준치학꽁치임..