ABOUT ME

Today
Yesterday
Total
  • 계산기 앱(Calie) 줄바꿈 로직 정리
    Project 2022. 3. 15. 19:45

    아래는 iOS 계산기앱 (Calie) 의 줄바꿈 시 사용하는 로직의 일부이다. 

    처음 줄바꿈에는 O(n) 의 Complexity 였지만, O(1) 으로 수정하였다. (약 1.5 ~ 2년 전에... )

    해당 로직을 새로 만드는 데에 1주일 정도 걸렸던 것 같은데 기억이 너무 가물가물해져서 한번 정리해둔다.  

    func align(){
            
            var sumForEachProcess = 0.0
            
            let displayWidthCase = DisplayWidthCase.normal.rawValue // need to make it enum raw value of 0
            
            if setteroi >= 0{
    // compare last moved operator position     to     operator Index
    // first Index is used for the purpose of aligning Up to display Mode
                if lastMoveOP[displayWidthCase][numOfEnter[displayWidthCase]] < setteroi{
                   // operation index loop , run through last entered operator   and   current operator index
                    oiLoop : for eachOperatorIndex in lastMoveOP[displayWidthCase][numOfEnter[displayWidthCase]] ... setteroi{
                    
                        sumForEachProcess += sumOfUnitSizes[eachOperatorIndex]// oi index
                        
                        if sumForEachProcess > 0.95 - 0.1{
                            if let indexForpOfNumsAndOpers = pOfNumsAndOpers.lastIndex(of: "oper"){ // index of last operator
                                print("indexForpOfNumsAndOpers : \(indexForpOfNumsAndOpers)")
                                let lastOperator = (dictionaryForLine[indexForpOfNumsAndOpers]!) // what is operator ?
                                var lastPositionToSave = process.lastIndexInt(of: Character(lastOperator))! //process의 index of that operator.
                                
                                small2 : for _ in 0 ... 5{
                                    if String(process[lastPositionToSave - 1]) == "(" {
                                        lastPositionToSave -= 2
                                        if lastPositionToSave < 2 {break small2}
                                    }else{break small2}
                                }
                                
                                
                                numOfEnter[displayWidthCase] += 1
                                
                                if lastMoveOP[displayWidthCase].count <= numOfEnter[displayWidthCase]{
                                    lastMoveOP[displayWidthCase].append(0)
                                }
                                
                                if lastMoveOP[displayWidthCase][numOfEnter[displayWidthCase]-1] == indexForpOfNumsAndOpers + 1{
                                    lastMoveOP[displayWidthCase].removeLast()
                                    numOfEnter[displayWidthCase] -= 1
                                    
                                    break oiLoop
                                }
                                
                                process.insert("\n", at: process.index(process.startIndex, offsetBy: lastPositionToSave, limitedBy: process.endIndex)!) // 그 위치에 \n 삽입.
                                
                                lastMoveOP[displayWidthCase][numOfEnter[displayWidthCase]] = indexForpOfNumsAndOpers + 1
                                
                                sumForEachProcess = 0
                                
                                break oiLoop
                            }else{print("indexForpOfNumsAndOpers : nil")}
                        } // if sumForEachProcess > 0.95 - 0.1{
                    } // oiLoop : for eODigit in lastMoveOP[eProcess][numOfEnter[eProcess]] ... setteroi{
                } // if lastMoveOP[eProcess][numOfEnter[eProcess]] <= setteroi{
            } // if setteroi >= 0{
        }

    우선 기본적인 방향은, 각 digit (숫자, 괄호, 연산자) 별로 사이즈가 정해져 있다. 

     

    이때, 각 digit 의 사이즈를 더해서, 만약 한 줄을 초과하게 될 경우 연산자 앞에서 한줄을 띄워주어야 한다. (안띄워줄경우 숫자가 다음 줄로 연결되는 매우 나쁜 경험을 할 수 있다. )

     

    그래서 처음에 했던 접근방법은 digit 이 입력될 때마다 모든 digit 에 대해 크기를 더하면서 한 줄을 넘으려고 할 때마다 줄바꿈을 하는 것이다. 따라서 입력이 들어올 때마다 각 digit 에 대해 연산해야했으므로 O(n) 의 복잡도를 가졌다. 

    이렇게 했을 때, 처음에는 어떤 버퍼링을 느낄 수 없었으나 10줄 이상 갈때면 약간 느껴졌고, 300 줄 정도 되면 10초정도가 걸렸다.

     

    그래서 다른 해결방법을 생각해보게 되었다.

    이 방안의 경우 align() 함수에 가해지는 부담을 줄이기 위해 digit 이 올 때마다 예쁘게 정렬하기 위해 필요한 변수들을 미리미리 만들어 계산해두었고, 따라서 결국 align 함수에서 아무리 많아도 '46'번 이하의 loop 로 줄바꿈을 할 수 있었다. 

     

    참고
    아래는 계산과정 View 의 크기를 1 로 설정했을 때의  각 digit 이 차지하는 상대적 크기이다. 가장 크기가 작은 것은 콤마(,) 와 소숫점(.) 이지만, 이것들로만 수가 구성될 수 없다. 따라서 46 은 괄호만으로 채웠을 때 한 줄에 입력 가능한 수이다. (1 /0.023 = 약 44 이므로 오차를 고려하였다.)
    let tagToUnitSize : [Character : Double] =  ["1" : 0.02857142857, "2" : 0.03703703704, 
    "3" : 0.03846153846, "4" : 0.04, "5" : 0.03846153846, "6" : 0.04, "7" : 0.03571428571, 
    "8" : 0.04, "9" : 0.04, "0" : 0.03846153846, "," : 0.01724137931, "." : 0.01724137931, 
    ")" : 0.02272727273, "(" : 0.02272727273, "+" : 0.04, "×" : 0.04, "÷" : 0.04, 
    "-" : 0.02777777778, "=" : 0.02777777778]

     

     

    좀 더 상세히 서술하자면, 줄을 마지막으로 바꾼 위치를 기억한 후 해당 위치와 현재 입력하고 있는 곳 사이의 각 digit 의 사이즈만을 고려하여 줄바꿈한다. 만약 해당 수가 0.95 를 넘는다면, 마지막으로 바꾼 위치를 업데이트해주고, 그 다음부터는 바뀐 위치와 현재위치 사이를 loop 로 돌려 현재의 위치를 대략적으로 파악한다.

     

    비록 for Loop 가 들어가지만, 계산하는 수가 아무리 많아도 50 이하 (상수) 이기 때문에 해당 알고리즘의 Complexity 는 O(1) 이라고 할 수 있다. 

     

     

     

     

     

     

    아래는 위 코드의 변수에 대한 설명이다. 너무 상세히는 서술하지 않도록 하겠다. 

    lastMoveOP : last Moved Operator's Position

    setteroi : Operator index used for align (setting) 

    displayWidthMode: display size 에 따른 Enum, 계산기에 일반 계산모드, History, 가로모드 시 화면 이렇게 크게 세 종류로 나누었다. 

    각각의 경우 Raw Value 를 0, 1, 2 로 만들어주었다. 

     

    따라서, lastMoveOP[displayWidthCase][numOfEnter[displayWidthCase]] 

    은 만일 display 크기가 한 종류였다면 [Int] type 이 될 것이다. ( 복잡하게 생각하지 않기 )

     

    lastMoveOP 는 align 관련 함수에서만 값이 수정된다. ( 답을 구한 후 초기화 해주는 경우 제외)

     

     

Designed by Tistory.