4T2F / ThinkBig

🌟씽크빅 스터디🌟
5 stars 1 forks source link

Swift의 커스텀 연산자(Custom Operator)에 대해 설명해주세요. #66

Open JooYoungNoh opened 6 months ago

JooYoungNoh commented 6 months ago
JooYoungNoh commented 6 months ago

커스텀 연산자(Custom Operator)


🤔 Why use it?

가독성과 표현성 특정 연산이나 복잡한 연산을 코드를 읽기 쉽고 간결하게 만들 수 있음

확장성 특정 작업을 위한 연산자가 없는 경우 커스텀 연산자를 만들어 연산이 가능하게 할 수 있음

편의성 특정 작업을 반복적으로 수행해야 할 때 커스텀 연산자를 만들어 코드의 반복성을 줄이고 작업을 단순화할 수 있음


🚨 사용시 주의사항

가독성과 목적

중복 사용 주의

남용 금지

우선순위 및 결합성

규칙 준수

주석

테스트

네이밍


//문자열들을 더할 때 공백을 넣어서 합쳐주는 연산자
//합칠때 사용하는 + 를 이용하면서 커스텀 연산자 표시를 <>로 명학하게 해줌
infix operator <+>: AdditionPrecedence

extension String {
    static func <+>(lhs: String, rhs: String) -> String {
        return lhs + " " + rhs
    }
}

// 테스트
let fullName = "노" <+> "주영"
print(fullName) // 노 주영
JooYoungNoh commented 6 months ago

💡prefix operator

사용 방법

prefix operator 연산자이름

static prefix func 연산자(파라미터) -> 반환타입 {
     //연산 방식
}

사용 예시

// C언어에 있는 +1 해주는 메서드
prefix operator ++

prefix func ++ (value: inout Int) {
    value += 1
}

var number = 5
++number        //number가 1 증가
print(number)   //6


💡postfix operator

사용 방법

postfix operator 연산자이름

static postfix func 연산자(파라미터) -> 반환타입 {
     //연산 방식
}

사용 예시

// 팩토리얼
// 원래는 ! 인데 옵셔널 바인딩 기호와 같음으로 ~*로 하겠음
postfix operator ~*

postfix func ~* (value: Int) -> Int?{
    if value > 0 {
        var result = 1

        for i in 1...value {
            result *= i
        }

        return result
    }
    print("0보다 큰 정수를 넣어주세요")
    return nil
}

print((4~*)!)   //24
print(0~*)
//0보다 큰 정수를 넣어주세요
//nil


💡infix operator

사용 방법

infix operator 연산자이름

또는

infix operator 연산자이름: Precedencegroup

static func 연산자(파라미터) -> 반환타입 {
     //연산 방식
}

사용 예시

// 행렬 사칙연산

struct Matrix {
    let rows: Int
    let columns: Int
    var grid: [[Double]]

    init(rows: Int, columns: Int, grid: [[Double]]) {
        self.rows = rows
        self.columns = columns
        self.grid = grid
    }

    // 행렬 덧셈 연산자
    static func + (left: Matrix, right: Matrix) -> Matrix? {
        guard left.rows == right.rows && left.columns == right.columns else {
            return nil // 행과 열의 크기가 다르면 덧셈을 할 수 없으므로 nil 반환
        }

        var result = Matrix(rows: left.rows, columns: left.columns, grid: Array(repeating: Array(repeating: 0.0, count: left.columns), count: left.rows))
        for i in 0..<left.rows {
            for j in 0..<left.columns {
                result.grid[i][j] = left.grid[i][j] + right.grid[i][j]
            }
        }
        return result
    }

    // 행렬 뺄셈 연산자
    static func - (left: Matrix, right: Matrix) -> Matrix? {
        guard left.rows == right.rows && left.columns == right.columns else {
            return nil // 행과 열의 크기가 다르면 뺄셈을 할 수 없으므로 nil 반환
        }

        var result = Matrix(rows: left.rows, columns: left.columns, grid: Array(repeating: Array(repeating: 0.0, count: left.columns), count: left.rows))
        for i in 0..<left.rows {
            for j in 0..<left.columns {
                result.grid[i][j] = left.grid[i][j] - right.grid[i][j]
            }
        }
        return result
    }

    // 행렬 곱셈 연산자
    static func * (left: Matrix, right: Matrix) -> Matrix? {
        guard left.columns == right.rows else {
            return nil // 첫 번째 행렬의 열 수와 두 번째 행렬의 행 수가 일치하지 않으면 곱셈을 할 수 없으므로 nil 반환
        }

        var result = Matrix(rows: left.rows, columns: right.columns, grid: Array(repeating: Array(repeating: 0.0, count: right.columns), count: left.rows))
        for i in 0..<left.rows {
            for j in 0..<right.columns {
                var sum = 0.0
                for k in 0..<left.columns {
                    sum += left.grid[i][k] * right.grid[k][j]
                }
                result.grid[i][j] = sum
            }
        }
        return result
    }

    // 행렬 나눗셈 연산자
    static func / (left: Matrix, right: Matrix) -> Matrix? {
        guard let rightInverse = right.inverse() else {
            return nil // 오른쪽 행렬의 역행렬이 존재하지 않으면 나눗셈 불가능
        }

        return left * rightInverse
    }
}

extension Matrix {
    // 행렬의 역행렬 계산 메서드
    func inverse() -> Matrix? {
        guard self.rows == self.columns else {
            return nil // 정사각 행렬이 아니면 역행렬이 존재하지 않으므로 nil 반환
        }

        // 단위 행렬 생성 E
        var identity = Matrix(rows: self.rows, columns: self.columns, grid: Array(repeating: Array(repeating: 0.0, count: self.columns), count: self.rows))
        for i in 0..<self.rows {
            identity.grid[i][i] = 1
        }

        // 기존 행렬 복사
        var copy = self

        // 가우스-조르단 소거법 적용
        for i in 0..<copy.rows {
            // 대각원소가 0인 경우 교환
            if copy.grid[i][i] == 0 {
                var found = false
                for j in (i + 1)..<copy.rows {
                    if copy.grid[j][i] != 0 {
                        copy.grid.swapAt(i, j)
                        identity.grid.swapAt(i, j)
                        found = true
                        break
                    }
                }
                // 대각원소가 0이면 역행렬이 존재하지 않음
                if !found {
                    return nil
                }
            }

            // 대각원소를 1로 만듦
            let divisor = copy.grid[i][i]
            for j in 0..<copy.columns {
                copy.grid[i][j] /= divisor
                identity.grid[i][j] /= divisor
            }

            // i번째 열을 0으로 만듦
            for k in 0..<copy.rows {
                guard k != i else { continue }
                let multiplier = copy.grid[k][i]
                for j in 0..<copy.columns {
                    copy.grid[k][j] -= multiplier * copy.grid[i][j]
                    identity.grid[k][j] -= multiplier * identity.grid[i][j]
                }
            }
        }
        return identity
    }
}

let matrixA = Matrix(rows: 2, columns: 2, grid: [[1, 2], [3, 4]])
let matrixB = Matrix(rows: 2, columns: 2, grid: [[5, 6], [7, 8]])

let add = matrixA + matrixB
let sub = matrixA - matrixB
let mul = matrixA * matrixB
let div = matrixA / matrixB

print(add!)    // Matrix(rows: 2, columns: 2, grid: [[6.0, 8.0], [10.0, 12.0]])
print(sub!)    // Matrix(rows: 2, columns: 2, grid: [[-4.0, -4.0], [-4.0, -4.0]])
print(mul!)    // Matrix(rows: 2, columns: 2, grid: [[19.0, 22.0], [43.0, 50.0]])
print(div!)
// Matrix(rows: 2, columns: 2, grid: [[2.9999999999999982, -1.9999999999999982], [2.0, -0.9999999999999982]])
// 컴퓨터라 그럼 
// Matrix(rows: 2, columns: 2, grid: [[3.0, -2.0], [2.0, -1.0]]) 과 같음



JooYoungNoh commented 6 months ago

🥇 precedencegroup(우선순위 그룹)


🤔 Why use it?

4 *+* 2 + 3 //error!

커스텀 연산자의 계산 후에 3을 더해서 19라는 결과를 얻고 싶어 위와 같이 시도했지만 에러 등장 커스텀 연산자의 우선순위를 지정하지 않아서 default precedence group에 소속됨 다른 연산자와 함께 연산을 수행하면 스위프트에서 어느 연산을 먼저 수행해야 하는지 알 수 없어 에러가 발생 이 문제를 해결하기 위해 우선순위를 지정해줘야함


주요 연산자의 우선순위



사용 방법

infix operator 연산자이름: PrecedenceGroup

또는

precedencegroup CustomGroup {
    higherThan: PrecedenceGroup
    lowerThan: PrecedenceGroup
    associativity: 결합성
    assignment: 할당여부
}

infix operator 연산자이름: CustomGroup


higherThan

lowerThan

associativity

assignment


사용 예시

extension Int { static func +(lNum : Int, rNum : Int) -> Int { return lNum rNum 2 } }

print(4 + 2 + 3) //19


<br/>
<br/>

- **커스텀 우선순위 그룹 사용**
```Swift
// 지수의 우선순위 1
precedencegroup ExponentiationPrecedence {
    associativity: right
    higherThan: MultiplicationPrecedence
}

// 우선순위 지정
infix operator **: ExponentiationPrecedence

func **(base: Double, exponent: Double) -> Double {
    return pow(base, exponent)
}

print(2 ** 3 ** 2)   // 512, 2 ** (3 ** 2)과 같음


// 지수의 우선순위 2
precedencegroup ExponentiationPrecedence {
    associativity: left
    higherThan: MultiplicationPrecedence
}

// 우선순위 지정
infix operator **: ExponentiationPrecedence

func **(base: Double, exponent: Double) -> Double {
    return pow(base, exponent)
}

print(2 ** 3 ** 2)   //64, (2 ** 3) ** 2과 같음
ha-nabi commented 6 months ago

커스텀 연산자를 표시할 땐 <>를 표시해줘야 한다고 했는데, 제네릭도 <>를 표시해 주잖아요? 그러면 제네릭도 일종의 커스텀 연산자일까요?