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)")