ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2024-01-06~2024-01-07
    스파르타/TIL(Today I Learned) 2024. 1. 8. 21:51
    더보기

    SQL 코드카타

     

    년, 월, 성별 별 상품 구매 회원 수 구하기(SQL) (날짜 year month, where)

    https://school.programmers.co.kr/learn/courses/30/lessons/131532

    USER_INFO 테이블과 ONLINE_SALE 테이블에서 년, 월, 성별 별로 상품을 구매한 회원수를 집계하는 SQL문을 작성해주세요. 결과는 년, 월, 성별을 기준으로 오름차순 정렬해주세요. 이때, 성별 정보가 없는 경우 결과에서 제외해주세요. 라는 문제이다.

    SELECT year(os.SALES_DATE) YEAR, month(os.SALES_DATE) MONTH,
            ui.GENDER GENDER , count(distinct os.user_id) USERS
    FROM USER_INFO ui join ONLINE_SALE os on ui.USER_ID = os.USER_ID
    WHERE ui.GENDER is not null
    GROUP BY year(os.SALES_DATE),month(os.SALES_DATE),ui.GENDER
    ORDER BY YEAR, MONTH, GENDER
    

    처음에는 살짝 무지성으로 성별 null값만 제외해주고 크게 신경 쓸게 없겠네 생각하고 쭉 작성하고 결과를 매겼는데 틀렸다고 떠서 뭘 빠트렸을까 생각하다가 다른 분들 제출한 결과의 데이터랑 비교해보니 내 데이터의 숫자가 조금 더 크길래 바로 눈치채고 user_id가(당연한 것 같지만)한달 내에도 여러번의 기록으로 남아있었고 따라서 중복된 user_id를 제외하고 세어줬어야 했다 따라서 위와 같이 수정하여 제출 하였고 제출완료 되었다(굳이 살짝 핑계를 대자면 문제에서 동일한 날짜, 회원 ID, 상품 ID 조합에 대해서는 하나의 판매 데이터만 존재한다고 되어있어서 단순히 회원 아이디에 대하여 하나 겠거니 하는 생각을 가졌다.(동일 날짜이지 동일한 달이 아니니 틀린말도 아니다) ).

    쿼리를 간단히 설명하자면 우선 USER_INFO테이블( 이하 ui )과 ONLINE_SALE테이블(이하 os)를 user_id를 공통컬럼으로 join해주었고, 그 뒤 GENDER의 값이 null로 되어있는 데이터는 제외하기 위하여 where절인 WHERE ui.GENDER is not null 를 통해 null값은 제외시켜주었다. 그리고 년(os.SALES_DATE), 월(os.SALES_DATE), 성별(ui.GENDER)로 그룹 지어주었으며, 컬럼으로 동일하게 년(os.SALES_DATE), 월(os.SALES_DATE), 성별(ui.GENDER)와 그에 따른 유저수를 보여주기 위해 count(distinct os.user_id)를 통해 해당 그룹에 속한 중복을 제외한 user_id의 총 갯수를 구해주었다. 그런 뒤 마지막으로 년, 월, 성별을 통해 오름차순 정렬해 주었다.

    아래는 처음 작성했던 쿼리이다.

    SELECT year(os.SALES_DATE) YEAR, month(os.SALES_DATE) MONTH,
            ui.GENDER GENDER , count(os.user_id) USERS
    FROM USER_INFO ui join ONLINE_SALE os on ui.USER_ID = os.USER_ID
    WHERE ui.GENDER is not null
    GROUP BY year(os.SALES_DATE),month(os.SALES_DATE),ui.GENDER
    ORDER BY YEAR, MONTH, GENDER, USERS
    

     

    서울에 위치한 식당 목록 출력하기(SQL) (round, join, like, select 서브쿼리,like, explain 속도 시간 성능 비교, 문법확인)

    https://school.programmers.co.kr/learn/courses/30/lessons/131118

    REST_INFO와 REST_REVIEW 테이블에서 서울에 위치한 식당들의 식당 ID, 식당 이름, 음식 종류, 즐겨찾기수, 주소, 리뷰 평균 점수를 조회하는 SQL문을 작성해주세요. 이때 리뷰 평균점수는 소수점 세 번째 자리에서 반올림 해주시고 결과는 평균점수를 기준으로 내림차순 정렬해주시고, 평균점수가 같다면 즐겨찾기수를 기준으로 내림차순 정렬해주세요. 라는 문제이다.

    SELECT info.REST_ID , info.REST_NAME , info.FOOD_TYPE ,
                info.FAVORITES ,info.ADDRESS,
                round(avg(rev.REVIEW_SCORE),2) SCORE
    FROM REST_INFO info join REST_REVIEW rev on info.REST_ID = rev.REST_ID
    WHERE info.ADDRESS like '서울%'
    GROUP BY info.REST_ID
    ORDER BY SCORE desc, info.FAVORITES desc
    

    이번 문제는 리뷰 평균을 내는 것 말고는 그다지 특별한 것이 없어서 쿼리 플로우? 설명은 적지않고 다른 이 쿼리를 작성하면서 들었는 생각 등을 적어보겠다

    우선 제일 처음에는 아직 작동 방식을 명확히 이해하지 못하여 그런듯하나, join을 일반적으로 다 하여 쿼리를 작성하고, 듣기로도 join을 하여 쿼리를 작성하는게 일반적으로 더 성능이 좋다고 들었던 것 같다. 흠 하지만 긴가민가한 생각으로 join을 하면 필요없는 부분까지 싹 가져와서 거기서 걸러서 가져오거나 하지만, 서브쿼리를 쓰면 따로 가져와서 필요한 부분은 쳐내고 가져와 쓰는게 아닌가 했지만 글을 쓰면서 다시 드는 생각은 전체적으로 join을 하거나 서브쿼리를 쓰거나 (적어도 이 문제 데이터에 관하여서 생각하면) 어차피 테이블을 둘다 가져와서 쓰이게 되는데 서브쿼리를 사용한다면 select문이 두번 연산하는 것이고, join을 하면 select문이 한번만 연산하기 때문에 join이 더 좋다는게 아닌가 싶기도하다. 자세한 것은 좀더 알아보거나 쿼리를 작성시 어떤식으로 연산이 진행되는지를 찾아봐야할 듯하다.

    그리고 문제 내용에 관련하여 따로 문제 내용에 리뷰평균이 null일 때 제외하라는 내용이 없었는데 제외를 해야만 정답이라는 것이 살짝 아쉬웠다. 물론 작성하면서도 그럼 리뷰가 없던 식당들도 있던데 걔들은 어떻게 처리해야하지 라는 생각은 했으나, 문제에 기입해 줬다면 좀 더 좋지 않았을까라는 생각을 하였다.

    그리고 마지막으로 나말고도 질문하기에 많은 사람들이 질문을 올렸던데 왜 서울%로만 해야하며 %서울%로 하면 틀렸다고 나오는지 모르겠다. 어떤 사람은 주소에 서울로 시작하는 주소가 아닌 식당 중에 서울이 포함된 내용이 있어서 그런게 아닌가라는 의견을 제시하였지만, 내가 일일히 모든 식당의 주소를 확인한 바로는 그런 식당이 없었던 것으로 기억한다. 그리고 만약 있더라도 필터링과정에서 출력되었을 것이기 때문에 출력된 내용에서 주소를 봤을 때 서울로 시작하지 않는 식당이 있어야 했는데 보지 못하였다 그래서 이 부분은 의문이 해소가 되지 않아서 튜터님께 질문 해보려고 한다.

    join 을 안하고 다른 방식으로 해본 내용이다

    SELECT a.REST_ID , a.REST_NAME , a.FOOD_TYPE ,
                a.FAVORITES , a.ADDRESS , a.SCORE
    FROM(
        SELECT info.REST_ID , info.REST_NAME , info.FOOD_TYPE ,
                info.FAVORITES ,info.ADDRESS,
                (SELECT round(avg(rev.REVIEW_SCORE),2)
                    FROM REST_REVIEW rev GROUP BY rev.REST_ID
                    HAVING rev.REST_ID = info.REST_ID) SCORE
        FROM REST_INFO info
        WHERE info.ADDRESS like '서울%'
        ORDER BY SCORE desc, info.FAVORITES desc)a
    where a.SCORE is not null
    

     그런데 이 부분 실수로 order 밖으로 안뺐는데도 그냥 정렬이 되어있던데 따로 따로 건들지 않고 고대로 가져다 쓰면 정렬 유지되는 건지 궁금하여 튜터님께 질문해서 확인해봐야 겠다.

    그리고 아래 쿼리는 제일 처음 했던 것인데 

    SELECT info.REST_ID , info.REST_NAME , info.FOOD_TYPE ,info.FAVORITES ,         info.ADDRESS,
            (SELECT round(avg(rev.REVIEW_SCORE),2)
                FROM REST_REVIEW rev GROUP BY rev.REST_ID
                HAVING rev.REST_ID = info.REST_ID) SCORE
    FROM REST_INFO info
    WHERE info.ADDRESS like '%서울%'
    ORDER BY SCORE desc, info.FAVORITES desc
    

    null을 지울 방법이 없어서 번거롭게 서브쿼리로 써서 바꾸었다.. 다들 join써서 했길래 다르게도 해보고 싶어서 불가피하게 번거로운 방법을 사용하였음

    그런데 왜 %서울%로 하면 실패 처리 나던데 서울%로 바꿔야 정답처리됬는데… 다른 분들도 그렇다고 하고 이유를 모르겠어서 이 부분도 튜터님께 질문하고 확인할 예정이다. 이 외도 null처리 할려고 했는데 왜 null제거가 미리 안되지라면서 고민하다가 해결한 부분, explain을 각각 join한 것, 안 한것에 사용하여 본 것(어떻게 봐야하는지는 잘모르겠어서 이부분도 여쭤봐야할 듯하다.) 등 몇몇 내용이 더있으나 적을 내용이 많기도 하고 굳이 블로그에 까지 올려서 정리해둘 필요는 없을 듯하여 노션에만 기록으로 남겨두었다.

     

    자동차 대여 기록에서 장기/단기 대여 구분하기(SQL)(date_format, 날짜 필터 조건,datediff)

    https://school.programmers.co.kr/learn/courses/30/lessons/151138

    원래 테이블에서 대여기간에 따라 30일이상이면 장기대여, 미만이면 단기 대여라고 표시해주는 컬럼을 추가해주는 문제이다.

    SELECT hst.HISTORY_ID, hst.CAR_ID,
            date_format(hst.START_DATE,'%Y-%m-%d') START_DATE,
            date_format(hst.END_DATE,'%Y-%m-%d') END_DATE,
            case 
                when datediff(hst.END_DATE,hst.START_DATE)+1>=30 then "장기 대여"
                else "단기 대여"
            end RENT_TYPE
    FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY hst
    WHERE date_format(hst.START_DATE,'%Y-%m') ='2022-09'
    ORDER BY hst.HISTORY_ID desc
    #WHERE date_format(hst.START_DATE,'%Y-%m-%d') BETWEEN '2022-09-01' and '2022-09-31'
    

    여전히 날짜로 필터링하는 부분이 문제이다.. 이제 할 줄은 알지만 뭔가 더 좋고 편한 방법이 있을 듯한데 알지 못하여 답답한 느낌이라고 할까 주석해둔 where절처럼 해도 동일하게 작동한다.

    그리고 대여기간이라 단순 대여종료일과 대여시작일의 차가 아닌 대여 시작일 부터 1일로 세어야 하는 것 같다 살짝 애매하긴 하지만 의미상 그게 맞는 것 같기도 하여 다음에 비슷한 개념이 나온다면 주의하여야 할듯하다. 그리고 이번에도 바로 datediff부터 떠오르지 않아서 동일한 실수를 하고 수정하였는데 바로 직접 -를 이용하여 빼주면 이상한 값이 나온다 아마 뭔가 각자 나타내는 값이 있기에 바로 빼면 그 값들이 나오는 것 일 텐데 짐작 가는 것들과 들어 맞는 것이 없어 더 이상 추측되는 값이 떠오르지 않는다.

    째든 쿼리에 대하여 간단히 설명하면 CAR_RENTAL_COMPANY_RENTAL_HISTORY (이하 hst)테이블에서 날짜가 2022년 9월인 데이터만 가져와서 대여id와 차id, 대여 시작날짜, 대여 종료날짜(해당 두 날짜는 형태를 시간 부분 날리고 날짜만 나타나게 바꾸어주었다), 그리고 대여 기간이 30일이상일 때 장기 대여, 미만일 때 단기 대여라고 표시해주는 컬럼을 나타낸 뒤 대여id를 기준으로 내림차순 해주었다.

    SELECT hst.HISTORY_ID, hst.CAR_ID,
            date_format(hst.START_DATE,'%Y-%m-%d') START_DATE,
            date_format(hst.END_DATE,'%Y-%m-%d') END_DATE,
            case 
                when hst.END_DATE-hst.START_DATE>=30 then "장기 대여"
                else "단기 대여"
            end RENT_TYPE
    FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY hst
    WHERE date_format(hst.START_DATE,'%Y-%m') ='2022-09'
    ORDER BY hst.HISTORY_ID desc
    #WHERE date_format(hst.START_DATE,'%Y-%m-%d') BETWEEN '2022-09-01' and '2022-09-31'
    
    SELECT hst.HISTORY_ID, hst.START_DATE
    FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY hst
    WHERE date(hst.START_DATE) BETWEEN '2022-09-01' and '2022-09-31'
    #WHERE hst.START_DATE BETWEEN '2022-09-01' and '2022-09-31'
    #WHERE hst.START_DATE BETWEEN 2022-09-01 and 2022-09-31
    

     위는 날짜 between 으로 조건으로 사용할려고 했는데 제대로 필터링이 안되고 그냥 아무 값도 나오지 않아서 date()로 했을 때는 between을 못쓰는지 확인해봐야 할듯하다

     

    자동차 평균 대여 기간 구하기(SQL) (datediff, round, having)

    https://school.programmers.co.kr/learn/courses/30/lessons/157342

    CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블에서 평균 대여 기간이 7일 이상인 자동차들의 자동차 ID와 평균 대여 기간(컬럼명: AVERAGE_DURATION) 리스트를 출력하는 SQL문을 작성해주세요. 평균 대여 기간은 소수점 두번째 자리에서 반올림하고, 결과는 평균 대여 기간을 기준으로 내림차순 정렬해주시고, 평균 대여 기간이 같으면 자동차 ID를 기준으로 내림차순 정렬해주세요. 라는 문제이다.

    SELECT hst.CAR_ID,
    round(avg(datediff(hst.END_DATE,hst.START_DATE)+1),1) AVERAGE_DURATION
    FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY hst
    GROUP BY hst.CAR_ID
    HAVING avg(datediff(hst.END_DATE,hst.START_DATE)+1)>=7
    ORDER BY AVERAGE_DURATION desc, hst.CAR_ID desc
    

    마찬가지로 대여 기간에 대하여 구해준 뒤 평균을 내서 7일이상인 데이터들에 대해서만 출력해주는 쿼리인데, 이번에도 빠르게 작성하다가 대여기간을 차로만 구해서 한 번 틀렸다가 수정하였다.

    간단히 쿼리에 대하여 설명하면 우선 CAR_RENTAL_COMPANY_RENTAL_HISTORY (이하hst)테이블에서 자동차id(hst.CAR_ID)에 대하여 그룹 지어주고 그 뒤 각 그룹의 평균 대여 기간이 7일 이상인 그룹인 데이터에 대해서만 가져온 다음, 자동차id(hst.CAR_ID)와 평균 대여 기간을 소수 첫째자리까지 나타내고 반올림한 값을 AVERAGE_DURATION라는 컬럼으로 출력해주는 쿼리이다.

     

    헤비 유저가 소유한 장소(SQL) (where 서브쿼리 조건 절)

    https://school.programmers.co.kr/learn/courses/30/lessons/77487

    이 서비스에서는 공간을 둘 이상 등록한 사람을 "헤비 유저"라고 부릅니다. 헤비 유저가 등록한 공간의 정보를 아이디 순으로 조회하는 SQL문을 작성해주세요. 라는 문제이다.

    먼저 필터링 할 조건으로 쓸 서브 쿼리문을 먼저 작성해주었다

    SELECT p_sub.HOST_ID
    FROM PLACES p_sub
    GROUP BY p_sub.HOST_ID
    HAVING  count(p_sub.ID)>=2
    

    HOST_ID로 그룹을 지어서 그룹당 ID가 2이상인 HOST_ID만을 출력하게 하였다

    그런뒤 메인쿼리문을 작성하여 where절에서 조건으로써 사용해준 뒤 서브쿼리문에서는 having을 이용해 연결해주었다.

    SELECT p.ID, p.NAME, p.HOST_ID
    FROM PLACES p
    WHERE p.HOST_ID = (SELECT p_sub.HOST_ID FROM PLACES p_sub 
                       GROUP BY p_sub.HOST_ID HAVING  count(p_sub.ID)>=2 
                            and p_sub.HOST_ID = p.HOST_ID)
    

    쿼리에 대하여 간단히 설명해보면

    우선 메인쿼리는 PLACES테이블에서 데이터를 가져오는데 이 때 따로 동일하게 PLACE테이블에서 데이터를 가져와 HOST_ID로 그룹을 지은 뒤 각 그룹에 대하여 ID가 2이상인 그룹만을 출력하는 서브쿼리를 p_sub.HOST_ID = p.HOST_ID로 연결시켜주고 이를 만족하는 HOST_ID에 대해 필터 조건으로써 사용하였다. 그 뒤 컬럼을 ID, NAME, HOST_ID에 대해 출력되도록 하였다

     

    우유와 요거트가 담긴 장바구니(SQL)(where 절 이중? 서브쿼리? 2개 조건 필터, distinct) (다양한 방법 모색, 속도 성능 비교하고 싶음(cast, left, locate, concat, union all , intersect, group_concat, sum(case when) ))

    https://school.programmers.co.kr/learn/courses/30/lessons/62284

    데이터 분석 팀에서는 우유(Milk)와 요거트(Yogurt)를 동시에 구입한 장바구니가 있는지 알아보려 합니다. 우유와 요거트를 동시에 구입한 장바구니의 아이디를 조회하는 SQL 문을 작성해주세요. 이때 결과는 장바구니의 아이디 순으로 나와야 합니다. 라는 문제이다.

    우선 필터로 쓸 조건이 필요하여 우선 서브쿼리로 각 조건들을 작성해주었다

    SELECT pct_mlik.CART_ID FROM CART_PRODUCTS pct_mlik
    WHERE pct_mlik.NAME ='Milk'
    
    SELECT pct_ygt.CART_ID FROM CART_PRODUCTS pct_ygt
    WHERE pct_ygt.NAME ='Yogurt'
    

    여기서 이렇게 하는게 맞는지 가장 좋은 방식 중 하나인지는 모르겠지만 mlik를 산 CART_ID에 속하고 yogurt를 산 CART_ID에 속하는 두 조건을 모두 만족하는 CART_ID에 대하여만 출력해주는데, 이 때 따로 그룹핑을 해주거나, 하지 않았기에 만족하는 CART_ID에 대하여 모든 ID에 대하여 데이터가 나오기 때문에 중복되어 여러 번 나오게 된다 따라서 distinct를 통해 중복을 제거하고 출력해주었다. (대강 작동원리에 대하여 적었으니 쿼리에 대한 설명은 생략하도록 하겠다.)

    SELECT distinct pct.CART_ID
    FROM CART_PRODUCTS pct
    WHERE pct.CART_ID in (SELECT pct_mlik.CART_ID 
                            FROM CART_PRODUCTS pct_mlik
                            WHERE pct_mlik.NAME ='Milk' 
                            and pct_mlik.CART_ID=pct.CART_ID) 
        and pct.CART_ID in (SELECT pct_ygt.CART_ID 
                            FROM CART_PRODUCTS pct_ygt 
                            WHERE pct_ygt.NAME ='Yogurt' 
                            and pct_ygt.CART_ID = pct.CART_ID)
    ORDER BY pct.CART_ID
    

     다른 분들은 어떻게 했나 궁금해서 확인하고 이해하는 과정까지 가졌지만 마찬가지로 그부분은 노션에다가만 적어두기로 하였다.

     

    더보기

    파이썬 코드카타 중 매우 오래 시간을 소모해버렸던 문제

     

    약간 시행착오 등이 매우 길어서 이것만 따로 작성해주었다.

    괄호 회전하기(python) (chatGPT, not list [] →True or False방식,리스트안에 요소들 회전 시키는 방식)

    https://school.programmers.co.kr/learn/courses/30/lessons/76502#

    대강 괄호 종류들로 이루어진 문자열이 주어지는데 이 문자열을 왼쪽으로 한 번씩 회전 시켜서(원형수열처럼 생각) 짝이 서로 잘 이루어진 괄호로 이루어진 문자열이 몇 번 가능한지 리턴하는 문제이다.(세부적인 판단 기준 등은 문제를 직접 읽기를 권장)

    처음에는 규칙성으로 하나의 덩어리라고 볼 수 있는 괄호 덩어리”( )나 [({})] 같은 경우, 괄호 문자열을 사용중이라 보기에 헷깔릴까봐 지금 큰따옴표를 사용하여 적었다”의 갯수 많큼 걔들끼리 회전 시켰을 때 경우의 수로 올바른 괄호 문자열이 나오게 되므로 이 점을 이용하려고 여러 기준,규칙을 찾아서 문자열을 나누고 그 덩어리끼리 한번만 회전시켜 문자덩어리가 몇개 나오는지 혹은 이루어지지 않는 부분이 있어 완전한 문자열이 나올 수 없는지 판단할려 했으나, 놓친 부분이 있는 것인지 끝내 반례를 찾지 못하여 결국 문제의 내용을 그대로 따라서 판단하게 코드를 짜게되었다.(긴 사연은 최종 제출 코드 아래에 적어두었다)

    def is_valid(string):
            stack = []
            for char in string:
                if char in ['[', '(', '{']:
                    stack.append(char)
                else:
                    if not stack: #아직 리스트에 아무것도 없는데 ],),}부터
                        return False #나오면 짝이 안맞는 셈
                    if char == ']' and stack[-1] == '[':
                        stack.pop()
                    elif char == ')' and stack[-1] == '(':
                        stack.pop()
                    elif char == '}' and stack[-1] == '{':
                        stack.pop()
                    else:
                        return False
            return not stack
    
    def solution(s):
        if len(s) % 2 != 0:
            return 0
    
        count = 0
        for i in range(len(s)):
            rotated = s[i:] + s[:i]
            if is_valid(rotated):
                count += 1
    
        return count
    

    너무 오래걸려서 반례를 도저히 떠올리지 못하여, 슬 그냥 포기하고 문제 내용 그대로 코드로 옮겨서 할까 하다가 마지막으로 다른 분들이 짠 방식이랑 내가 짜고 있던 코드를 chatGPT에게 다른 분들이 짠 방식이랑 다르게 값이 리턴 되는 경우 찾으라고 구박하고 있었는데 얘가 계속 악 복잡해서 잘못 찾았어요 죄송해요 그러면서 계속 간단한 맞게 나오는 반례 찾더니만 갑자기 오 이제 코드 과정에서 문제 있던부분 찾아서 해결한 것 같아요 그러길래 지쳐서 그래? 그러면서 복붙해서 프로그래머스에 제출해봤는데 됬길래 그것도 엄청 빨리 됬길래 오오!! 그러면서 코드 내용을 보니 내가 짠 방식은 다 버리고 다른 분들이 한 것처럼 문제 내용 그대로 문자열 회전시켜가면서 체크한 것이였다.. 아쉽지만 너무 시간을 많이 허비하기도 했고 도저히 여기서 진행이 될 것 같지않아서 나온 코드를 이해하고 일단 넘어갈려고 한다.

    우선 is_valid라는 부속 함수를 먼저 만들어 주었는데 문자열을 입력하면 스택이라는 빈 리스트에 괄호가 짝이 맞게 될 경우 스택에 들어간 문자가 짝이 되는 문자와 만나게 될 경우 제거되어 끝까지 짝이 맞게 되었다면 빈리스트로 나오게 되는데 return not stack을 해줄 경우 몰랐던 용법?방식?인데 ‘not 빈리스트’ 하면 True로 나오게 되고, ‘not 안에 무엇인가 들어있는 리스트’ 하면 False가 나오게 되는데 위 설계대로면 빈리스트로 나오게 되어 not stack을 리턴하면 True를 리턴하게 되고 위에서 중간에 짝이 안맞는 경우가 나온다면 False를 바로 리턴하도록 되어있다

    그리고 이제 메인함수인 solution을 설명하자면 우선 이 방식은 내가 시간 단축하기 위해서 사용했던 방식인데, 어차피 짝이 이루어진다는 것은 문자열이 짝수개여야 가능 하므로 짝수개가 아니면 바로 0을 리턴하게 한다. 그리고 그렇지 않다면 이제 문자열을 1개씩 회전시켜 가면서 올바른 문자열이 되는지 세어주는 for문을 돌리는데 문자열을 회전시키는 방식이 원본s는 그대로 내버려두고 새로 rotated라는 변수를 지정하여 s의 첫번째 원소부터 마지막 원소까지 하나씩을 기준으로 하여금 기준으로 할 원소를 기준으로 앞뒤로 슬라이싱을 이용해서 자른 뒤 순서를 바꾸어 합쳐준 것으로 정의해준다 그리고 이 roated를 is_valid(rotated)해주어서 판별한 뒤 True가 나오면 count를 1 증가 시켜준다 그리고 다 회전시키고 나면 최종적으로 세어준 값을 리턴한다.

    >>> solution('{[()]}')
    1
    >>> solution('[()]}{')
    2
    >>> solution('()]}{[')
    2
    >>> solution(')]}{[(')
    1
    >>>
    

     

    중간 중간 중간과정 기록해둔 부분은 생략하고 최종적으로 포기한 부분에 대해서만 올리면

    def solution(s):
        complete=0
        what_remain=0 #아직 짝 못찾은 것들
        rem_list=[] # 그것들 저장해두는 리스트
        slice_list=[]
        temp=''
        s_dict={'[':0, '(':0, '{':0, '}':0, ')':0, ']':0}
    
        if len(s)%2 !=0:
            return 0
        
        for i in range(len(s)): #나누기 작업부터
            #dict에 갯수파악
            if s[i] in s_dict:
                s_dict[s[i]]+=1
        
            # )}]세트 중 한개 와 [{(세트 중 한개 가 이런 순서로 만날시 나누기
            if i>0:
                if s[i-1] in [']','}',')'] and s[i] in ['[','{','(']:
                    slice_list.append(temp)
                    temp=''       
            temp+=s[i]
            if i==len(s)-1:
                    slice_list.append(temp)
    
        #확인 전 딱봐도 짝이 없는 경우면 커트!
        if s_dict['[']+s_dict['(']+s_dict['{'] ==0 \\
            or s_dict[']']+s_dict[')']+s_dict['}'] ==0:
            return 0
        
    
        print(slice_list)
        #순서 바꾸면 될 것 같은 경우 한번 회전시켜봄
        stop2='N'
        for target2 in range(len(slice_list)):
            for compare2 in range(target2+1,len(slice_list)):
                if (slice_list[target2][-1] == ')' and slice_list[compare2][0] == '(') \\
                   or (slice_list[target2][-1] == ']' and slice_list[compare2][0] == '[') \\
                   or (slice_list[target2][-1] == '}' and slice_list[compare2][0] == '{'):
                    temp=slice_list[target2]
                    for ch in range(len(slice_list)):
                        if ch<len(slice_list)-1:
                            slice_list[ch]=slice_list[ch+1]
                        elif ch == len(slice_list)-1:
                            slice_list[ch]=temp
                    stop2='Y'
                    break
            if stop2 == 'Y':
                break
        print(slice_list)
        #순서 바꿔서 합쳐져 완전한 괄호가 되는지 확인(합치기)
        if len(slice_list)>=2: #나눠지는게 없는 하나짜리는 확인 및 회전필요X
            stop='N'
            #print(slice_list)
            for target in range(len(slice_list)):
                for compare in range(target+1,len(slice_list)):
                    if (slice_list[target][0]=='[' and slice_list[compare][-1]==']') or\\
                       (slice_list[target][0]=='{' and slice_list[compare][-1]=='}') or\\
                       (slice_list[target][0]=='(' and slice_list[compare][-1]==')'):
                        slice_list=slice_list[:target]+["".join(slice_list[target:compare+1])]+slice_list[compare+1:]
                        stop='Y'
                        break
                if stop == 'Y':
                    break
            #print(slice_list)
    
        for st in range(len(slice_list)):
            for j in range(len(slice_list[st])):
                if what_remain==0:
                    rem_list.append(slice_list[st][j])
                    what_remain+=1 #여기 이후로 변수 수정
                else: #비교할 것이 있을 때
                    if slice_list[st][j] == ']':#1
                        if rem_list[-1] == '[':
                            what_remain-=1
                            rem_list.pop()
                            if what_remain == 0:
                                complete+=1
                        elif rem_list[-1] in ['(','{']:
                            return 0
                        else:
                            rem_list.append(slice_list[st][j])
                            what_remain+=1
                    elif slice_list[st][j] == ')':#2
                        if rem_list[-1] == '(':
                            what_remain-=1
                            rem_list.pop()
                            if what_remain == 0:
                                complete+=1
                        elif rem_list[-1] in ['[','{']:
                            return 0
                        else:
                            rem_list.append(slice_list[st][j])
                            what_remain+=1
                    elif slice_list[st][j] == '}':#3
                        if rem_list[-1] == '{':
                            what_remain-=1
                            rem_list.pop()
                            if what_remain == 0:
                                complete+=1
                        elif rem_list[-1] in ['(','[']:
                            return 0
                        else:
                            rem_list.append(slice_list[st][j])
                            what_remain+=1
                    elif slice_list[st][j] == '[': #4
                        if rem_list[-1] in [']', ')','}'] :
                            return 0
                        else:
                            rem_list.append(slice_list[st][j])
                            what_remain+=1
                    elif slice_list[st][j] == '(': #5
                        if rem_list[-1] in [']', ')','}'] :
                            return 0
                        else:
                            rem_list.append(slice_list[st][j])
                            what_remain+=1
                    elif slice_list[st][j] == '{': #6
                        if rem_list[-1] in [']', ')','}'] :
                            return 0
                        else:
                            rem_list.append(slice_list[st][j])
                            what_remain+=1
        
        return complete
    

     그리고 메모로 이것저것 계속 생각하면서 적었던 부분들이다

     

    더보기

    파이썬 코드카타

     

    연속 부분 수열 합의 개수(python)(set)

    https://school.programmers.co.kr/learn/courses/30/lessons/131701

    연속하는 원형 수열이 주어질 때 1개~(원형 수열의 길이)개 까지 각각의 합으로 중복없이 만들 수 있는 수의 개수를 구하는 문제이다.

    아래는 set을 테스트 해본 내용이다.

    a=set([1,2,3])
    a.add(4)
    a.add(2)
    print(a,len(a))
    
    result=set([])
    result.add(1)
    print(result)
    

     아래는 최종 제출한 코드와 설계단계?의 메모이다

    def solution(elements):
        answer=0
        result=set([])
        d_list=elements*2
        for scope in range(1,len(elements)+1):
            for i in range(len(elements)):
                result.add(sum(d_list[i:i+scope]))
                
        return len(result)
    

     다소 조금 무식하게 짜기는 했는데 제출에는 성공했다(그러니  어느정도는 성능이 사용할만 하다?의 범주에 든다고 볼 수 있지 않을까 생각한다.)

     잘 짜두신 분의 것을 보니 %연산자를 활용하여서 했던데 저런 테크닉? 아이디어?를 잘 기억해두면 도움이 될듯 하다.

     

    H-Index(python) (sort(reverse))

    https://school.programmers.co.kr/learn/courses/30/lessons/42747#

    대충 실제 한 과학자의 h-index라는 지표를 구하는 문제로, h-index는 n편의 논문 중에서 h편이상 인용된 논문이 h편이상일때 이 때 만족하는 h의 최댓값을 h-index라고 한다.

    def solution(citations):
        citations.sort() #정렬
        cnt={}
        check_list=list(set(citations))
        check_list.sort(reverse=True)
        for key in citations:
            if key in cnt:
                cnt[key] += 1
            else:
                cnt[key] = 1
        for h in range(max(cnt),-1,-1):
            check=0
            for j in [i for i in check_list if i>=h]:
                check += cnt[j]
            if check >= h:
                return h
    

    뭔가 구하는 방법 자체는 전체 인용됫 횟수 별로 몇 개씩 존재하는지 파악하고, 가장 큰 인용횟수부터 하나씩 값을 내리면서 h를 확인해보되, 불필요한 연산을 최대한 줄이는 방식을 생각해서 시작 자체는 크게 안 어려웠으나, h를 변화 시킬 때 for문 구성과, h번 이상 인용된 갯수의 총합 구하는 for문이 살짝 혼동이 와서 해맸으나 금방 print를 통해 확인하면서 수정하여 제출완료하였다.

    대략적인 코드에 대한 알고리즘과 구조에 대해 설명을 붙이자면

    없어도 상관은 없으나 확인할 때 눈에 편하라고 주어진 리스트를 정렬해주고 중복 없이 종류대로만 기록하기 위해 잠깐 set으로 바꿨다가 list로 다시 돌려준 뒤 내림차순으로 정렬해준다.

    그리고 각 인용 횟수에 대하여 해당하는 논문이 몇 편씩 있는지 확인하여 dict형태로 저장해준다.

    그 뒤 h를 가장 큰 인용 횟수 부터 0까지 1씩 작아지게끔 반복하여 확인해주는데(이 때 인용횟수에 해당 하는 값에 대해서만 확인하는지 헷갈렸었다) 확인해줄 때 각 인용 횟수가 비교하는 기준h이상인 것들에 대해서만 값을 더해서 총 몇 편인지 세어주고(세어 줄 때도 있는 값만 아니라 1씩 변화시킨 순서대로 확인해야 하나 하다가 처음에 딕셔너리 key에러가 났었다(순서대로 h,h+1..로 확인 시 key에 없는 값에 대해서도 확인하게 되므로)) 마지막에 h번이상 인용된 논문이 h편 이상 으로 존재하는지 확인하고 그렇지 않을 경우 다음 h에 대하여 반복한다.

    직접 딕셔너리 만들어서 세는 거랑 collections에서 counter 와 속도 비교해보면 좋을 듯 하다

     

    n^2 배열 자르기(python)

    https://school.programmers.co.kr/learn/courses/30/lessons/87390

    대강 n, left, right를 입력받는데 n행n열 크기의 2차원 리스트를 만들고 1행1열부터 i행i열까지 영역을 모두 i로 채워준다(대신 i-1행 i-1열까지 범위는 그 이전 수로 채움) 그 뒤 행을 모두 분리하여 하나의 행으로 다 붙여준 뒤 left인덱스 값부터 right 인덱스 값까지 부분을 리턴해주는 문제이다.

    def solution(n, left, right):
        answer=[0 for i in range(right-left+1)]
        for i in range(len(answer)):
            answer[i]+= 1+max((left+i)//n,(left+i)%n) #x,y중 큰 것 값따라 추가계산
            
        return answer
    

    최종적으로 낸 것으로 아래 문제에서 얘기해준 대로 하는 방식에서 left에서 right에 해당하는 부분에 대해서만 계산하고 리턴하게 바꾼 방식으로, 먼저 (right-left )의 길이를 가진 리스트를 만들어주고 (초기값은 0으로 설정) 그 뒤 반복문을 통해 값을 계산하여 적용해준다 (직접 n*n리스트를 만들어서 값을 넣어보면 알 수 있지만 y,x인덱스가 있다고 생각할 때 인덱스 중 큰 것의 값에 +1해주면 해당 값이 된다)

     아래는 생각할 때 했던 메모들이다

     그리고 아래는 처음 생각했던 아이디어대로 짠 코드이다

    def nn(n, left, right): #시각적 체크용
        answer = []
        raw_nn=[[0 for j in range(n)] for i in range(n)]
        
        for y in range(n):
            for x in range(n):
                raw_nn[y][x] += 1+max(x,y)
        print(raw_nn)        
        for i in range(len(raw_nn)):
            answer+=raw_nn[i]
            
        return answer[left:right+1]
    

    단순하게 문제 그대로 n*n리스트를 모두 만든 뒤 계산하고 마지막에 해당부분 자르기하여 리턴

    대신 값이 커지면 시간이 어마어마하게 오래걸림 →그래서 제출 시 시간초과엄청 많이 떴음

    raw_nn=[[ifor i in range(n)] for i in range(n)]
    >>> solution(3, 1, 3)
    [[0, 1, 2], [0, 1, 2], [0, 1, 2]]
    
    raw_nn=[[i+j for j in range(n)] for i in range(n)]
    >>> solution(3, 1, 3)
    [[0, 1, 2], [1, 2, 3], [2, 3, 4]]
    []
    

     컴프리헨션으로 n*n리스트 만들기

     

    행렬의 곱셈(python)

    https://school.programmers.co.kr/learn/courses/30/lessons/12949

    2차원 행렬 arr1과 arr2를 입력받아, arr1에 arr2를 곱한 결과를 반환하는 함수, solution을 완성해주세요. 라는 문제로 간단히 생각해 행렬연산을 코드에서 하는 것이다.

    def solution(arr1, arr2):
        answer = []
        for i in range(len(arr1)): #arr1의 a*b일때 a
            row_val=[]
            for j in range(len(arr2[0])): #arr2의 a*b일때 b
                value=0
                for cal in range(len(arr2)): #arr1의 b이자 arr2의 a
                     value+=arr1[i][cal]*arr2[cal][j]
                row_val.append(value)
            answer.append(row_val)
        return answer
    

    이번 코드는 짜둔 것 그대로 보면 되는 것으로 말로 설명이 조금 어려워, 메모로 하여금 설명을 첨부 하였다.

     

    할인 행사(python) (zip, dictionary딕셔너리 컴프리헨션)

    https://school.programmers.co.kr/learn/courses/30/lessons/131127

    대강 문제에 대해서 설명하면, 한번 금액 지불하면 10일동안 회원 유지되는데 그동안 원하는 품목 다 할인해서 살 수 있도록 할 때 가능한 날의 갯수가 몇 개인지 답하는 문제이다.

    def solution(want, number, discount):
        answer = 0
        want_dict={key:value for key,value in zip(want,number)}
        #10일간 회원유지되므로 이정도만 반복체크
        for day_index in range(len(discount)-9): 
            check_dict={}
            switch='Y'
            for update in discount[day_index:day_index+10]:
                if update in check_dict:
                    check_dict[update]+=1
                else:
                    check_dict[update]=1
            for check in want_dict:
                if check not in check_dict:
                    switch='N'
                    break
                else:
                    if check_dict[check]<want_dict[check]:
                        switch='N'
                        break
            if switch=='Y':
                answer+=1
        
        return answer
    

    간략하게 코드에 대해 설명하면, 우선 want와 number로 입력된 내용을 딕셔너리로 저장해두고 주어진 discount의 길이-9만큼 반복시켜서 10일간 할인 품목에 대해서만 확인해서 check_dict에 저장하고 그것과 want_dict를 대조하여 want_dict를 만족한다면 answer를 1증가시키는 식으로 세어준다 그 뒤 최종적인 answer를 리턴한다

     

    의상(python)(dictionary 딕셔너리)

    https://school.programmers.co.kr/learn/courses/30/lessons/42578

    간략히 문제를 설명하자면 매일 다른 옷을 입도록 종류별로 갖춰입을 경우 경우의 수를 구하라는 문제이다(단, 있는 부위를 모두 착용할 필요는 없지만, 아무것도 아예 안 입는 것은 안된다)

    def solution(clothes):
        answer = 1
        type_dict={}
        for i in clothes:
            if i[1] in type_dict:
                type_dict[i[1]]+=1
            else:
                type_dict[i[1]]=1
        for j in list(type_dict.values()):
            answer*=(j+1)
        return answer-1
    

    코드에 대해 간략히 설명하자면

    우선 의상 종류 별로 몇 개가 있는지 확인하여 딕셔너리로 저장한다. 그 뒤 value를 모두 가져와서 각 +1해준 뒤 곱하게 하고 (안 입는 경우를 포함하여 모든 경우의 수) 그리고 아예 안 입는 경우는 제외해야하므로 -1을 해준 뒤 리턴해준다.

     

     

     

     

     

    '스파르타 > TIL(Today I Learned)' 카테고리의 다른 글

    2024-01-09  (2) 2024.01.09
    2024-01-08  (0) 2024.01.08
    2024-01-05  (1) 2024.01.05
    2024-01-04  (0) 2024.01.04
    2024-01-03  (2) 2024.01.03
Designed by Tistory.