###### tags: `7주차` `계산기` `README` # ➗✖️ 계산기 ➕➖ > 프로젝트 기간 : 2023.06.12 ~ 2023.06.16 ## 📖 목차 1. [소개](#1.) 2. [팀원](#2.) 3. [타임라인](#3.) 4. [다이어그램](#4.) 5. [실행 화면](#5.) 6. [트러블 슈팅](#6.) 7. [참고 링크](#7.) 8. [팀 회고](#8.) <br> <a id="1."></a> ## 1. 📢 소개 사용자로부터 입력받은 숫자와 연산자를 활용하여 순차적으로 연산을 진행합니다. - 주요 개념: `UIKit`, `Queue`, `Linked-List` <br> <a id="2."></a> ## 2. 👤 팀원 | [Serena 🐷](https://github.com/serena0720) | [yyss99 🥹](https://github.com/yy-ss99)| | :--------: | :--------: | | <Img src = "https://i.imgur.com/q0XdY1F.jpg" width="450"/>| <Img src = "https://hackmd.io/_uploads/HypGZ8YP3.png" width="450"/>| <br> <a id="3."></a> ## 3. ⏱️ 타임라인 <details> <summary>타임라인</summary> |날짜|내용| |:---:|---| | **2023.06.13** |▫️ componentsByOpertators 함수 병합 <br> ▫️ ExpressionParser 함수 병합 <br> ▫️ CalculatorError 파일 생성 <br> ▫️ CaculatorViewController 버튼 기능 구현 <br> ▫️ CaculatorViewController 익스텐션 구현 <br> ▫️ viewdidload 초기값 세팅 | | **2023.06.14** |▫️ ExpressionParserTests 생성 | | **2023.06.15** |▫️ CalculatorNameSpace enum 생성 <br>▫️ tapSerialZeroButton 함수 삼항연산자 삭제 <br>▫️ tapOperatorButton 함수 기능 분리 | | **2023.06.16** |▫️ NameSpace 수정 및 추가 <br> ▫️ addFormulaStackView 함수 기능 분리 <br> ▫️ clearFormula 함수 기능 분리 <br> ▫️ configureFormulaStackView 함수로 기능 분리 | </details> <br> <a id="4."></a> ## 4. 📊 다이어그램 <Img src = "https://hackmd.io/_uploads/B1NmkYKDn.png" width="700"/> <br> <a id="5."></a> ## 5. 📲 실행 화면 | 0인 상태에서 연산자 입력 | .에 대한 예외처리 | 소수점끼리 연산 | |:---:|:---:|:---:| |<Img src = "https://hackmd.io/_uploads/BkxeoDKv3.gif" width="200"/> | <Img src = https://hackmd.io/_uploads/H1jNswFP3.gif width="200"/>| <Img src = "https://hackmd.io/_uploads/SyFKsDtDn.gif" width="200"/>| | **무한 소수점 출력 </br> (15번째 소수점에서 반올림)** | **NaN 출력** |**숫자 00 입력** | |<Img src = "https://hackmd.io/_uploads/Byvq3vYPh.gif" width="200"/> | <Img src = "https://hackmd.io/_uploads/H1HER_FDh.gif" width="200"/>| <Img src = "https://hackmd.io/_uploads/rkOfRwYv2.gif" width="200"/>| | **AC/CE** | **마이너스 부호 변환** | **FormulaStack 자동 스크롤** | |<Img src = "https://hackmd.io/_uploads/rJyD0wYPn.gif" width="200"/> | <Img src = https://hackmd.io/_uploads/ryJcAPtvn.gif width="200"/>| <Img src = "https://hackmd.io/_uploads/SJkzlwYPh.gif" width="200"/>| <br> <a id="6."></a> ## 6. 🛎️ 트러블 슈팅 ### 🔥 자료 구조 선택 #### 더블 스택 큐 vs 단방향 연결 리스트 **단방향 연결리스트** - 단방향 연결리스트는 배열에 비해 데이터 추가 및 삽입이 용이합니다. head와 tail을 이용해서 추가할 노드와 삭제할 노드를 관리합니다. - 메모리를 효율적으로 사용가능합니다. value가 있을 때만 Node를 만들어서 추가하기 때문에 value에 nil이 들어가는 경우는 생기지 않습니다. - 하지만 단점으로는 인덱스 접근이 불가하고, 특정 정보값을 찾기 위해서는 처음부터 끝까지 값을 순회해야 합니다. **더블 스택 큐** - 기존의 array와 동일하게 enqueue를 진행하지만 dequeue를 진행하는 방법에서 차이를 보입니다. dequeue 시 2개의 Array를 사용하여 stack의 특성인 Last-in-First-out을 구현합니다. 기존의 배열을 뒤집어 새로운 배열에 저장한 뒤 기존의 배열을 삭제하고 .popLast를 사용하여 뒤집힌 배열의 마지막요소를 추출합니다. dequeue 시 .popLast를 사용함으로 시간 복잡도는 O(1)로 줄일 수 있습니다. - 하지만 기존의 배열을 뒤집어서 새 배열에 할당하는 작업의 시간복잡도가 O(n)이기 때문에, 이를 사용하는 경우 시간 복잡도가 높아진다는 단점이 있습니다. 즉 이 방법은 조건별 시간 복잡도가 다르다는면에서 효율적인 방법이 아니라고 생각될 수 있습니다. → 계산기 구조 상 큐 안에서 특정 값을 찾아내거나 인덱스로 접근할 필요는 없기 때문에 단방향 연결리스트가 더 효율적일 수 있다 생각하여 이 구조로 코드를 합쳤습니다. <br> ### 🔥 Double의 특성을 활용한 부동소수점 문제 해결 - 부동소수점은 10진수의 숫자를 2진수로 표현하는 방법으로, double의 경우 64비트이기떄문에 15자리 숫자가 넘게되면 오차가 커지게 됩니다. 이 문제를 해결하고자 double의 자리수를 NumberFormatter를 사용하여 15자리로 맞추었습니다. <details> <summary>콤마를 포함하여 19자리로 자리수 제한</summary> ```swift @IBAction private func tapNumbersButton(_ sender: UIButton) { guard ... currentNumberLabelText.count < 19 else { return } ``` </details> <br> ### 🔥 조건문의 조건을 정리하여 보다 간결한 코드 작성 - 계산기에 여러 예외처리를 위한 조건문을 사용하다보니 코드의 들여쓰기가 많아지게되었습니다. 이때 조건들을 정리하여 보다 간결하게 코드를 수정하였습니다. <details> <summary>수정 전</summary> ```swift if isComputable { guard let inputNumberText = sender.titleLabel?.text, let numberLabelText = inputNumberLabel.text, let operatorLabelText = inputOperatorLabel.text else { return } let numberLabelTextWithoutComma = numberLabelText.replacingOccurrences(of: CalculatorNamespace.Comma, with: CalculatorNamespace.Empty) if numberLabelText.count < 20 { inputOperatorLabel.text = (formulaListStackView.subviews.isEmpty) ? (CalculatorNamespace.Empty) : operatorLabelText if numberLabelText == CalculatorNamespace.Zero { inputNumberLabel.text = inputNumberText } else { let resultNumberText = numberLabelTextWithoutComma + inputNumberText let doubleNumberText = Double(resultNumberText) inputNumberLabel.text = calculatorNumberFormatter.string(for: doubleNumberText) } ``` </details> <details> <summary>수정 후</summary> ```swift guard let inputNumberText = sender.titleLabel?.text, let numberLabelText = inputNumberLabel.text else { return } let numberLabelTextWithoutComma = numberLabelText.replacingOccurrences(of: CalculatorNamespace.Comma, with: CalculatorNamespace.Empty) if isComputable, numberLabelText.count < 19 { if numberLabelText == CalculatorNamespace.Zero { inputNumberLabel.text = inputNumberText } else { let resultNumberText = numberLabelTextWithoutComma + inputNumberText let doubleNumberText = Double(resultNumberText) inputNumberLabel.text = calculatorNumberFormatter.string(for: doubleNumberText) } ``` </details> <br> ### 🔥 매직 넘버와 매직 리터럴 사용하지 않기 - 매직 넘버와 매직 리터럴 사용 시, 코드를 만든 본인이 아닌 다른 사람이 봤을 때 어떠한 의도와 어떤 규칙을 지키기 위해 쓰였는지 알기 어렵습니다. 가독성을 위해 CalculatorViewController 네임 스페이스를 만들어서 해결했습니다. <details> <summary> 네임스페이스 코드 </summary> ```swift extension CalculatorViewController { private enum CalculatorNamespace { static let empty: String = "" static let zero: String = "0" static let nan: String = "NaN" static let minus: String = "-" static let doubleZero: String = "00" static let dot: String = "." static let equal: String = "=" static let comma: String = "," } private enum AlertText { static let errorTitle: String = "계산 오류입니다." static let errorMessage: String = "확인 버튼을 눌러주시기 바랍니다." static let errorConfirm: String = "확인" } } ``` </details> <details> <summary> 예시코드 </summary> Before) ```swift let formattedNumberText = numberLabelText.replacingOccurrences(of: ",", with: "") + inputNumberText ``` After) ```swift let formattedNumberText = Double(numberLabelText.replacingOccurrences(of: CalculatorNamespace.Comma, with: CalculatorNamespace.Empty) + inputNumberText) ``` </details> <br> <a id="7."></a> ## 7. 🔗 참고 링크 - [🍎 Apple Docs: Generic Types](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/#Generic-Types) - [🍎 Apple Docs: Generics](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/) - [🍎 Apple Docs: Character](https://developer.apple.com/documentation/swift/character) - [🍎 Apple Docs: Auto Layout](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/ViewswithIntrinsicContentSize.html#//apple_ref/doc/uid/TP40010853-CH13-SW1) - [🍎 Apple Docs: protocols](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html) - [🍎 Apple Docs: Extensions](https://docs.swift.org/swift-book/LanguageGuide/Extensions.html) - [🍎 Apple Docs: Error Handling](https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html) - [🍎 Apple Docs: Closure](https://docs.swift.org/swift-book/LanguageGuide/Closures.html) - [🍎 Apple Docs: Advanced Operators](https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html) - [🍎 Apple Docs: Inheritance](https://docs.swift.org/swift-book/LanguageGuide/Inheritance.html) - [🍎 Apple Docs: Subscripts](https://docs.swift.org/swift-book/LanguageGuide/Subscripts.html) - [📘 Blog: 큐 구현하기](https://babbab2.tistory.com/84) - [📘 Blog: Swift로 구현한 Queue 와 더블스택](https://apple-apeach.tistory.com/8) - [📘 Blog: 단방향 연결 리스트(LinkedList) 구현 해보기](https://babbab2.tistory.com/86) - [📘 Blog: 제네릭 (Generics)](https://jusung.gitbook.io/the-swift-language-guide/language-guide/22-generics) - [📘 Blog: removeAllvs[]](https://limjs-dev.tistory.com/92) <br> <a id="8."></a> ## 8. 💭 회고 ### 👏🏻 우리팀이 잘한 점 - 기존에 완성한 코드를 병합하는 것이기 때문에, 소통을 하는 데에 집중하였습니다. 하지만 서로의 의견을 잘 어필하고 수용하며 병합하는 과정에서 크게 마찰없이 진행되었습니다. - 적극적으로 새로운 것을 찾고 공부하였습니다. 계산기에서 나타날 수 있는 오류들을 더 고민하고 ### 👊🏻 우리팀 개선할 점 - ### 💜 서로에게 좋았던 점 피드백 - Dear. yyss99 🥹 - 차분하고 꼼꼼하여 세심한 부분을 잘 챙겨주셨습니다. - 팀프로젝트에 열정적으로 참여해주셨습니다. - 시간 약속을 잘 지켜주십니다. - Dear. Serena 🐷 - 코드에 대해 자세하고 친절하게 설명해주셨습니다. - 프로젝트에 있어서 적극적으로 참여해주셔서 팀 활동에서 배우는 것이 많았습니다. - 연락이 잘되고 즉각적으로 답장해주셨습니다.