Day 5 정렬 기본 알고리즘, 자료형

가연·2024년 12월 11일

SWAP

a = [3,10]
t = a[0]
a[0] = a[1]
a[1] = t
a 
-> [10,3]

멀티 할당 이용
a[0], a[1] = a[1], a[0]
왼쪽은 new, 오른쪽은 old

선택정렬

제일 큰 값을 선택하고,
그 값을 제외한 나머지 들 중 제일 큰 값을 선택하고 /
... 반복(i개의 step)

arr = [20, 12, 15 10, 2]
for i_step in range(len(arr)): # 5개 원소 롤링 --> 0~4 step
    # 1) 지금 i_step에서 제일 작은 친구 위치 기록
    #     ==> i번째 있는 친구로 초기화
    min_idx = i_step
    # 2) i_step +1 위치 값 부터~끝까지
    for j in range(i_step+1, len(arr)):
        # 비교 : 지금까지 1등 vs 새로운 값
        # ==> 신규 값 : j번째
        #     기존 1등 위치 : min_idx에 존재
        if arr[j] < arr[min_idx]: # 신규출전자가 더 작을 때
            min_idx = j
        else:
            pass
    # i_step에서의 제일 작은값 선택 = min_idx
    # 3) 기존 i_step 값과 새로운 값 자리 swap
    arr[min_idx], arr[i_step] = arr[i_step], arr[min_idx]
print(arr)

삽입정렬

"기존"에 "정렬"이 되어있는 상태에서 "중간에" 삽입
🆚 선택정렬, 크기 비교->기록 필요 없음. 계속 비교하면서 swap/stop

arr =[9, 5, 1, 4, 3]
for step in range(1, len(arr)):
	for i in range(step,0,-1):
        if arr[i-1] > arr[i]: #new가 더 작으면
            arr[i-1], arr[i] = arr[i], arr[i-1]
        else :
        	break #공회전 방지, 효율 증가

버블정렬

코드상 삽입정렬과 유사 / 개념상 선택정렬과 유사

for step in range(len(arr)):
    # 앞에서 부터 2개씩 가지고 와서 비교 & 큰 것을 뒤로 swap
    for i in range(0, len(arr)-step-1):
        if arr[i] > arr[i+1]: # 앞의 친구가 크면 > 뒤로 보내
            arr[i], arr[i+1] = arr[i+1], arr[i]

자료형의 기능들

List

  • 원소 추가 : .append(), .insert()
    * 병합 : .extend(), +
    ex/ [a,b,c] + [d,e] => [a,b,c,d,e] 단, 원본 유지되기 때문에 변수 할당 or 갱신 해줘야함.
  • 원소 제거 : .pop(위치index), .remove(값)
    default는 맨 뒤, 중복된 값이 있다면, 앞의 1개만 제거.
  • 원소 길이, 개수 : len()
    값의 개수 .count(값), 값의 자리 index(값)
  • 순서 : .reverse(), reversed()
  • 정렬 : .sort(key=lambda ~), sorted(list, key=lambda ~)

Dict

  • k, v 등록 : dict[key] = value 기존 key값이 존재하면 value 갱신.
  • 제거(key 중심) : del dict[key]
  • 제거 : del dict - dict 제거 , dict.clear() - 내부 원소 모두 제거 ( 틀 {} 만 남김 )
  • k, v 풀기 : dict.items() -> [ (key, value), (key, value), ... ]

01. 자료형_마라톤문제

어렵진 않지만 자주 사용되는 구조. 익숙하게 생각하라

Q.
수많은 마라톤 선수들이 마라톤에 참여하였습니다. 단 한 명의 선수를 제외하고는 모든 선수가 마라톤을 완주하였습니다.

마라톤에 참여한 선수들의 이름이 담긴 배열 participant와 완주한 선수들의 이름이 담긴 배열 completion이 주어질 때, 완주하지 못한 선수의 이름을 return 하도록 solution 함수를 작성해주세요.

[제한사항]
마라톤 경기에 참여한 선수의 수는 1명 이상 100,000명 이하입니다.
completion의 길이는 participant의 길이보다 1 작습니다.
참가자의 이름은 1개 이상 20개 이하의 알파벳 소문자로 이루어져 있습니다.
참가자 중에는 동명이인이 있을 수 있습니다.

[입출력 예]
pariticipant
["marina", "josipa", "nikola", "vinko", "filipa"]
completion
["josipa", "filipa", "marina", "nikola"]
return
"vinko"

A.
#포함 여부 판단 : if + in 사용. dict의 경우 key 기준.

def solution(participant, completion):
    answer = ''

1. 변수 세팅

참가자 이름 : key, 참가자 수 : value => dict로 세팅

    people_dict = {}

2. 롤링

  1. 참가자 명단 정리(dict) : 참가자 리스트 롤링
    for p in participant: 
        # 기준 : 기등록이름 vs 처음등록이름
        if p in people_dict:  # 기등록이름
            people_dict[p] +=1
        else: # 신규등록
            people_dict[p] =1
  1. 완주자 명단을 롤링
    for c in completion:
        # 기준 : 완주자가 1명 vs 2명 이상
        if people_dict[c] == 1: 
            del people_dict[c]
        else: 
            people_dict[c] -= 1
    # 완주자 명단을 보면서 참가자 지우기 --> 1명만 남을 때까지
    answer = list(people_dict.keys())[0]
    return answer

#코드는 간단해도 비효율적일 수 있다.

02. 자료형_신고결과문제

실제 기출 1번 정도의 문제

Q.
신입사원 무지는 게시판 불량 이용자를 신고하고 처리 결과를 메일로 발송하는 시스템을 개발하려 합니다. 무지가 개발하려는 시스템은 다음과 같습니다.

  • 각 유저는 한 번에 한 명의 유저를 신고할 수 있습니다.
    - 신고 횟수에 제한은 없습니다. 서로 다른 유저를 계속해서 신고할 수 있습니다.
    - 한 유저를 여러 번 신고할 수도 있지만, 동일한 유저에 대한 신고 횟수는 1회로 처리됩니다.
  • k번 이상 신고된 유저는 게시판 이용이 정지되며, 해당 유저를 신고한 모든 유저에게 정지 사실을 메일로 발송합니다.
    - 유저가 신고한 모든 내용을 취합하여 마지막에 한꺼번에 게시판 이용 정지를 시키면서 정지 메일을 발송합니다.

다음은 전체 유저 목록이 ["muzi", "frodo", "apeach", "neo"]이고, k = 2(즉, 2번 이상 신고당하면 이용 정지)인 경우의 예시입니다.

유저 ID유저가 신고한 ID설명
"muzi""frodo""muzi"가 "frodo"를 신고했습니다.
"apeach""frodo""apeach"가 "frodo"를 신고했습니다.
"frodo""neo""frodo"가 "neo"를 신고했습니다.
"muzi""neo""muzi"가 "neo"를 신고했습니다.
"apeach""muzi""apeach"가 "muzi"를 신고했습니다.

각 유저별로 신고당한 횟수는 다음과 같습니다.

유저 ID신고당한 횟수
"muzi"1
"frodo"2
"apeach"0
"neo"2

위 예시에서는 2번 이상 신고당한 "frodo"와 "neo"의 게시판 이용이 정지됩니다. 이때, 각 유저별로 신고한 아이디와 정지된 아이디를 정리하면 다음과 같습니다.

유저 ID유저가 신고한 ID정지된 ID
"muzi"["frodo", "neo"]["frodo", "neo"]
"frodo"["neo"]["neo"]
"apeach"["muzi", "frodo"]["frodo"]
"neo"없음없음

따라서 "muzi"는 처리 결과 메일을 2회, "frodo"와 "apeach"는 각각 처리 결과 메일을 1회 받게 됩니다.

이용자의 ID가 담긴 문자열 배열 id_list, 각 이용자가 신고한 이용자의 ID 정보가 담긴 문자열 배열 report, 정지 기준이 되는 신고 횟수 k가 매개변수로 주어질 때, 각 유저별로 처리 결과 메일을 받은 횟수를 배열에 담아 return 하도록 solution 함수를 완성해주세요.

[제한사항]

  • 2 ≤ id_list의 길이 ≤ 1,000
    1 ≤ id_list의 원소 길이 ≤ 10
    id_list의 원소는 이용자의 id를 나타내는 문자열이며 알파벳 소문자로만 이루어져 있습니다.
    id_list에는 같은 아이디가 중복해서 들어있지 않습니다.
  • 1 ≤ report의 길이 ≤ 200,000
    3 ≤ report의 원소 길이 ≤ 21
    report의 원소는 "이용자id 신고한id"형태의 문자열입니다.
    예를 들어 "muzi frodo"의 경우 "muzi"가 "frodo"를 신고했다는 의미입니다.
    id는 알파벳 소문자로만 이루어져 있습니다.
    이용자id와 신고한id는 공백(스페이스)하나로 구분되어 있습니다.
    자기 자신을 신고하는 경우는 없습니다.
  • 1 ≤ k ≤ 200, k는 자연수입니다.
  • return 하는 배열은 id_list에 담긴 id 순서대로 각 유저가 받은 결과 메일 수를 담으면 됩니다.

[입출력 예]
/입력
id_list = ["muzi", "frodo", "apeach", "neo"]
report = ["muzi frodo","apeach frodo","frodo neo","muzi neo","apeach muzi"]
k = 2
/출력
result = [2,1,1,0]

def solution(id_list, report, k):
    answer = []
    return answer

A.
#입력:
id_list 전체 "유저" list,
report "유저 신고유저" list, 신고 내역
k 신고 처리 기준, k번 이상 신고했을 때 처리

#출력: 신고 유저 중, 처리된 유저 개수 list. (id_list 순서대로)

#나의.. 멍청한코드..

report_dict = {}
num_report = {}
for i in report : 
	id, r_id = i.split(' ') #먼저 유저:신고유저 dict1에 담기
    report_dict[id] = r_id 
    if r_id in num_report : #신고 수를 counting 해서 유저:신고수 dict2에 담고
		num_report[r_id] += 1
    else :
    	num_report[r_id] = 1
   
for 문 돌리면서
dict2 value>k 인 경우 
for i in num_report.values() :
	if i >= k :
    	#dict2 key값을 dict1 value에서 찾아서
		#그에 상응하는 key값 -> 전체 유저 list에서 index값 찾기
		#-> 순서대로 list에 갱신(+1씩)

Dict으로 세팅

1) 신고 하고, 2) 신고 개수에 따라 처리 여부 결정, 3) 처리된 사람을 신고한 사람을 다시 찾아서.. 4) 신고한 사람의 신고 처리된 개수를 구해야함... 왔다갔다...

💡 key : 신고 당한 사람 기준, value : 신고 한 사람들(여러명)

k값과 value의 개수를 비교하여 처리 여부 판단 ->
처리된 사람 key에서 찾고 - value(list)로 counting (id_list에서 위치 찾아서 answer에 +1 갱신)

** 한 사람을 여러 번 신고할 경우(중복) 1번으로 처리 -> value 자료형을 list ↔ set 해주면 중복 제거됨

1. 정보 세팅

빈 Dict 생성(k:신고 당한 사람, v:신고 한 사람)
d = { }
id_list 통해 key값 미리 생성해두기.
d = {id:[] for id in id_list}
k값 이상 신고되어 신고 처리 된 사람
BL = []
출력 값
answer = [0] * len(id_list)
[0,0,0,0,..] 세팅해두고 counting해서 +1씩 갱신

2. 계산

2.1 report를 통해 신고 접수 결과 정리
for r in range(0,len(report)) :
    value = r.split(' ')[0] : 신고한 사람 (value)
    key = r.split(' ')[1] : 신고 당한 사람 (key)
    if key in d :
        d[key].append(value)
#pythontic한 코드 짜기

* 중복 제거 :
for i in d :
   d[i] = set(d[i])
   d[i] = list(d[i])

2.2 k값 이상 신고 당한 사람 filtering
if len(d[i]) >= k :
   BL.append(i)

2.3 신고 처리 된 사람(BL)을 처리한 사람 counting
for i in BL :
   l = d[i]
   for j in l :
       answer[id_list.index(j)] = +1

특이 케이스 유의

답안

def solution(id_list, report, k):
    answer = [0]*len(id_list)
    reported_dict = {id:set([]) for id in id_list}
    for report_pair in report: # ---> ???? set 중복신고 결과 유니크
        reporter, reported = report_pair.split(" ")
        reported_dict[reported].add(reporter) # *******
    k_reported = [key_id for key_id, v in reported_dict.items() if len(v) >=k]
    for ban_id in k_reported:
        mail_res_ids = reported_dict[ban_id] # 	["muzi"," apeach"]
        for id in mail_res_ids:
            id_index = id_list.index(id)
            answer[id_index] += 1
    return answer

0개의 댓글