diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d37466c --- /dev/null +++ b/.gitignore @@ -0,0 +1,95 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## DS_Store +*.DS_Store + +## User settings +xcuserdata/ +xcuserdata/* +*.xcuserstate + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ \ No newline at end of file diff --git a/Git_ Exercise.xcodeproj/project.pbxproj b/Git_ Exercise.xcodeproj/project.pbxproj index 424a5e0..157cb2d 100644 --- a/Git_ Exercise.xcodeproj/project.pbxproj +++ b/Git_ Exercise.xcodeproj/project.pbxproj @@ -8,6 +8,9 @@ /* Begin PBXBuildFile section */ 2768CA08294F453B00990B98 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2768CA07294F453B00990B98 /* main.swift */; }; + 3B7DEA03295003AA002C7E4D /* input data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B7DEA02295003AA002C7E4D /* input data.swift */; }; + 3B7DEA05295004C8002C7E4D /* student class.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B7DEA04295004C8002C7E4D /* student class.swift */; }; + 3B7DEA0729500574002C7E4D /* store data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B7DEA0629500574002C7E4D /* store data.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -25,6 +28,9 @@ /* Begin PBXFileReference section */ 2768CA04294F453B00990B98 /* Git_ Exercise */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Git_ Exercise"; sourceTree = BUILT_PRODUCTS_DIR; }; 2768CA07294F453B00990B98 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 3B7DEA02295003AA002C7E4D /* input data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "input data.swift"; sourceTree = ""; }; + 3B7DEA04295004C8002C7E4D /* student class.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "student class.swift"; sourceTree = ""; }; + 3B7DEA0629500574002C7E4D /* store data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "store data.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -58,6 +64,9 @@ isa = PBXGroup; children = ( 2768CA07294F453B00990B98 /* main.swift */, + 3B7DEA02295003AA002C7E4D /* input data.swift */, + 3B7DEA04295004C8002C7E4D /* student class.swift */, + 3B7DEA0629500574002C7E4D /* store data.swift */, ); path = "Git_ Exercise"; sourceTree = ""; @@ -121,6 +130,9 @@ buildActionMask = 2147483647; files = ( 2768CA08294F453B00990B98 /* main.swift in Sources */, + 3B7DEA03295003AA002C7E4D /* input data.swift in Sources */, + 3B7DEA0729500574002C7E4D /* store data.swift in Sources */, + 3B7DEA05295004C8002C7E4D /* student class.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Git_ Exercise/input data.swift b/Git_ Exercise/input data.swift new file mode 100644 index 0000000..b84f128 --- /dev/null +++ b/Git_ Exercise/input data.swift @@ -0,0 +1,81 @@ +// +// input data.swift +// Git_ Exercise +// +// Created by J.E on 2022/12/19. +// + +import Foundation + +///**'메뉴선택'용 입력값**을 저장하는 변수 +///- main의 repeat-while문의 반복조건 +///- `X`를 입력하면 반복문 종료 +var inputMenu = String() + +///**'기능실행'용 입력값(들)**을 저장하는 변수 +///- main의 switch-case문에서 호출 +///- 중복·가장자리 공백은 자동으로 처리, 유효한 단어 수에 따라 변수에 저장 +var inputData = String() + + +//MARK: - readLine()값 저장 +var (name, subject, grade) = (String(), String(), String()) +func getInputs(_ s: String = (readLine() ?? "")) { + let input = s.components(separatedBy: .whitespaces).compactMap { try? checkInput(str: $0).get() } + switch input.count { + case 1: (name, subject, grade) = (input[0], "", "") + case 2: (name, subject, grade) = (input[0], input[1], "") + case 3: (name, subject, grade) = (input[0], input[1], input[2]) + default: print("입력이 잘못되었습니다. 다시 확인해주세요.") + } +} + + +//MARK: - 허용된 입력값인지 검사 + +///**입력값 에러 종류** 목록 +///- __notMenu__: `inputMenu`에 선택지 6종 외의 값이 들어올 경우 +///- __notAlphanumeric__: `inputMenu` 또는 `inputData`에 영문·숫자가 아닌 자모가 포함된 경우 +enum InputError: Error { + case notMenu(text: String = "뭔가 입력이 잘못되었습니다. 1~5 사이의 숫자 혹은 X를 입력해주세요.") + case notAlphanumeric(text: String = "입력이 잘못되었습니다. 다시 확인해주세요.") +} + +///**입력값 에러 판별** 함수 +///- `checkInput(str:)`: **기능실행** 단계에서 활용 +///- `checkInput(chr:)`: **기능선택** 단계에서 활용 +func checkInput(str: String) -> Result { + var isValid = Bool() + for chr in str { + isValid = chr.isCased || chr.isNumber || chr.isWhitespace || chr == "+" + } + guard isValid else { return .failure(InputError.notAlphanumeric()) } + return .success(isValid ? str : nil) +} +///**입력값 에러 판별** 함수 +///- `checkInput(str:)`: **기능실행** 단계에서 활용 +///- `checkInput(chr:)`: **기능선택** 단계에서 활용 +func checkInput(chr: String) -> Result { + guard chr.count == 1 && ["1", "2", "3", "4", "5", "X"].contains(chr) else { return .failure(InputError.notMenu()) } + return .success(chr) +} + + +///**입력값 에러 처리** 함수 - 기능 선택 시 +///+ 에러가 아니면 true, 에러에 해당하면 false를 반환 +///+ 용도에 따라(guard문 조건 or 문구출력) 다용도로 사용 +func getInput(_ checkedResult: Result) -> Bool { + switch checkedResult { + case .success: return true //return try! checkedResult.get() + case .failure(let error): print("\(error)".components(separatedBy: "\"")[1]); return false + } +} +///**입력값 에러 처리** 함수 - 기능 실행 시 +///+ 에러가 아니면 true, 에러에 해당하면 false를 반환 +///+ 용도에 따라(guard문 조건 or 문구출력) 다용도로 사용 +func getInput(_ checkedResult: Result) -> Bool { + switch checkedResult { + case .success: return true + case .failure(let error): print("\(error)".components(separatedBy: "\"")[1]); return false + } +} diff --git a/Git_ Exercise/main.swift b/Git_ Exercise/main.swift index 986559b..6f148b4 100644 --- a/Git_ Exercise/main.swift +++ b/Git_ Exercise/main.swift @@ -7,5 +7,69 @@ import Foundation -print("Hello, World!") +//MARK: - 지난 실행기록 불러오기 +readWholeData() +repeat { + + //MARK: - 원하는 기능 선택하기 + print(""" + 원하는 기능을 입력해주세요 + 1: 학생추가, 2: 학생삭제, 3: 성적추가(변경), 4: 성적삭제, 5: 평점보기, X: 종료 + """) + inputMenu = readLine() ?? "" + let _ = checkInput(chr: inputMenu) + + //MARK: - 선택한 기능 실행하기 + switch inputMenu { + case "1": + print("추가할 학생의 이름을 입력해주세요") + inputData = readLine() ?? "" + guard getInput(checkInput(str: inputData)) else { print(); continue } + getInputs(inputData) + let _ = Student(name: name) + case "2": + print("삭제할 학생의 이름을 입력해주세요") + inputData = readLine() ?? "" + guard getInput(checkInput(str: inputData)) else { print(); continue } + getInputs(inputData) + print(Student.students.removeValue(forKey: name) == nil ? "\(name) 학생을 찾지 못했습니다." : "") + case "3": + print(""" + 성적을 추가할 학생의 이름, 과목 이름, 성적(A+, A0, F 등)을 띄어쓰기로 구분하여 차례로 작성해주세요. + 입력예) Mickey Swift A+ + 만약에 학생의 성적 중 해당 과목이 존재하면 기존 점수가 갱신됩니다. + """) + inputData = readLine() ?? "" + guard getInput(checkInput(str: inputData)) else { print(); continue } + getInputs(inputData) + guard let student = Student.students[name] else { print("\(name) 학생을 찾지 못했습니다."); break } + student.grades.updateValue(grade, forKey: subject) + case "4": + print(""" + 성적을 삭제할 학생의 이름, 과목 이름을 띄어쓰기로 구분하여 차례로 작성해주세요. + 입력예) Mickey Swift + """) + inputData = readLine() ?? "" + guard getInput(checkInput(str: inputData)) else { print(); continue } + getInputs(inputData) + guard let student = Student.students[name] else { print("\(name) 학생을 찾지 못했습니다."); break } + student.grades.removeValue(forKey: subject) + case "5": + print("평점을 알고싶은 학생의 이름을 입력해주세요") + inputData = readLine() ?? "" + guard getInput(checkInput(str: inputData)) else { print(); continue } + getInputs(inputData) + guard let student = Student.students[name] else { print("\(name) 학생을 찾지 못했습니다."); break } + student.grades.sorted(by: { $0.key.count < $1.key.count }).forEach { print("\($0.key): \($0.value)") } + print("평점 :", "\(student.overallScore)".replacingOccurrences(of: ".0", with: "").prefix(4)) + case "X": + //MARK: - 지난 실행기록 저장하기 + saveWholeData() + print("프로그램을 종료합니다...") + default: + print("뭔가 입력이 잘못되었습니다. 1~5 사이의 숫자 혹은 X를 입력해주세요.") + } + print() + +} while inputMenu != "X" diff --git a/Git_ Exercise/store data.swift b/Git_ Exercise/store data.swift new file mode 100644 index 0000000..82679e4 --- /dev/null +++ b/Git_ Exercise/store data.swift @@ -0,0 +1,28 @@ +// +// store data.swift +// Git_ Exercise +// +// Created by J.E on 2022/12/19. +// + +import Foundation + +//MARK: - 학생객체를 백그라운드에 저장하는 방법 + +///이번 실행데이터 저장하기 +func saveWholeData() { + let path = "/Users//Desktop/새싹/tastycode_SeSAC_1st/MyCreditManager_cdenen20/MyCreditManager_cdenen20/Info.plist" + let backupDictionary = NSMutableDictionary(contentsOfFile: path) ?? [:] + let encoder = PropertyListEncoder() + let encodedData = try! encoder.encode(Student.students) + try! encodedData.write(to: .init(fileURLWithPath: path)) +} + +///지난 실행데이터 불러오기 +func readWholeData() { + let path = "/Users//Desktop/새싹/tastycode_SeSAC_1st/MyCreditManager_cdenen20/MyCreditManager_cdenen20/Info.plist" + let backupDictionary = try! Data(contentsOf: URL(fileURLWithPath: path)) + let decoder = PropertyListDecoder() + let data = try! decoder.decode([String: Student].self, from: backupDictionary) + Student.students = data +} diff --git a/Git_ Exercise/student class.swift b/Git_ Exercise/student class.swift new file mode 100644 index 0000000..7c60464 --- /dev/null +++ b/Git_ Exercise/student class.swift @@ -0,0 +1,52 @@ +// +// student class.swift +// Git_ Exercise +// +// Created by J.E on 2022/12/19. +// + +import Foundation + +///학생객체를 생성하고 관리하는 클래스 +///- `students`: 이름(key)에 대응하는 학생객체(value)를 저장 +///- `grades`: 과목(key)에 대응하는 성적(value)을 저장 +class Student: Codable { + + static var students = [String: Student]() + let name: String + var grades = [String: String]() { + didSet { + switch grades.count - oldValue.count { + case 1: print("\(name) 학생의 \(subject) 과목이 \(grade)로 추가되었습니다.") + case 0 where grade != "": print("\(name) 학생의 \(subject) 과목이 \(grade)로 변경되었습니다.") + case 0 where grade == "": print("\(name) 학생의 \(subject) 과목의 성적을 찾을 수 없습니다.") + default: print("\(name) 학생의 \(subject) 과목의 성적이 삭제되었습니다.") + } + } + } + + ///평점 계산 + var overallScore: Double { + let gradeList = ["F", "D0", "D+", "C0", "C+", "B0", "B+", "A0", "A+"] + let scoreList = [0, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5] + guard !grades.isEmpty else { return 0 } + return grades.values.map { scoreList[gradeList.firstIndex(of: $0) ?? 0] }.reduce(0, +) / Double(grades.count) + } + + ///학생 추가 + init?(name: String) { + if Student.students.keys.contains(name) { + print("\(name)은(는) 이미 존재하는 학생입니다. 추가하지 않습니다.") + return nil + } + self.name = name + Self.students.updateValue(self, forKey: name) + print("\(name) 학생을 추가했습니다.") + } + + ///학생 삭제 + deinit { + print("\(name) 학생을 삭제하였습니다.") + } + +}