❓  문제

땅따먹기

 

🛎️  아이디어

1. 2번째 행부터 돌면서 윗 줄의 최댓값을 더해준다. 단 본인 열은 제외한다.

2. 마지막행에서는 이전 행까지의 최댓값들을 더한 값이 있음. 따라서 마지막행에서의 max값을 리턴한다.

 

💻  풀이

def solution(land):
  row = len(land)
  column = len(land[0])

  for r in range(1, row):
    for c in range(column):
      land[r][c] += max(land[r - 1][:c] + land[r - 1][c + 1:])

  return max(land[row - 1])

print(solution([[1,2,3,5],[5,6,7,8],[4,3,2,1]]))

 

❓  문제

다리를 지나는 트럭

 

🛎️  아이디어

  1. bridge 길이 만큼의 deque를 생성한다.

  2. 모든 트럭이 다리에 올라갈 때까지 while문을 돈다.

  3. bridge에서 popleft를 통해 한 칸 씩 왼쪽으로 옮겨간다. (rotate와 다름. rotate는 1->2->3->1 처럼 돌기 때문)

  4. 새롭게 들어갈 트럭의 무게와 현재 bridge의 무게를 더해서 견딜 수 있는지 확인한다.
    • 견딜 수 있다면 새롭게 들어갈 트럭의 무게를 더하고
    • 견딜 수 없다면 0을 더한다.

 

💻  풀이

# 최대 bridge_length대 올라갈 수 있음. (다리의 길이)
# weight만큼 버틸 수 있음.
# 다리에 완전히 오르지 않았다면 무게를 무시한다.

from collections import deque

def solution(bridge_length, weight, truck_weights):
    time = 0

    # 현재 올라간 트럭의 무게 합
    weight_sum = 0

    # bridge 크기만큼의 데큐 생성
    bridge = deque([0 for i in range(bridge_length)])
    while truck_weights:
        time += 1
        weight_sum -= bridge.popleft()

        # 이번에 들어갈 트럭 무게 계산
        truck = truck_weights[0]
        if weight_sum + truck <= weight:
            weight_sum += truck
            bridge.append(truck_weights.pop(0))
        else:
            bridge.append(0)

    # 마지막 트럭이 들어간 후 완전히 나올 때 까지의 시간 더해주기 (bridge_length)
    return time + bridge_length

 

🧩  포인트

- deque를 사용해서 popleft, append로 실제 다리를 건너는 것처럼 한다.

- 처음에 현재 bridge의 무게를 구할 때 sum() 함수를 사용했는데, 이럴 경우 시간 초과가 나기 때문에 sum을 담을 변수를 생성한 후 계산을 해줘야 한다.

❓  문제

행렬 테두리 회전하기

 

🛎️  아이디어

1. 회전해야하는 숫자들을 top, right, bottom, left순으로 리스트에 담는다.
    a. 단, bottom과 left는 역순으로 담는다.

2. deque의 rotate 함수를 활용해서 1칸씩 오른쪽으로 이동해준다.

3. 다시 top, right, bottom, left순으로 넣어준다.
    a. 단, bottom과 left는 역순으로 넣어준다.

 

💻  풀이

from collections import deque

def solution(rows, columns, queries):
  items = [[y + x * columns for y in range(1, columns + 1)] for x in range(rows)]
  minimum_list = []

  for x1, y1, x2, y2 in queries:
    x1 -= 1
    y1 -= 1
    x2 -= 1
    y2 -= 1

    # 위치 변경할 숫자들
    change_list = []

    # top
    change_list.extend(items[x1][y1: y2])

    # right
    for x in range(x1, x2):
      change_list.append(items[x][y2])

    # bottom
    change_list.extend(reversed(items[x2][y1 + 1: y2 + 1]))

    # left
    for x in range(x2, x1, -1):
      change_list.append(items[x][y1])

    # 최솟값 구하기
    minimum_list.append(min(change_list))

    # deque를 이용한 위치 변경
    dq = deque(change_list)
    dq.rotate(1)

    # top, right, bottom, left 순으로 넣어주기
    # top
    for y in range(y1, y2):
      items[x1][y] = dq.popleft()

    # right
    for x in range(x1, x2):
      items[x][y2] = dq.popleft()

    # bottom
    for y in range(y2, y1, -1):
      items[x2][y] = dq.popleft()

    # left
    for x in range(x2, x1, -1):
      items[x][y1] = dq.popleft()

  return minimum_list

 

❓  문제

2019 KAKAO BLIND RECRUITMENT - 후보키

 

🛎️  아이디어

1. 후보키가 될 수 있는 집합을 만든다.
    a. relation의 컬럼(column) 숫자를 가지고 만든다.
    b. 해당 결과는 (0), (1), ... (0, 1), (0, 2), ... (0, 1, 2, 3) 으로 나온다.

2. 만든 집합을 돌면서 해당 인덱스에 맞는 값들을 가지고 실제 집합을 만들어 준다.

3. 실제 집합 상태에서 중복 제거를 해준다.
      a. 유일성을 만족하는지 확인하기 위함이다.

4. 유일성을 만족한다면 (중복 제거 후에도 relation의 row길이와 같다면) 
      a. answer 배열에 해당 인덱스 집합의 부분집합이 있는지 확인한다.
      b. 예를 들어, (0, 1)은 (0)을 포함하기 때문에 최소성을 만족하지 않는다.
      c. 부분집합에 해당하지 않으면 answer 배열에 해당 인덱스 집합을 더해준다.

 

💻  풀이

from itertools import combinations

def solution(relation):
    
    # 가능한 인덱스 조합
    index_combination = []
    for r in range(1, len(relation[0]) + 1):
        index_combination.extend(combinations(range(len(relation[0])), r))
        
    answer = []
    for comb in index_combination:
        
        # 해당 인덱스를 사용한 실제 조합
        tmp1 = []
        for r in relation:
            tmp2 = []
            for c in comb:
                tmp2.append(r[c])
            
            # 이후에 2차원 중복제거를 위해 tuple로 변환 후 append
            tmp1.append(tuple(tmp2))
        
        # 유일성 확인
        if len(set(tmp1)) == len(relation):
            # 최소성 확인
            check = True
            for a in answer:
                if set(a).issubset(comb):
                    check = False
                    break
        
            if check: answer.append(comb)
    return len(answer)

 

🧩  포인트

- 2차원 리스트를 중복제거 하기 위해서는 tuple을 사용해 1차원 리스트로 만들어 주고 set을 사용한다.
- tuple은 중복 제거가 되지 않는다.

❓  문제

2018 KAKAO BLIND RECRUITMENT - [3차] 방금그곡

 

🛎️  아이디어

1. musicinfos 배열에서 가져온 데이터를 "," 기준으로 분리한다.

2. sheet(악보정보)를 list를 사용해 문자열을 분리하고 만약 분리된 문자열이 #이면 바로 앞 문자에 붙여준다.
      a. "AB#C" -> ["A", "B", "#", "C"] -> ["A", "B#", "#", "C"]

3. sheet 배열에서 '#'를 삭제해준다.
      a. ["A", "B#", "#", "C"] -> ["A", "B#", "C"]

4. 시작 시간과 종료 시간은 ":" 기준으로 시/분으로 분리한다.

5. 시와 분끼리 계산해주고 실제 재생된 Melody를 만들어준다.

6. 해당 멜로디에 네오가 기억하는 부분이 있는지 확인한다.
      a. 샾이 붙은 데이터는 미리 제거한다.
            ㄱ. ABC#D에는 ABC와 ABC# 모두 포함될 수 있다.
            ㄴ. 네오가 기억하는 부분에 #를 더한 ABC#과 ABC##을 찾아서 미리 제거한다.

7. 네오가 기억하는 부분이 포함되면 match_list에 append해준다.

8. 재생시간 기준으로 역순 정렬한다. 만약 match_list가 비어있다면 (None)을 반환한다.

 

💻  풀이

# 음악이 시작한 시각, 끝난 시각, 음악 제목, 악보 정보
def solution(m, musicinfos):
    match_list = []

    for musicinfo in musicinfos:
        start_time, end_time, title, sheet = musicinfo.split(",")

        # sheet 변경
        #1. 모든 문자열 분리 및 #붙이기
        sheet = list(sheet)
        for idx, item in enumerate(sheet):
            if item == "#":
                sheet[idx - 1] += "#"

        # 샾 제거
        sheet = [i for i in sheet if i != "#"]

        # 분, 초 분리
        start_hour, start_minute = start_time.split(":")
        end_hour, end_minute = end_time.split(":")

        # 재생 시간 계산
        sub_hour = int(end_hour) - int(start_hour)
        sub_minute = int(end_minute) - int(start_minute)
        play_time = 60 * sub_hour + sub_minute

        melody = sheet * (play_time // len(sheet)) + sheet[:play_time % len(sheet)]

        # 샾이 붙은 데이터 제거
        # ABC#D에는 ABC와 ABC# 모두 포함될 수 있음. 따라서 ABC#과 ABC##을 찾아서 미리 없앰.
        check_melody = ''.join(melody).replace(m + "#", '')
        if m in check_melody:
            match_list.append({"playTime": play_time,
                               "title": title})

    match_list = sorted(match_list, key= lambda x: x["playTime"], reverse= True)
    if match_list == []:
        return "(None)"
    else:
        return match_list[0]["title"]

❓  문제

Summer/Winter Coding(~2018) - 스킬트리

 

 

🛎️  아이디어

1. skill_trees 배열에서 하나씩 꺼내온다.

2. 해당 배열에서 find를 사용하여 skill의 각각의 원소에 해당하는 인덱스를 가져온다.

3. 만약 find의 리턴 값이 -1이라면 (해당 배열에 값이 없는 경우) skills의 길이를 넣어준다.
    a. -1이 앞에 있는 경우 미통과이기 때문에 순서를 뒤바꿀 수 있는 큰 값을 넣어주는 것이다.

4. 정렬했을 때 순서가 바뀌지 않는다면 통과!

 

💻  풀이

def solution(skill, skill_trees):
    answer = 0

    for skills in skill_trees:
        token_index_list = list(map(lambda x: skills.find(x), skill))
        token_index_list = list(map(lambda x: len(skills) if x == -1 else x, token_index_list))
        if token_index_list == sorted(token_index_list):
            answer += 1

    return answer

    print(solution("CBD", ["BACDE", "CBADF", "AECB", "BDA"]))

"""
token_index_list 결과가 다음과 같으면,

[2, 0, 3] -> 미통과
[0, 1, 3] -> 통과
[2, 3, -1] -> 통과
[-1, 0, 1] -> 미통과

-1이 있는 두 가지를 구별할 방법은 -1인 경우 큰 값을 넣어주기!
"""

 📌 문제

파이어베이스에서 페이스북 로그인을 구현할 때 공식문서를 참고하여 Info.plist에 값을 넣었음에도 자꾸 다음과 같은 오류가 발생하였다.

 

오류 메시지를 읽어보면 info.plist 파일의 URL scheme 부분에 값이 제대로 들어가지 않았다고 한다.

오류가 난 info.plist 파일은 다음과 같다.

info.plist

 

 📌 문제 해결

나의 문제점은 URLSchemes의 아래 값을 넣어주는 부분에 fb를 붙여주어서 그렇다.

이런식으로 앞에 fb를 붙이지 않고 숫자만 넣어준다면 해결될 것이다.

 📌 결과 화면

아래와 같이 테이블뷰 안에 컬렉션뷰를 중첩한 구조를 만들려고 한다.

 

 📌 화면 구상

먼저 전체적으로 테이블뷰를 넣고 테이블뷰셀 안에 컬렉션뷰를 넣어주는 식으로 작업했다.

 

왼쪽부터 TV를 넣어준 SB, TVC(안에 CV) xib, CVC xib이다.

 

 📌 코드 - TV 설정

TV가 있는 스토리보드와 연결한 뷰컨트롤러 파일을 열어준다.

이후 스토리보드의 TV를 IBOutlet으로 연결해준다.

@IBOutlet weak var postTV: UITableView!

 

그리고 TVC를 등록하고 TVC의 delegate와 dataSource를 self로 설정해준다.

나는 해당 코드를 함수로 묶어 viewDidLoad에 넣어주었다.

private func setPostTV() {
    postTV.register(UINib(nibName: "PostTVC", bundle: nil), forCellWithReuseIdentifier: "PostTVC")
    postTV.delegate = self
    postTV.dataSource = self
}

 

UITableViewDelegate와 UITableViewDataSource 각각 코드를 작성해준다.

// MARK: - UITableViewDelegate
extension CreateMainVC: UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        /// 만약 서버통신 후 받아온 각각의 게시글의 이미지 개수가 2개이면 iPhone 13 mini 기준 높이를 268, 3개이면 207로 설정해준다.
        let height = (postList[indexPath.row].images.count == 2) ? 268 : 207
        
        return height
    }
}

// MARK: - UITableViewDataSource
extension CreateMainVC: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        /// 총 TVC의 개수는 서버 통신 후 받아온 게시글의 총 개수
        return postList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: PostTVC.className, for: indexPath) as? PostTVC else { return UITableViewCell() }
        
        /// 순서대로 데이터 넣어주기
        /// TVC에 작성해준 setData 함수를 활용한다. 이때 CV를 위한 이미지 리스트도 전달한다.
        cell.setData(postList[indexPath.row])
        
        return cell
    }
}

 

 📌 코드 - TVC 설정

TVC xib파일과 연결한 UITableViewCell 파일을 열어준다.

이후 xib의 CV를 IBOutlet으로 연결해준다.

@IBOutlet weak var imgCV: UICollectionView!

 

그리고 CVC를 등록하고 CVC의 delegate와 dataSource를 self로 설정해준다.

나는 해당 코드를 함수로 묶어 viewDidLoad에 넣어주었다.

private func setImgCV() {
    imgCV.register(UINib(nibName: "ImgCVC", bundle: nil), forCellWithReuseIdentifier: "ImgCVC")
    imgCV.delegate = self
    imgCV.dataSource = self
}

그리고 TVC를 등록하고 TVC의 delegate와 dataSource를 self로 설정해준다.

나는 해당 코드를 함수로 묶어 viewDidLoad에 넣어주었다.

 

마지막으로 UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout 각각 코드를 작성해준다.

TV에서 setData함수를 통해 넘겨준 데이터 중 imgList를 활용한다.

// MARK: - UICollectionViewDelegate
extension PostTVC: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        /// CVC 개수는 이미지 개수와 같다.
        return imgList.count
    }
}

// MARK: - UICollectionViewDataSource
extension PostTVC: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ImgCVC.className, for: indexPath) as? ImgCVC else {
            return UICollectionViewCell()
        }
        /// CVC에 작성해준 setData 함수를 활용한다.
        cell.setData(imgList[indexPath.row])
        
        return cell
    }
}

// MARK: - UICollectionViewDelegateFlowLayout
extension PostTVC: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        /// 게시글 이미지 개수에 따라 너비와 높이를 정해준다.
        let cellWidth = (imgList.count == 2) ? 163 : 104
        let cellHeight = (imgList.count == 2) ? 170 : 109
        
        return CGSize(width: cellWidth, height: cellHeight)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        /// 게시글의 이미지 개수에 따라 인셋을 정해준다.
        if imgList.count == 2 {
            return UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
        } else {
            return UIEdgeInsets(top: 12, left: 18, bottom: 12, right: 18)
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        /// 게시글의 이미지 개수에 따라 CVC간 간격을 정해준다.
        let spacingSize = (imgList.count == 2) ? 17 : 14
        
        return CGFloat(spacingSize)
    }
}

 

 📌 근거리 네트워트 (LAN, Local Area Network)

  • 범위가 건물 안이나 특정지역인 네트워크이다.
  • 유선 케이블, 적외선 링크, 무선 송수신기 등을 이용하여 통신한다.
  • 한 건물 또는 인접한 건물군 내에 있는 네트워크는 하나의 LAN으로 간주되며 집이나 빌딩 안에 있는 사무실 등 지리적으로 제한된 곳에서 컴퓨터와 프린터, 스캐너 등을 연결할 수 있는 네트워크이다.
  • 서로 다른 LAN을 연결하여 사용할 수 있다.

 

 📌 도시 지역 네트워크 (MAN, Metropolitan Area Network)

  • LAN보다 넓은 영역이고 WAN보다 작은 영역을 커버한다.
  • 떨어져 있지만 동일하거나 다른 도시에 상주하는 두 대 이상의 컴퓨터를 연결한다.
  • 예시
    고객에게 고속 DSL 회선을 제공할 수 있는 회사의 일부 네트워크나 케이블 TV 네트워크가 있다.
    지역 또는 도시 내에 위치한 다양한 경찰서를 서로 연결하는 데 사용할 수 있다. 경찰관은 인터넷 연결 없이 쉽게 통신할 수 있고 중요한 데이터와 긴급 메시지를 네트워크를 통해 신속하게 전달할 수 있다.

 

 📌 광역 네트워크 (WAN, Wide Area Network)

  • 두 개 이상의 근거리 네트워크를 넓은 지역에 걸쳐 연결하는 것이다.
  • 근거리 네트워크에 포함되지 않은 멀리 떨어진 컴퓨터 사이에서도 광역 네트워크를 이용하여 서로 통신할 수 있다.
  • LAN보다 오류 발생 확률이 크고, 속도는 느리며, 비용은 많이 든다.
  • 예시
    어떤 기업의 본사는 서울에 있고 생산 공장은 지방에 있을 수 있다. 이 둘 간에 데이터와 프로그램 등을 공유하려고 기존 전화선에 라우터를 연결하여 광역 네트워크를 구성하기도 한다.

 

 📌 인트라넷 (Intranet)

  • 인터넷에서 사용하는 회선과 여러 기반 기술을 이용하여 구축하는 사설 네트워크이다.
  • 기업에서 내부망, 외부망을 따로 관리하는 경우가 있는데 이러한 경우 내부망이 바로 인트라넷이다.
  • 예시
    각 지방에 분산된 대학 캠퍼스들을 전용회선을 이용하여 네트워크로 연결한다면 지불 비용이 많이 들 것이다. 하지만 인터넷을 이용하면 쉽게 해결할 수 있다.
  • 아무리 멀리 떨어져 있어도 각 지방의 캠퍼스들을 ISP까지만 연결하면 인터넷을 이용하여 저렴한 비용으로 사설 네트워크를 구축할 수 있다.

 

 


이미지 출처

2020 데이터통신 및 네트워크 수업 자료

 📌 필요한 기능

학교 선택 -> 해당 학교 이메일 뒷부분(예를 들어 @korea.ac.kr) 확인 -> 일치한다면 입력한 이메일로 메일 전송 -> 인증

이런 로직으로 학교 인증을 구현하였다.

firebase를 사용하면 이메일 인증도 쉽게 구현할 수 있다.

 

 📌 이메일 전송 코드

// 로그인 및 메일 전송
const sentEmail = await signInWithEmailAndPassword(firebaseAuth, email, password)
  .then(() => {
    sendEmailVerification(firebaseAuth.currentUser);
    return { err: false };
  })
  .catch((e) => {
    console.log(e);
    return { err: true, error: e };
  });

if (sentEmail.err) {
  return res
    .status(statusCode.INTERNAL_SERVER_ERROR)
    .send(util.fail(statusCode.INTERNAL_SERVER_ERROR, 
    		responseMessage.SEND_VERIFICATION_EMAIL_FAIL));
}

 

 📌 인증 여부 확인 코드

const userFirebase = await signInWithEmailAndPassword(firebaseAuth, email, password)
  .then((user) => user)
  .catch((e) => {
    console.log(e);
    return { err: true, error: e };
  });
  
  ...
  
const {
  user: { uid: firebaseId, emailVerified: isEmailVerified },
} = userFirebase;
// const firebaseId = userFirebase.user.uid;
// const isEmailVerified = userFirebase.user.emailVerified; 와 동일

if(!isEmailVerfied) {
	// 이메일 인증이 되지 않은 유저에 대한 처리
}

인증 여부는 로그인 정보의 emailVerifed를 가지고 처리해주었다.

 

 📌 이메일 전송에서 어려웠던 점

serverless 환경이 아니라 서버측에서 이메일 인증을 구현하였기 때문에 해당 유저가 로그인이 되어있는지 알 수 없다.

따라서 로그인을 시키고 이메일을 전송해야 한다.

 

signInWithEmailAndPassword로 로그인을 시키고 그 이후 sendEmailVerification을 사용하여 인증 메일을 보낸다.

+ Recent posts