-
Notifications
You must be signed in to change notification settings - Fork 0
4-MuchanKim #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
4-MuchanKim #20
Conversation
MuchanKim/구현/오픈채팅방.swift
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 이렇게 처리해주는 함수를 만들긴했는데, 저는 이걸 한 번 더 출력을 위한 배열에 담아주고 해결을 하다보니 번거로웠는데, 단계를 한번 더 줄이셔서 코드가 깔끔해요 👍
func addMessage(_ eventType: EventType, _ uid: String, _ userName: String = "") {
switch eventType {
case .Change:
// change된다는 것 자체가 이미 들어와있는놈 바꾸는거라 바로바로 처리해줘도됨 userDB[uid]가 없을 확률 없음
// 문제 설명 中 - 채팅방에서 나간 유저가 닉네임을 변경하는 등 잘못 된 입력은 주어지지 않는다.
userDB[uid] = userName
case .Enter:
userDB[uid] = userName
enterLogArr.append((event: eventType, uid: uid))
default:
enterLogArr.append((event: eventType, uid: uid))
}
}
```
저는 여기서 또 enterLogArr를 순회하면서 출력을 뽑습니닷There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
finn쌤은 그대로 담아서 차례로 출력했네요.
bishoe01
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코드구조가 비슷하네요..! 무선생님의 제 코드의 상위호환느낌이었습니닷!
typealias 사용하신점이 눈에 띄네요. 저도 제 코드에서 userDB를 선언해줄 때 타입을 [String:String]로 해줘야하니까 가독성이 떨어진다고 느꼈는데, 간단하게 해결하는 방법이 있었군요. 자주 써먹겠습니다. 감사해요 👍
// 오픈채팅방
import Foundation
enum EventType: String {
case Enter
case Leave
case Change
}
func solution(_ record: [String]) -> [String] {
var userDB: [String: String] = [:]
var answer = [String]()
// change 빼고 출입력은 따로 관리
var enterLogArr: [(event: EventType, uid: String)] = []
// 밑에다 놔서 깔끔하게 하려는거였는데, Programmers는 선언먼저해야한다고 변수아래로 끌어올렸습니닷
func addMessage(_ eventType: EventType, _ uid: String, _ userName: String = "") {
switch eventType {
case .Change:
// change된다는 것 자체가 이미 들어와있는놈 바꾸는거라 바로바로 처리해줘도됨 userDB[uid]가 없을 확률 없음
// 문제 설명 中 - 채팅방에서 나간 유저가 닉네임을 변경하는 등 잘못 된 입력은 주어지지 않는다.
userDB[uid] = userName
case .Enter:
userDB[uid] = userName
enterLogArr.append((event: eventType, uid: uid))
default:
enterLogArr.append((event: eventType, uid: uid))
}
}
// leave일때는 userName칸이없어서 logArr[2] 접근할때 오류가 떠서 조건문 분리해준것말고는 특별한거 없음
for log in record {
let logArr = log.split(separator: " ")
let eventType = EventType(rawValue: String(logArr[0]))!
let uid = String(logArr[1])
if eventType == .Leave {
addMessage(eventType, uid)
} else {
let userName = String(logArr[2])
addMessage(eventType, uid, userName)
}
}
// 정답출력 부분
for item in enterLogArr {
let userName = userDB[item.uid]!
if item.event == .Enter {
answer.append("\(userName)님이 들어왔습니다.")
} else {
answer.append("\(userName)님이 나갔습니다.")
}
}
return answer
}무선생님 코드보니까 훨씬 깔끔해서 제가 사용하고 있는 addMessage함수가 이전에 길선생님이 리뷰주신 BFS/DFS에서 isRange함수가 너무 많은 것을 감당하고 있다는 이야기가 다시 한번 떠올랐습니다.
메세지만 추가해주는게 아니라 userDB도 바꾸고 있다보니까, 구조에 대해 좀 고민해보게 됐습니닷.
import Foundation
func solution(_ records: [String]) -> [String] {
var nickName: [String: String] = [:] // 유저 아이디 : 닉네임 딕셔너리
var notes: [(String, String)] = [] // 명령어와 닉네임 튜플 배열
for record in records {
let notifications = record.split(separator: " ").map { String($0) } // substring을 string으로 바꿔줌
let command = notifications[0] // 명령어 담기
let userId = notifications[1] // 유저 아이디 담기
if command == "Enter" {
nickName[userId] = notifications[2] // 기존 닉네임 값 담기 + 기존 닉네임과 달라지면 새로운 닉네임 넣기
notes.append(("Enter", userId))
} else if command == "Leave" {
notes.append(("Leave", userId)) // 그냥 기록만 남기기
} else if command == "Change" {
nickName[userId] = notifications[2] // 변경된 닉네임 담기
}
}
var result: [String] = []
for (command, userId) in notes {
let name = nickName[userId]! // nickName은 무조건 존재함 -> 강제추출
let message = command == "Enter" ? "\(name)님이 들어왔습니다." : "\(name)님이 나갔습니다."
result.append(message)
}
return result
}무 ! 문제 완전 재밌었어요 전 구현문제가 오천만배 재밌는 듯 ㅠㅠㅠㅠㅠ |
MuchanKim/구현/오픈채팅방.swift
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앗 저는 여기서 열거형 쓸 생각 못 하고 if문으로 썼는데 보기 좋아요 👍🏻
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아니 ㅋㅋㅋ 걍 각각 명령어 record나누고 담고 상태까지안하고 걍 들어오면 들어오는데로 나가면 나가는데로 찍어주는 거였네요 ㅋㅋㅋ 하.... 글로니 코드도 잘 보고갑니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
헐 enum을 이렇게써서 하는게 있었네요 if문으로 대충 했는데 좋군요!
MuchanKim/구현/오픈채팅방.swift
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
switch문도 까먹고 전 삼항연산자 썼네요 . .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typealias 사용하는 C를 떠올리게하는 문제였고, 너무 재밌는 문제네요 !!!
프로그래머스 문제가 정말 손에 많이 가지만 억지로 손에 안가는 문제를 하려고 백준푸는데 다음엔 프로그래머스도 풀어야겠어요 ㅎㅎㅎ
저는 좀 복잡하게 풀었어요 ... 채팅 로그를 기록을 해줘야한다라고 그냥 문제만 신나서 봤네요... 전에 로그들도 다 변경이 된다. 이 말에 현혹이 되었던거 같아요... 입력과 출력만 본다면 딱히 상태관리와 로그까진 관리해줄 필요가 없었던 코드 같네요.
import Foundation
// MARK: - Action Enum
enum ActionType: String {
case enter = "Enter"
case leave = "Leave"
case change = "Change"
}
// MARK: - Record Struct
struct Record {
let action: ActionType
let userID: String
let nickname: String?
init?(_ line: String) {
let parts = line.split(separator: " ").map(String.init)
guard let action = ActionType(rawValue: parts[0]) else { return nil }
self.action = action
self.userID = parts[1]
self.nickname = parts.count == 3 ? parts[2] : nil
}
}
// MARK: - User Class
class User {
let id: String
private(set) var nickname: String
init(id: String, nickname: String) {
self.id = id
self.nickname = nickname
}
func updateNickname(_ newNickname: String) {
self.nickname = newNickname
}
}
// MARK: - Parsing
func parseRecords(_ record: [String]) -> [Record] {
return record.compactMap { Record($0) }
}
// MARK: - Build User Map -> 최종 유저 닉네임 상태 기억하기위한 함수
func buildUserMap(from records: [Record]) -> [String: User] {
var userMap: [String: User] = [:]
for record in records where record.action != .leave {
guard let nickname = record.nickname else { continue }
if let user = userMap[record.userID] {
user.updateNickname(nickname)
} else {
userMap[record.userID] = User(id: record.userID, nickname: nickname)
}
}
return userMap
}
// MARK: - Generate Logs
func generateLogs(from records: [Record]) -> [(ActionType, String)] {
return records.compactMap {
switch $0.action {
case .enter, .leave:
return ($0.action, $0.userID)
default:
return nil
}
}
}
// MARK: - Generate Messages
func generateMessages(from logs: [(ActionType, String)], using userMap: [String: User]) -> [String] {
return logs.map { action, userID in
guard let user = userMap[userID] else { return "" }
switch action {
case .enter:
return "\(user.nickname)님이 들어왔습니다."
case .leave:
return "\(user.nickname)님이 나갔습니다."
default:
return ""
}
}
}
// MARK: - Solution
func solution(_ record: [String]) -> [String] {
let parsed = parseRecords(record)
let userMap = buildUserMap(from: parsed)
let logs = generateLogs(from: parsed)
return generateMessages(from: logs, using: userMap)
}
MuchanKim/구현/오픈채팅방.swift
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typealias 를 사용하셨군요 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
옹 typealias는 생각안하고 구현했는데 이것도 좋네요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typealias fxxking Swiftful!
MuchanKim/구현/오픈채팅방.swift
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dictionary로 파싱했군요.
MuchanKim/구현/오픈채팅방.swift
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
record를 여기서 담고 나눴군요 !
MuchanKim/구현/오픈채팅방.swift
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
어쩌면 제가 너무 복잡하게 푼것일수 도 있나봐요.
MuchanKim/구현/오픈채팅방.swift
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그냥 문제에서 요구하는게 여러 action들에 대해서 하나의 액션을 가져와서 배열에 담고 뱉어주기만하면되는데.. 저는 log까지 생각해서 전에께 다 바뀌어야 생각해서 log함수를 따로 측정하고 generateMessage를 했어요.
alstjr7437
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
뭔가 저는 급하게 문제를 풀어야해!!
이걸 위해서 풀어서 가독성이 그렇게 좋진 못하네요!!(그래서 주석 추가했어요..)
급하게 풀어놓고 리팩토링한다면 무 코드로 바뀔 것 같아요 좋은 코드를 만들어주셔서 감사합니다!!
import Foundation
func solution(_ record:[String]) -> [String] {
// 아이디: 닉네임 저장
var name: [String: String] = [:]
// (방문여부 아이디) 저장
var visited: [String] = []
// 명령어 나누는 부분
for current in record {
// 0: 명령어 1: 아이디 2: 닉네임
let order = current.split(separator: " ").map { String($0) }
if order[0] == "Enter" {
visited.append("방문 \(order[1])")
name[order[1]] = order[2]
} else if order[0] == "Leave" {
visited.append("이탈 \(order[1])")
} else if order[0] == "Change" {
name[order[1]] = order[2]
}
}
// 결과 만드는 부분
var result: [String] = []
for visit in visited {
let order = visit.split(separator: " ").map { String($0) }
if order[0] == "방문" {
result.append("\(name[order[1]]!)님이 들어왔습니다.")
} else if order[0] == "이탈" {
result.append("\(name[order[1]]!)님이 나갔습니다.")
}
}
return result
}
MuchanKim/구현/오픈채팅방.swift
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
옹 typealias는 생각안하고 구현했는데 이것도 좋네요!
MuchanKim/구현/오픈채팅방.swift
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
헐 enum을 이렇게써서 하는게 있었네요 if문으로 대충 했는데 좋군요!
MuchanKim/구현/오픈채팅방.swift
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오호 Action을 만들어서 enum으로 하니 가독성이 좋네요
MuchanKim/구현/오픈채팅방.swift
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
함수화 시켜주니까 main이 확실히 깔끔해지네요
헐 글로니 선생님 처음에 딕셔너리로 했다가 어 키값 겹치네 하면서 문자열로 했는데 덕분에 깨닫고 갑니다.. |
|
@alstjr7437 저도 그거 겹쳐서 고민하다가 튜플로 결정한 건데요! 튜플 제대로 첨 써봣읍니다 ㅋㅋ .. |
giljihun
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
어찌저찌... 풀긴했는데 다른 분들 로직보니 반성 많이 한 문제입니다.
저는 유저별 로그를 저장하고, 닉네임이 바뀔 때
기존 메시지를 다 수정하면서(계속 업데이트) 진행을 해야한다고 생각을 했습니다.
문제 해결 보단 실시간 갱신?에 매몰되었네요.
제가 보기에는 Enter, Leave같은 액션을 기록하고
모든 로그가 끝나고 최신 닉네임으로 메시지를 생성. 하는 로직으로 다들 짜신것 같은데요!
func solution(_ record:[String]) -> [String] {
var userDB: [String: (name: String, log: [([String], order: Int)])] = [:]
var logOrder: Int = 1
record.forEach { line in
let lineComponents = line.components(separatedBy: " ")
let action = lineComponents[0]
let uid = lineComponents[1]
switch action {
case "Enter":
if lineComponents.count > 2 {
let name = lineComponents[2]
enterChat(uid, name)
}
case "Leave":
leaveChat(uid)
case "Change":
if lineComponents.count > 2 {
let name = lineComponents[2]
changeName(uid, name)
}
default: return
}
}
// MARK: - Functions
func enterChat(_ uid: String, _ name: String) {
if userDB[uid] == nil {
userDB[uid] = (name: name, log: [(["\(name)님이 들어왔습니다."], logOrder)])
logOrder += 1
} else {
userDB[uid]?.name = name
userDB[uid]?.log.append(contentsOf: [(["\(name)님이 들어왔습니다."], logOrder)])
logOrder += 1
changeName(uid, name)
}
}
func leaveChat(_ uid: String) {
guard let user = userDB[uid] else { return }
userDB[uid]?.log.append((["\(user.name)님이 나갔습니다."], logOrder))
logOrder += 1
}
func changeName(_ uid: String, _ newName: String) {
guard let user = userDB[uid] else { return }
let updatedLogs = user.log.map { (entry, order) -> ([String], Int) in
let updatedEntry = entry.map { message -> String in
// "님" 앞까지는 이전 이름이므로, 그 부분을 잘라내고 새 이름으로 교체
guard let range = message.range(of: "님") else { return message }
let suffix = message[range.lowerBound...]
return "\(newName)\(suffix)"
}
return (updatedEntry, order)
}
userDB[uid] = (name: newName, log: updatedLogs)
}
// MARK: - Output
var allLogs: [(String, Int)] = []
for (_, user) in userDB {
for (messages, order) in user.log {
for message in messages {
allLogs.append((message, order))
}
}
}
let sortedLogs = allLogs.sorted(by: { $0.1 < $1.1 })
return sortedLogs.map { $0.0 }
}특히 반성
var userDB: [String: (name: String, log: [([String], order: Int)])] = [:]
var logOrder: Int = 1위처럼 유저디비를 만들고 관리를 하려고 막 짜다가 막힌 부분이
changName() 함수를 작성하는 부분이었습니다.
저는 "님" 앞까지는 이전 이름이므로, 그 부분을 잘라내고 새 이름으로 교체하는 로직으로
변경 함수를 작성했거든요!
이게 무와 다른 분들 코드를 보니 정말 정말 최악으로 비효율적이네요 ㅋㅋㅋ
물론 문제 조건 상, 알파벳의 닉네임만 들어오기 때문에 문제는 없었으나.
구조상 많이 비효율적인 것 같습니다.
다른 분들 코드 보고 많이 배우게 되는 문제였습니다. 감사합니다.
MuchanKim/구현/오픈채팅방.swift
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typealias fxxking Swiftful!

🔗 문제 링크
오픈채팅방
✔️ 소요된 시간
1시간 + @
✨ 수도 코드
구현문제라 딱히 설명할게 없네유
중요포인트는
"잘못된 입력은 주어지지않는다." 입니다. 입장을 안했는데 닉네임 변경 시도가 있거나 퇴장이 있을 수 없어요. 이 외에 다른 케이스도 마찬가지.
유저정보는 유저아이디: 닉네임쌍이고 기능은 Enter, Change, Leave로 잡고 시작했어요.
액션들을 담을 자료구조는 (액션, 유저아이디)로 만들어줬습니다.
record 문자열 배열을 순회하면서 공백 단위로 쪼개줍니다. [0]은 액션, [1]은 유저아이디로 따로 담아줍시다. 닉네임은 leave일 경우 안받기 때문에 따로 처리해줘야함.
닉네임 업데이트(생성/변경)이 일어나는 명령어의 case는 Enter, Change 입니다. 별도로 둘까하다가 userID 존재 여부 파악하는게 시간 더 걸릴 것 같아 이대로 구현했어요. 따로 둔다면, change일 경우에 userID 존재 여부를 파악하고 닉네임 값을 바꿔 줄 수 있을 듯.
기록이 남는 경우의 명령어는 Enter, Leave입니다. 해당 케이스 일 경우 액션 배열에 튜플 형태로 넣어줍니당.
액션 기록 한 배열을 순회하면서 각 케이스별로 문자열보간법써서 결과 배열에 담아주면 끝.
이제보니 변수명 너무 대충지었네요..ㅋㅋㅋ
📚 새롭게 알게된 내용
typealias
이거 쓴 이유?? 길이쓰는거 보니까 가독성이 좋아져서요. 근데 유지보수 측면에서도 좋겠더라고요?
만약 나중에 UserID가 String이 아닌 Int나 UUID로 바뀌어야 한다면? typealias 선언 한 곳만 수정하면 됨.
CustomStringConvertible
디버깅용으로 action을 출력해보니까 저런식으로 뜨더라고요. 어짜피 .enter로 비교해주면 되는데 저대로 값이 들어가는거라고 착각해서 새로운 것 하나 알았습니다 ㅋ
CustomStringConvertible 채택해주고 별도로 구현하면 원하는 값으로 쉽게 출력 가능합니다. 아래처럼요~~
예시블로그
디버깅용 코드 작성할 때 유용할 듯요!