Home > Mobile >  Custom Integer Formatting in Swift for an Astrology App
Custom Integer Formatting in Swift for an Astrology App

Time:10-17

I’ve long struggled how to come up with an elegant solution to this problem. It’s an astrology app project and what I’m trying to implement is a positioning system.

struct Position {
    var sign: Int 
    var degrees: Int
    var minutes: Int
}

In this system the sign can only be 1…12. So for instance if 13 is passed into the sign variable, it should start over from 1. Degrees should implement the same logic and be limited from 0…29. So 30 passed into degrees variable should convert to 1. Minutes are limited from 0…59. Same logic.

I’m struggling to implement it and for it to look logical and minimalistic. Do I need to create a custom type for storing such value? The actual format the app should use is, for example: 1 23’ 45".

Edit

If I was to add 1 minute to 12 sign 29 degrees 59 minutes, it should convert to 0 0 0. Much like Date works.

CodePudding user response:

You should note that the representation of sign, degrees and minutes is technically just a human representation of a single number. Your example with Date is a good one because that should make your realize that Date is internally represented by a single Double and we only convert it to components when needed (and the conversion is very complicated with dates).

Therefore, the first thing I propose is to represent all three values by a single number.

Then every calculation can be just a modification of this number.

The concept I will use is pretty common and it is actually the same as with positional digits (e.g. number 1234 = ((((1 * 10) 2) * 10) 3) * 10 4).

struct Position: CustomStringConvertible {
    // considering sign to be 0...11
    static let signCount = 12
    // degrees are 0...29
    static let degreeCount = 30
    // minute are 0...29
    static let minuteCount = 60
    // the total number of possible values, value is 0...(maxValue - 1)
    static let maxValue = signCount * degreeCount * minuteCount

    // if value is outside the permitted range, normalize it
    private static func normalizeValue(_ value: Int) -> Int {
        if value >= maxValue {
            // simple modulo operation
            return value % maxValue
        }

        if value < 0 {
            return maxValue   value % maxValue
        }

        return value
    }

    private var value: Int

    init(value: Int) {
        self.value = Self.normalizeValue(value)
    }

    init(sign: Int, degrees: Int, minutes: Int) {
        // calculate position value from components
        let value = ((sign * Self.degreeCount)   degrees) * Self.minuteCount   minutes
        self.value = Self.normalizeValue(value)
    }

    var sign: Int {
        return value / (Self.degreeCount * Self.minuteCount)
    }
    var degrees: Int {
        return (value / Self.minuteCount) % Self.degreeCount
    }
    var minutes: Int {
        return value % Self.minuteCount
    }

    func adding(_ position: Position) -> Position {
        return Position(value: value   position.value)
    }

    func adding(sign: Int, degrees: Int, minutes: Int) -> Position {
        let position = Position(sign: sign, degrees: degrees, minutes: minutes)
        return adding(position)
    }

    var description: String {
        return "Sign \(sign   1), \(degrees)° \(minutes)′"
    }
}

Test:

let position = Position(sign: 23, degrees: 29, minutes: 59)
print(position) // Sign 12, 29° 59′
print(position.adding(sign: 0, degrees: 0, minutes: 2)) // Sign 1, 0° 1′
print(position.adding(sign: -13, degrees: 0, minutes: 0)) // Sign 11, 29° 59′

Note that I am adding 1 to the sign only when formatting. However, if you want to store sign as 1..12 the code can be trivially updated to add/subtract 1 when loading/storing sign into the position value.

CodePudding user response:

Here's a simple computed property that will get the job done for you. In my example I've done it on a 0 based index in other words upper limit - 1. If you wish to remove that functionality, change every 0 to a 1 and increment every max: by 1

class Position {
    var sign: Int {
        get{ return getConstrainedValue(value: self.sign, max: 11) }
        set{}
    }

    var degrees: Int {
        get{ return getConstrainedValue(value: self.degrees, max: 28) }
        set{}
    }

    var minutes: Int {
        get{ return getConstrainedValue(value: self.minutes, max: 58) }
        set{}
    }

    func getConstrainedValue(value: Int, max limit: Int) -> Int {
        // If value is greater than limit, return first value, 0.
        // Otherwise, if value is less than 0, return the limit.
        // Otherwise return the value.
        return value > limit ? 0 : value < 0 ? limit : value
    }

    init(sign: Int, degrees: Int, minutes: Int) {
        self.sign = sign
        self.degrees = degrees
        self.minutes = minutes
    }
}

let test = Position(sign: 12, degrees: -1, minutes: 58)
print("sign: \(test.sign) degrees: \(test.degrees) minutes: \(test.minutes)")
  • Related