import Foundation // Foundation 프레임워크 임포트
// A. 학생 정보 저장
var STUDENTS: [String: String] = [:]
// B. 학생별 듣는 과목들 저장
var SUBJECTS: [String: Set<String>] = [:]
// C. 학생별 과목별 성적 저장
var GRADES: [String: [String: [Int]]] = [:] // 학생 번호 -> 과목 -> 성적 배열
// I. 학생 관리
// 1. 학생 목록을 조회하는 함수
func listStudents() {
print("학생 목록:")
for (number, name) in STUDENTS {
let studentSubjects = SUBJECTS[number]?.joined(separator: ", ") ?? "없음" // SUBJECTS[number]가 존재하지 않으면 "없음" 출력
print("학생 번호: \(number), 이름: \(name), 듣는 과목: \(studentSubjects)")
}
}
// 2. 학생 정보를 조회하는 함수
func viewStudent(number: String) {
if let name = STUDENTS[number] { // STUDENTS[number]가 존재하면 name 상수에 할당, 아니면 else로
let studentSubjects = SUBJECTS[number]?.joined(separator: ", ") ?? "없음" // subjects[number]가 존재하지 않으면 "없음" 출력
print("학생 번호: \(number), 이름: \(name), 듣는 과목: \(studentSubjects)")
for (subject, scores) in GRADES[number] ?? [:] { //각 과목에 대해 점수 Array를 나열, 과목이 존재하지 않으면 [:]로 처리
print("\(subject) 성적: \(scores)")
}
} else {
print("해당 번호의 학생이 존재하지 않습니다.")
}
}
// 3. 학생을 등록하는 함수
func addStudent(number: String, name: String) {
STUDENTS[number] = name
SUBJECTS[number] = [] // 학생을 등록할 때 과목 Set도 초기화
GRADES[number] = [:] // 성적 정보도 초기화
print("\(number) 번호로 \(name) 학생이 등록되었습니다. )")
}
// II. 과목 관리
// 4. 과목 추가하는 함수
func addSubject(number: String, subject: String) {
if var studentSubjects = SUBJECTS[number] { // 불러온 SUBJECTS[number]를 studentSubjects에 저장, 없으면 else로
studentSubjects.insert(subject)
SUBJECTS[number] = studentSubjects // 과목 추가한 studentSubjects를 SUBJECTS[number]에 저장
GRADES[number]?[subject] = [] // GRADES -> [학생 번호:[과목:[점수]]] / 성적 정보 삭제
print("\(STUDENTS[number] ?? "알 수 없는 학생")의 \(subject) 과목이 추가되었습니다.")
} else {
print("해당 번호의 학생이 존재하지 않습니다.")
}
}
// 5. 과목 제거하는 함수
func removeSubject(number: String, subject: String) {
if var studentSubjects = SUBJECTS[number] { // 불러온 SUBJECTS[number]를 studentSubjects에 저장, 없으면 else로
if studentSubjects.remove(subject) != nil { // 여기서 != nil은 기능하는데 필요하진 않음. 다만 없는 과목을 삭제하려 해도 동작하는 게 문제임
SUBJECTS[number] = studentSubjects // 과목 삭제한 studentSubjects를 SUBJECTS[number]에 저장
GRADES[number]?.removeValue(forKey: subject) // GRADES -> [학생 번호:[과목:[점수]]] / 성적 정보 삭제
print("\(STUDENTS[number] ?? "알 수 없는 학생")의 \(subject) 과목이 삭제되었습니다.")
} else {
print("\(subject) 과목이 존재하지 않습니다.")
}
} else {
print("해당 번호의 학생이 존재하지 않습니다.")
}
}
// III. 과목별 성적 관리
// 6. 과목별 성적 추가하는 함수
func addGrade(number: String, subject: String, grade: Int) {
guard let _ = STUDENTS[number] else {
print("해당 번호의 학생이 존재하지 않습니다.")
return
}
guard let _ = GRADES[number]?[subject] else {
print("\(subject) 과목이 존재하지 않거나 해당 학생이 없습니다.")
return
}
GRADES[number]?[subject]?.append(grade)
print("\(STUDENTS[number] ?? "알 수 없는 학생")의 \(subject) 과목에 성적 \(grade) 추가되었습니다.")
}
// 7. 과목별 성적 초기화하는 함수
func resetGrades(number: String, subject: String) {
guard let _ = STUDENTS[number] else {
print("해당 번호의 학생이 존재하지 않습니다.")
return
}
guard GRADES[number]?[subject] != nil else {
print("\(subject) 과목이 존재하지 않거나 해당 학생이 없습니다.")
return
}
GRADES[number]?[subject] = [] // 성적 초기화
print("\(STUDENTS[number] ?? "알 수 없는 학생")의 \(subject) 과목 성적이 초기화되었습니다.")
}
// IV. 성적 평균 계산
// 8. 학생의 각 과목 점수의 평균과 전체 평균을 계산하는 함수
func calculateAverageGrades(number: String) {
guard let name = STUDENTS[number] else {
print("해당 번호의 학생이 존재하지 않습니다.")
return
}
guard let studentGrades = GRADES[number] else { // studentGrades에 GRADES[number] 담기
print("\(name) 학생의 성적 정보가 없습니다.")
return
}
var subjectAverages: [String: Double] = [:]
for (subject, scores) in studentGrades { // GRADES[number]가 담겨 있는 studentGrades에 대해서 반복
if !scores.isEmpty {
let average = Double(scores.reduce(0, +)) / Double(scores.count)
subjectAverages[subject] = average
} else {
subjectAverages[subject] = 0.0 // 성적이 없으면 평균을 0으로 설정
}
}
// 각 과목 평균 출력
print("\(name) 학생의 각 과목 평균:")
for (subject, average) in subjectAverages {
print("\(subject): \(average)")
}
// 전체 평균 계산
let allAverages = subjectAverages.values
let totalAverage = allAverages.isEmpty ? 0.0 : allAverages.reduce(0, +) / Double(allAverages.count)
print("\(name) 학생의 전체 평균: \(totalAverage)")
}
// 10. 명령어 도움말 함수
func showHelp() {
print("""
사용 가능한 명령어:
1. listStudents: 등록된 학생 목록 조회
2. viewStudent <번호>: 특정 학생 정보 조회
3. addStudent <번호> <이름>: 학생 등록
4. addSubject <번호> <과목>: 특정 학생에게 과목 추가
5. removeSubject <번호> <과목>: 특정 학생에게 과목 제거
6. addGrade <번호> <과목> <성적>: 특정 학생의 과목에 성적 추가
7. resetGrades <번호> <과목>: 특정 학생의 과목 성적 초기화
8. averageGrades <번호>: 특정 학생의 각 과목 성적 평균 및 전체 평균 계산
9. help: 사용 가능한 명령어 열람
0. exit: 프로그램 종료
""")
}
// executeCommand
func executeCommand(_ command: String) {
let components = command.split(separator: " ").map { String($0) } // 문자열 command를 공백 기준으로 나누고 (배열 생성), 각 부분을 String 타입으로 변환 (map으로 또다른 배열 생성)
guard let action = components.first else { // map으로 생성한 배열의 첫번째 String을 action에 저장.
print("잘못된 명령어입니다.")
return
}
switch action { // action이 무슨 명령어인지 파악
case "listStudents":
listStudents()
case "viewStudent":
if components.count == 2 {
let number = components[1]
viewStudent(number: number)
} else {
print("명령어 형식이 잘못되었습니다. 예: viewStudent 001")
}
case "addStudent":
if components.count == 3 {
let number = components[1]
let name = components[2]
addStudent(number: number, name: name)
} else {
print("명령어 형식이 잘못되었습니다. 예: addStudent 001 홍길동")
}
case "addSubject":
if components.count == 3 {
let number = components[1]
let subject = components[2]
addSubject(number: number, subject: subject)
} else {
print("명령어 형식이 잘못되었습니다. 예: addSubject 001 수학")
}
case "removeSubject":
if components.count == 3 {
let number = components[1]
let subject = components[2]
removeSubject(number: number, subject: subject)
} else {
print("명령어 형식이 잘못되었습니다. 예: removeSubject 001 수학")
}
case "addGrade":
if components.count == 4 {
let number = components[1]
let subject = components[2]
if let grade = Int(components[3]) {
addGrade(number: number, subject: subject, grade: grade)
} else {
print("성적은 숫자로 입력해야 합니다.")
}
} else {
print("명령어 형식이 잘못되었습니다. 예: addGrade 001 수학 85")
}
case "resetGrades":
if components.count == 3 {
let number = components[1]
let subject = components[2]
resetGrades(number: number, subject: subject)
} else {
print("명령어 형식이 잘못되었습니다. 예: resetGrades 001 수학")
}
case "averageGrades":
if components.count == 2 {
let number = components[1]
calculateAverageGrades(number: number)
} else {
print("명령어 형식이 잘못되었습니다. 예: averageGrades 001")
}
case "help":
showHelp()
case "exit":
print("프로그램을 종료합니다.")
exit(0)
default:
print("알 수 없는 명령어입니다.")
}
}
// 메인 루프: 사용자 입력을 계속 받아들임
while true {
print("명령어를 입력하세요 (도움말은 'help' 입력): ", terminator: "")
if let command = readLine() {
executeCommand(command) // func executeCommand(_ command: String)에서 '_'를 붙였으니 'command: command'라 안적어도 됨
}
}
Foundation
프레임워크
Foundation
프레임워크: Swift와 Objective-C에서 기본적으로 제공되는 클래스와 기능 포함func
- 함수 정의하기
func functionName(parameters) -> ReturnType {
// 코드 블록
}
옵셔널 체이닝(Optional Chaining) - ?
?
는 옵셔널 변수가 nil
인지 아닌지를 확인한다.nil
이 아니라면 그 뒤의 내용을 실행한다.nil
이라면, 그 뒤의 내용을 실행하지 않고 nil
을 반환한다.// 옵셔널 변수
var optionalString: String? = "Hello"
// 옵셔널 체이닝을 통한 문자열 길이 확인
let length = optionalString?.count
print(length) // Optional(5)
// 옵셔널이 nil인 경우
optionalString = nil
let length2 = optionalString?.count
print(length2) // nil
널 병합 연산자 (??
)
nil
일 경우 대체 값을 제공하는 데 사용함문자열 연결하기
.joined(separator:)
: 배열의 문자열 요소를 연결하여 하나의 문자열로 만듦separator:
파라미터를 이용해 각 문자열 요소를 나누는 문자열을 넣을 수 있음와일드카드 (_
) 사용법
nil
이 아닌지 확인할 때, _
를 사용하면 그 값을 사용할 필요가 없다는 것을 나타낼 수 있음guard let _ = students[number] else {
print("해당 번호의 학생이 존재하지 않습니다.")
return
}
guard
- 조건을 만족하지 않을 경우 즉시 실행을 중단하는 제어문
guard
의 else
블록에서는 반드시 return
, break
, continue
, throw
중 하나를 사용하여 제어 흐름을 변경해야 한다.guard 조건 else {
// 조건이 false일 때 실행될 코드
// 일반적으로 return, break, continue, throw 등이 사용됨
}
return
정의: return
은 함수를 종료하고, 호출한 곳으로 제어를 반환하는 명령어이다.
함수가 값을 반환하는 경우, return
을 사용하여 값을 돌려준다.
용도: 함수의 실행을 중단하고 값을 반환하거나, 값을 반환하지 않고 단순히 함수를 종료할 때 사용한다.
func sayHello(to name: String) -> String {
return "Hello, \(name)!" // "Hello, John!"과 같은 문자열을 반환
}
func printGreeting(name: String?) {
guard let validName = name else {
print("이름이 없습니다.")
return // 함수 종료
}
print("안녕하세요, \(validName)님!")
}
print(sayHello(to: "John")) // "Hello, John!"
printGreeting(name: nil) // "이름이 없습니다."
break
정의: break
는 반복문이나 switch
문을 즉시 중단하고, 해당 블록을 빠져나가는 명령어이다.
용도: 반복문(for
, while
, repeat
)이나 switch
문에서 현재 실행 중인 루프나 switch
를 종료할 때 사용한다.
let numbers = [1, 2, 3, 4, 5]
for number in numbers {
if number == 3 {
print("3을 찾았습니다!")
break // 반복문을 즉시 종료
}
print(number)
}
continue
continue
는 현재 반복문에서 남은 코드를 건너뛰고, 다음 반복을 진행하는 명령어이다.for number in 1...5 {
if number % 2 == 0 {
continue // 짝수는 건너뛰고, 다음 반복으로
}
print("\(number)는 홀수입니다.")
}
throw
정의: thorw
는 에러를 발생시키는 명령이다. 함수나 메소드가 에러를 던질 때 사용되며, 발생된 에러는 do-catch
구문을 통해 처리된다.
용도: 에러가 발생할 수 있는 함수나 메소드에서 특정 상황에서 에러를 던지고, 호출한 코드에서 해당 에러를 처리할 수 있도록 한다.
enum PasswordError: Error {
case tooShort
case tooSimple
}
func validatePassword(_ password: String) throws {
if password.count < 6 {
throw PasswordError.tooShort // 에러 던짐
}
if password == "123456" {
throw PasswordError.tooSimple // 에러 던짐
}
print("비밀번호가 유효합니다.")
}
do {
try validatePassword("123456") // 에러 발생
} catch PasswordError.tooShort {
print("비밀번호가 너무 짧습니다.")
} catch PasswordError.tooSimple {
print("비밀번호가 너무 단순합니다.")
}
클로저(Closures)
// 기본 문법
{ (parameters) -> returnType in
code
}
// 예시
let sumClosure = { (a: Int, b: Int) -> Int in
return a + b
}
print(sumClosure(3, 5)) // 출력: 8
$0
, $1
등의 사용$0
, $1
, $2
등은 Swift 클로저에서 사용되는 익명 매개변수이다.map
, filter
, reduce
같은 고차 함수에서 사용됨reduce
메소드
일종의 반복문처럼 작동함
배열의 모든 요소를 결합해 단일 값으로 줄이는 데 사용
let result = collection.reduce(initialResult) { (currentResult, element) in
// 연산 수행
return updatedResult
}
reduce
메소드는 클로저를 두 가지 방식으로 사용할 수 있음
let sum = numbers.reduce(0) { $0 + $1 }
{ $0 + $1 }
는 명시적 클로저로, 배열의 각 요소를 차례로 처리하는 방법을 지정함$0
은 현재까지 계산된 결과(누적값)$1
은 배열의 현재 요소let sum = numbers.reduce(0, +)
0
은 초기 값. (만약 곱셈을 하고 싶다면 1을 놔야할 것)+
는 함수처럼 동작하는 연산자임. +
연산자는 사실 func +(lhs: Int, rhs: Int) -> Int
와 같은 함수 형태로 존재함 (참고: 연산자 계산 시 자주 쓰는 이름 / lhs
left-hand side, rhs
: right-hand side)let sum = numbers.reduce(0, { (currentSum: Int, number: Int) -> Int in
return currentSum + number
})
// 0: 초기값. currentSum 에 전달됨
// 클로저: (currentSum, number)의 두 매개변수를 받고, 두 값을 더해 반환하는 코드
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0) { (result, number) in
return result + number
}
print(sum) // 출력: 15
위의 코드를 클로저 대신 연산자를 사용하면 간단하게 표현할 수 있음let sum = numbers.reduce(0, +)
print(sum) // 출력: 15
reduce(0, +)
분석
0
은 초기 값. (만약 곱셈을 하고 싶다면 1을 놔야할 것)+
는 함수처럼 동작하는 연산자임. +
연산자는 사실 func +(lhs: Int, rhs: Int) -> Int
와 같은 함수 형태로 존재함 (연산자 계산 시 자주 쓰는 이름 / lhs: left-hand side, rhs: right-hand side)let sum = numbers.reduce(0, { (currentSum: Int, number: Int) -> Int in
return currentSum + number
})
// 0: 초기값. currentSum 에 전달됨
// 클로저: (currentSum, number)의 두 매개변수를 받고, 두 값을 더해 반환하는 코드
파라미터(parameter)와 인자(argument)의 차이
func add(a: Int, b: Int) -> Int {
return a + b
}
// a와 b는 파라미터. 함수가 호출될 때 어떤 값을 받을지 정의한 변수.
let result = add(a: 3, b: 5)
// `3`과 `5`는 인자. 함수가 실행될 때 함수에 실제로 전달되는 값.
삼항 연산자(ternary operator)
condition ? valueIfTrue : valueIfFalse
매개변수 이름의 두 가지 종류
매개변수 앞에 _
붙이기
_
를 붙이면 외부 매개변수의 이름을 생략할 수 있다.func exampleFunction(_ firstName: String, lastName: String) {
print("First name: \(firstName), Last name: \(lastName)")
}
exampleFunction("John", lastName: "Doe")
split
메소드
.split(separator:)
은 문자열을 특정 구분자(delimiter)로 나눠 배열로 반환함Substring
배열로 반환됨Substring
타입
Substring
은 String
의 부분 문자열을 나타내는 타입String
의 일부를 메모리에서 효율적으로 참조 -> String
으로 쓰기에는 변환이 필요함map
메소드
nil
요소에 대한 처리가 불가능함. 옵셔널 처리가 필요하면 compactMap
을 써야 한다.// 기본 문법
let newArray = oldArray.map { (element) in
// 변환 로직
return 변환된 값
}
let numbers = [1, 2, 3, 4, 5]
// 각 요소를 2배로 변환하는 map
let doubled = numbers.map { (num) in
return num * 2
}
print(doubled) // 출력: [2, 4, 6, 8, 10]
클로저의 익명 매개변수를 사용해 더 간결하게 작성할 수 있다.
$0
은 배열의 각 요소를 의미한다.
let doubled = numbers.map { $0 * 2 }
print(doubled) // 출력: [2, 4, 6, 8, 10]
while true
로 무한 루프를 생성할 수 있다.
readLine()
을 통해 콘솔에서 상호작용하는 애플리케이션을 만들 수 있다.
readLine()
의 반환값은 String
이 아니라 String?
임if let
등으로 옵셔널 처리를 해줘야 함print()
에서 terminator
의 역할
print
는 출력 후에 새 줄(\n
)을 추가하지만, terminator
를 사용하여 이 기본 동작을 변경할 수 있다.print("""...""")
로 문자열 블록 입력하기 (멀티라인 문자열)
멀티라인 문자열 생성
let multilineString = """
안녕하세요!
이것은 멀티라인 문자열입니다.
여러 줄로 구성되어 있습니다.
"""
print(multilineString)
문자열 내부의 특수 문자 처리
\n
, \t
와 같은 특수 문자를 사용하지 않고도 줄바꿈과 공백을 쉽게 표현할 수 있다.문자열 앞에 공백을 포함한 경우
trimmingCharacters(in:)
메소드를 사용할 수 있다. let indentedString = """
Hello,
This is a multiline string
with indentation.
""".trimmingCharacters(in: .whitespacesAndNewlines)
print(indentedString)
trimmingCharacters(in:)
메소드에 대한 공부는 일단 생략...exit()
함수
exit(0)
: 정상적으로 프로그램 종료 (exit()
는 exit(0)
으로 간주됨)exit(1)
: 오류가 발생하여 프로그램 비정상 종료