웹 개발의 페러다임이 백엔드에서 프런트앤드로 옮겨오면서 예전보다 많은 코드가 서버가 아닌 브라우저에서 실행되고 있습니다. ex.*템플릿 렌더링
동적으로 렌더링하는 부분을 제외한 초기 페이지는 server-side 렌더링하고 동적인 부분만 client-side 렌더링
https://www.clien.net/service/board/park/5699595
데이터를 가져와서 화면을 만드는 것이 아닌
미리 선언되어있는 구조에서 템플릿으로 데이터를 전달(변경된 데이터를 감지하고 전달)
EventListener 중심
https://yozm.wishket.com/magazine/detail/1334/
서버가 어떤 데이터를 브라우저 측에 저장한 후 다시 그 데이터를 받아오는 기술, 또는 그 데이터 자체
클라이언트(브라우저)와 서버가 네트워크를 통해 쿠키를 주고받기 위해 HTTP 프로토콜을 만들었고, 이에 의해 쿠키는 HTTP 헤더에 담아 전송하기로 약속됨
클라이언트가 최초의 연결 요청을 보낼 때 서버가 쿠키를 set-cookie
에 담아 보내줌
Max-Age=0
Domain=test.com
을 명시하면 해당 도메인의 서브도메인까지 쿠키 범위 확장Path=/user
명시하면 해당 경로를 포함하는 url에 대해서만 쿠키를 공유Secure
속성을 명시하면 해당 쿠키는 https
프로토콜 상에서만 쿠키를 서버로 전송HttpOnly
브라우저에서 자바스크립트로 Document.cookie
를 통해 접근할 수 없음이후 요청시 클라이언트는 cookie
에 쿠키를 담아 보냄 (여러 개를 보낼 땐 ;로 구분해서 보낼 수 있음)
브라우저는 사용자가 www.test.com이라는 같은 도메인
에 머무는 한 /index.html을 방문하든 /about.html을 방문하든 /contact.html을 방문하든 매번 같은 쿠키를 돌려준다
➡️ 동일한 도메인에서는 요청시 전송하는 쿠키가 동일하다 ⭐⭐⭐⭐⭐⭐
🥛같은 도메인에서 같은 쿠키
(동일한 호스트)를 보내므로 http의 stateless 특성에도 불구하고 요청을 보낸 브라우저를 구분
할 수 있다
🥛로드 밸런서에 도움
소규모의 서비스에서 많은 사용자들이 서비스를 이용할 때 항상 같은 서버에 요청을 보낼 수가 없음
ex. 로그인 요청 서버≠게시글 작성 요청 서버
이 때 서버를 인식할 수 있도록 해당 브라우저가 어떤 서버에 요청을 보내면 될 지 set-cookie
에 serverId를 보내주면 이후에 계속 같은 서버로 요청을 보내는 것
이 가능하다
🥛세션 ID 저장
🥛좀비 쿠키
유효 기간을 엄청 길게 하거나 자바스크립트를 이용한 다른 편법들을 써서 아무리 브라우저에서 삭제를 해도 계속 부활하는 쿠키
🥛서드파티(third party) 쿠키
타 도메인을 상대로 적용되는 쿠키가 넘어오는 경우
사용자 맞춤 광고를 위한 추적에 활용
중요한 데이터를 쿠키를 사용해서 브라우저에 저장하면 유실되기 쉽다
서버에서는 브라우저로 부터 수신한 쿠키 데이터가 유효한지 검증할 필요가 있다
매 요청마다 같은 데이터가 서버로 전송됨에 따른 네트워크 대역폭 낭비
https://www.daleseo.com/http-cookies/
https://www.daleseo.com/http-session/
서버 자원이 풍족한 현재에는 순수하게 데이터를 클라이언트 측에 저장하기 위한 용도로써의 쿠키는 입자가 많이 좁아짐 → 웹 스토리지 기술
브라우저에 데이터를 저장하는 기술
문자열 데이터만 저장
다른 타입의 데이터를 문자형으로 변환해서 저장
JSON을 이용해서 저장시 stringify해서 저장하고 가져올 때 parse해서 읽어들이면 원본 데이터를 얻을 수 있다
> localStorage.setItem('sth', JSON.stringify({a: 1, b: 2}))
undefined
> JSON.parse(localStorage.getItem('sth'))
{a: 1, b: 2}
웹페이지의 세션이 끝날 때 저장된 데이터가 지워짐
브라우저에서 같은 웹사이트를 여러 탭이나 창에 띄우면, 여러 개의 세션 스토리지에 데이터가 서로 격리되어 저장되며, 각 탭이나 창이 닫힐 때 저장해 둔 데이터도 함께 소멸
웹페이지의 세션이 끝나더라도 데이터가 지워지지 않음
여러 탭이나 창 간에 데이터가 서로 공유되며 탭이나 창을 닫아도 데이터는 브라우저에 그대로 남아 있음
→ 로컬 스토리지의 데이터 영속성
(persistence)은 동일한 컴퓨터에서 동일한 브라우저를 사용할 때에 한정됨
https://www.daleseo.com/js-web-storage/
def solution(orders, course):
course_combi={} # 구성 메뉴 수: 가능한 조합 수
for c in course:
temp=[]
for order in orders:
if len(order)>=c:
order=''.join(sorted(order)) # 알파벳 순서에 따라서 조합이 분리되는 다르게 인식되는 경우 방지
temp.extend(list(combinations(order, c)))
course_combi[c]=temp
combi_freq=[] # 조합별 빈도수
for k in course_combi.keys():
combi_freq.append(dict(Counter(course_combi[k])))
answer = [] # 2이상이면서 max값인 경우 리턴
for k in combi_freq:
for kk in k.keys():
if k[kk]==max(list(k.values())) and k[kk]>=2:
answer.append(''.join(kk))
answer.sort() # 알파벳 순으로 정렬
return answer
코스를 구성하는 메뉴 수에 따라서 주문한 메뉴에서 combinations를 이용해서 조합해서 course_combi 딕셔너리의 밸류 값으로 넣어줬다. 이때 append를 하면 밸류 리스트 안에 order별로 리스트로 또 분리가 되어서 값을 쓰기가 힘들다. 아래 기념사진 참고
이후 만들어진 딕셔너리에서 조합 별 빈도수를 Counter를 이용해서 계산 후 딕셔너리로 바꿔서 리스트에 넣어줘서 리스트 안에 딕셔너리가 course 수만큼 들어가 있는 combi_freq를 완성했다
마지막으로 카운터로 정리한 리스트 안의 각각의 딕셔너리에서 가장 큰 값을 가진 메뉴를 뽑아야하므로 각 딕셔너리들의 밸류값 중 최댓값과 일치하는 값을 가진 키값을 answer에 append했다. 이 때 max는 조건에 따라 2이상이어야 한다
마지막으로 알파벳 순으로 정렬해주면 끝~~ 진짜 조합은 리스트가 너무 길게 나와서 토할 것 같다 ^^
#append 쓰는 경우
{2: [[('A', 'B'), ('A', 'C'), ('A', 'F'), ('A', 'G'), ('B', 'C'), ('B', 'F'), ('B', 'G'), ('C', 'F'), ('C', 'G'), ('F', 'G')], [('A', 'C')], [('C', 'D'), ('C', 'E'), ('D', 'E')], [('A', 'C'), ('A', 'D'), ('A', 'E'), ('C', 'D'), ('C', 'E'), ('D', 'E')], [('B', 'C'), ('B', 'F'), ('B', 'G'), ('C', 'F'), ('C', 'G'), ('F', 'G')], [('A', 'C'), ('A', 'D'), ('A', 'E'), ('A', 'H'), ('C', 'D'), ('C', 'E'), ('C', 'H'), ('D', 'E'), ('D', 'H'), ('E', 'H')]],
3: [[('A', 'B', 'C'), ('A', 'B', 'F'), ('A', 'B', 'G'), ('A', 'C', 'F'), ('A', 'C', 'G'), ('A', 'F', 'G'), ('B', 'C', 'F'), ('B', 'C', 'G'), ('B', 'F', 'G'), ('C', 'F', 'G')], [('C', 'D', 'E')], [('A', 'C', 'D'), ('A', 'C', 'E'), ('A', 'D', 'E'), ('C', 'D', 'E')], [('B', 'C', 'F'), ('B', 'C', 'G'), ('B', 'F', 'G'), ('C', 'F', 'G')], [('A', 'C', 'D'), ('A', 'C', 'E'), ('A', 'C', 'H'), ('A', 'D', 'E'), ('A', 'D', 'H'), ('A', 'E', 'H'), ('C', 'D', 'E'), ('C', 'D', 'H'), ('C', 'E', 'H'), ('D', 'E', 'H')]],
4: [[('A', 'B', 'C', 'F'), ('A', 'B', 'C', 'G'), ('A', 'B', 'F', 'G'), ('A', 'C', 'F', 'G'), ('B', 'C', 'F', 'G')], [('A', 'C', 'D', 'E')], [('B', 'C', 'F', 'G')], [('A', 'C', 'D', 'E'), ('A', 'C', 'D', 'H'), ('A', 'C', 'E', 'H'), ('A', 'D', 'E', 'H'), ('C', 'D', 'E', 'H')]]}
#extend 쓰는 경우
{2: [('A', 'B'), ('A', 'C'), ('A', 'F'), ('A', 'G'), ('B', 'C'), ('B', 'F'), ('B', 'G'), ('C', 'F'), ('C', 'G'), ('F', 'G'), ('A', 'C'), ('C', 'D'), ('C', 'E'), ('D', 'E'), ('A', 'C'), ('A', 'D'), ('A', 'E'), ('C', 'D'), ('C', 'E'), ('D', 'E'), ('B', 'C'), ('B', 'F'), ('B', 'G'), ('C', 'F'), ('C', 'G'), ('F', 'G'), ('A', 'C'), ('A', 'D'), ('A', 'E'), ('A', 'H'), ('C', 'D'), ('C', 'E'), ('C', 'H'), ('D', 'E'), ('D', 'H'), ('E', 'H')],
3: [('A', 'B', 'C'), ('A', 'B', 'F'), ('A', 'B', 'G'), ('A', 'C', 'F'), ('A', 'C', 'G'), ('A', 'F', 'G'), ('B', 'C', 'F'), ('B', 'C', 'G'), ('B', 'F', 'G'), ('C', 'F', 'G'), ('C', 'D', 'E'), ('A', 'C', 'D'), ('A', 'C', 'E'), ('A', 'D', 'E'), ('C', 'D', 'E'), ('B', 'C', 'F'), ('B', 'C', 'G'), ('B', 'F', 'G'), ('C', 'F', 'G'), ('A', 'C', 'D'), ('A', 'C', 'E'), ('A', 'C', 'H'), ('A', 'D', 'E'), ('A', 'D', 'H'), ('A', 'E', 'H'), ('C', 'D', 'E'), ('C', 'D', 'H'), ('C', 'E', 'H'), ('D', 'E', 'H')],
4: [('A', 'B', 'C', 'F'), ('A', 'B', 'C', 'G'), ('A', 'B', 'F', 'G'), ('A', 'C', 'F', 'G'), ('B', 'C', 'F', 'G'), ('A', 'C', 'D', 'E'), ('B', 'C', 'F', 'G'), ('A', 'C', 'D', 'E'), ('A', 'C', 'D', 'H'), ('A', 'C', 'E', 'H'), ('A', 'D', 'E', 'H'), ('C', 'D', 'E', 'H')]}
끝없는 리스트의 지옥을 간직하고 싶었다 기념사진임 ㅋㅅㅋ
조합별 빈도수 리스트를 만드는 부분의 for문이 위의 구성 메뉴수 별 가능한 조합수 만드는 부분이랑 똑같은 범위를 사용하고 있어서 합쳐줬다
course_combi
딕셔너리도 더이상 필요하지 않게됐다
combi_freq=[]
for c in course:
temp=[]
for order in orders:
if len(order)>=c:
order=''.join(sorted(order))
temp.extend(list(combinations(order, c)))
combi_freq.append(dict(Counter(temp)))
다른 사람 코드랑 비교해보니까 아랫 부분도 정리할 수 있을 것 같다....
이 for문도 첫번째 for문 안에 들어가서 총 3개로 분리되어있던 for문을 하나로 합치게 됐다 ㅎㅅㅎ
combi_freq=(dict(Counter(temp)))
for cf in combi_freq.keys():
if combi_freq[cf]==max(list(combi_freq.values())) and combi_freq[cf]>=2:
answer.append(''.join(cf))
완성본 🤓
from itertools import combinations
from collections import Counter
def solution(orders, course):
# combi_freq=[]
answer = []
for c in course:
temp=[]
for order in orders:
if len(order)>=c:
order=''.join(sorted(order))
temp.extend(list(combinations(order, c)))
combi_freq=(dict(Counter(temp)))
for cf in combi_freq.keys():
if combi_freq[cf]==max(list(combi_freq.values())) and combi_freq[cf]>=2:
answer.append(''.join(cf))
answer.sort()
return answer
combi_freq
라는 리스트도 필요하지 않게 되어 삭제했다
대신 combi_freq변수에 카운터값을 넣고 각 코스에 대한 combi_freq변수의 최댓값이 answer에 append되기 때문에 결과는 계속 누적되어 저장되기때문에 사실상 마지막 for문이 굳이 필요하지 않았던 거였다.
마지막 for문을 위해서 combi_freq 라는 리스트가 필요했던 건데 없어지니까 중복되는 데이터가 없어져서 더 좋은 것 같다
데이터가 어떤 리스트에 담겨서 정리된 상태일 때 처리하기가 더 쉬운 모양이라 이제까지는 그렇게 했는데 그러면 코드의 낭비도 있고 메모리의 낭비도 있을 것 같다
어차피 append는 누적값이니 개별적으로 처리해서 append할 수 있으면 그렇게 하는 편이 더 좋은 것 같다
SELECT B.BOOK_ID, A.AUTHOR_NAME, DATE_FORMAT(B.PUBLISHED_DATE,"%Y-%m-%d") AS PUBLISED_DATE
FROM BOOK B
JOIN AUTHOR A
ON B.AUTHOR_ID = A.AUTHOR_ID
WHERE B.CATEGORY = "경제"
ORDER BY B.PUBLISHED_DATE
날짜 형식 지정 DATE_FORMAT
AS
표시할 칼럼명