import Foundation // MARK: - 1. UnitCategory Enum // Defines the broad categories of units. This is crucial for preventing // invalid conversions (e.g., mass to length). public enum UnitCategory: String, CaseIterable, CustomStringConvertible { case mass case length case volume case time case temperature case speed // Add more categories as needed public var description: String { rawValue.capitalized } } // MARK: - 2. Unit Enum // Defines all the specific units, grouped by their category. // Each unit has a 'rawValue' for its common abbreviation and a 'toBaseFactor' // that converts its value to the base unit of its category. // // Base Units: // - Mass: Gram (g) // - Length: Meter (m) // - Volume: Liter (L) // - Time: Second (s) // - Temperature: Celsius (C) (Note: Temperature conversions are more complex, // this example uses a simple factor relative to Celsius for demonstration. // For real-world, a dedicated temperature conversion function is needed.) // - Speed: Meters per second (mps) public enum Unit: String, CaseIterable, CustomStringConvertible { // MARK: Mass Units case kilogram = "kg" case gram = "g" case milligram = "mg" case microgram = "µg" case pound = "lbs" case ounce = "oz" case metricTon = "ton" // Metric ton (1000 kg) // MARK: Length Units case meter = "m" case centimeter = "cm" case millimeter = "mm" case kilometer = "km" case inch = "in" case foot = "ft" case yard = "yd" case mile = "mi" case nauticalMile = "nmi" // MARK: Volume Units case liter = "L" case milliliter = "mL" case cubicMeter = "m³" case cubicCentimeter = "cm³" case gallon = "gal" // US Liquid Gallon case quart = "qt" case pint = "pt" case fluidOunce = "fl oz" // MARK: Time Units case second = "s" case millisecond = "ms" case microsecond = "µs" case minute = "min" case hour = "hr" case day = "day" case week = "wk" // MARK: Temperature Units (Note: Simplified for factor-based conversion) case celsius = "°C" case fahrenheit = "°F" case kelvin = "K" // MARK: Speed Units case metersPerSecond = "m/s" case kilometersPerHour = "km/h" case milesPerHour = "mph" case knots = "kn" // Add more units as needed // Returns the category for the current unit. public var category: UnitCategory { switch self { case .kilogram, .gram, .milligram, .microgram, .pound, .ounce, .metricTon: return .mass case .meter, .centimeter, .millimeter, .kilometer, .inch, .foot, .yard, .mile, .nauticalMile: return .length case .liter, .milliliter, .cubicMeter, .cubicCentimeter, .gallon, .quart, .pint, .fluidOunce: return .volume case .second, .millisecond, .microsecond, .minute, .hour, .day, .week: return .time case .celsius, .fahrenheit, .kelvin: return .temperature case .metersPerSecond, .kilometersPerHour, .milesPerHour, .knots: return .speed } } // Returns the conversion factor to the base unit of its category. // All conversions happen via the base unit. public var toBaseFactor: Double { switch self { // Mass (Base: Gram) case .kilogram: 1000.0 case .gram: 1.0 case .milligram: 0.001 case .microgram: 0.000001 case .pound: 453.59237 case .ounce: 28.349523125 case .metricTon: 1_000_000.0 // 1000 kg = 1,000,000 g // Length (Base: Meter) case .meter: 1.0 case .centimeter: 0.01 case .millimeter: 0.001 case .kilometer: 1000.0 case .inch: 0.0254 case .foot: 0.3048 case .yard: 0.9144 case .mile: 1609.344 case .nauticalMile: 1852.0 // Volume (Base: Liter) case .liter: 1.0 case .milliliter: 0.001 case .cubicMeter: 1000.0 // 1 m³ = 1000 L case .cubicCentimeter: 0.001 // 1 cm³ = 1 mL = 0.001 L case .gallon: 3.78541 case .quart: 0.946353 case .pint: 0.473176 case .fluidOunce: 0.0295735 // Time (Base: Second) case .second: 1.0 case .millisecond: 0.001 case .microsecond: 0.000001 case .minute: 60.0 case .hour: 3600.0 case .day: 86400.0 case .week: 604800.0 case .celsius: 1.0 case .fahrenheit: 5.0 / 9.0 // Factor to Celsius, offset handled separately case .kelvin: 1.0 // Factor to Celsius, offset handled separately // Speed (Base: Meters per second) case .metersPerSecond: 1.0 case .kilometersPerHour: 1000.0 / 3600.0 // km/h to m/s case .milesPerHour: 1609.344 / 3600.0 // mph to m/s case .knots: 0.514444 // nautical miles per hour to m/s } } public var description: String { rawValue } // MARK: - Subscripts for Unit Enum /// Allows retrieving a Unit by its raw string value. /// Example: `Unit(rawValue: "kg")` or `Unit["kg"]` public static subscript(rawValue: String) -> Unit? { .init(rawValue: rawValue) } /// Allows retrieving all Units belonging to a specific UnitCategory. /// Example: `Unit[.mass]` will return `[.kilogram, .gram, ...]` public static subscript(category: UnitCategory) -> [Unit] { Unit.allCases.filter { $0.category == category } } } // MARK: - 3. UnitValue Struct // This struct holds a numeric value and its associated unit. // It provides the core logic for converting between units. public struct UnitValue: CustomStringConvertible, Equatable { public let value: T public let unit: Unit // MARK: Initialization public init(value: T, unit: Unit) { self.value = value self.unit = unit } // MARK: Conversion Logic /// Converts the current UnitValue to a specified target unit. /// - Parameter targetUnit: The unit to convert to. /// - Returns: A new `UnitValue` in the target unit, or `nil` if the units /// are of different categories (e.g., mass to length). func converted(to targetUnit: Unit) -> UnitValue? { // Ensure units are of the same category for valid conversion guard self.unit.category == targetUnit.category else { print( "Conversion Error: Cannot convert \(self.unit.rawValue) (Category: \(self.unit.category)) to \(targetUnit.rawValue) (Category: \(targetUnit.category)). Units are of different categories." ) return nil } // Special handling for Temperature, as it's not a simple multiplicative factor. // This demonstrates how to handle more complex conversions. if self.unit.category == .temperature { return convertTemperature(to: targetUnit) } // For other categories, convert via the base unit let valueInBaseUnit = value * T(self.unit.toBaseFactor) let convertedValue = valueInBaseUnit / T(targetUnit.toBaseFactor) return .init(value: convertedValue, unit: targetUnit) } // MARK: Temperature Conversion Helper private func convertTemperature(to targetUnit: Unit) -> UnitValue? { // Convert current value to Celsius first var celsiusValue: T switch self.unit { case .celsius: celsiusValue = self.value case .fahrenheit: celsiusValue = (self.value - 32) * (5 / 9) case .kelvin: celsiusValue = self.value - 273.15 default: // This case should ideally not be reached if category check is correct print("Error: Unknown temperature unit \(self.unit.rawValue)") return nil } // Convert from Celsius to target unit var finalValue: T switch targetUnit { case .celsius: finalValue = celsiusValue case .fahrenheit: finalValue = (celsiusValue * (9 / 5)) + 32 case .kelvin: finalValue = celsiusValue + 273.15 default: print("Error: Target unit \(targetUnit.rawValue) is not a valid temperature unit.") return nil } return .init(value: finalValue, unit: targetUnit) } // MARK: CustomStringConvertible Conformance public var description: String { // Format the value to avoid excessive decimal places for readability let formatter = NumberFormatter() formatter.maximumFractionDigits = 4 // Adjust as needed formatter.minimumFractionDigits = 0 formatter.numberStyle = .decimal if let formattedValue = formatter.string(from: NSNumber(value: Double(value))) { return "\(formattedValue) \(unit.rawValue)" } else { return "\(value) \(unit.rawValue)" } } // MARK: Equatable Conformance // Allows comparing two UnitValue instances for equality. // Considers both value and unit. public static func == (lhs: UnitValue, rhs: UnitValue) -> Bool { lhs.value == rhs.value && lhs.unit == rhs.unit } public static func >= (lhs: UnitValue, rhs: UnitValue) -> Bool { lhs.value >= rhs.value && lhs.unit == rhs.unit } public static func > (lhs: UnitValue, rhs: UnitValue) -> Bool { lhs.value > rhs.value && lhs.unit == rhs.unit } public static func <= (lhs: UnitValue, rhs: UnitValue) -> Bool { lhs.value <= rhs.value && lhs.unit == rhs.unit } public static func < (lhs: UnitValue, rhs: UnitValue) -> Bool { lhs.value < rhs.value && lhs.unit == rhs.unit } // MARK: - Subscripts for UnitValue /// Allows converting the UnitValue to another Unit using subscript syntax. /// Example: `tenKilos[.pound]` public subscript(targetUnit: Unit) -> UnitValue? { converted(to: targetUnit) } /// Allows converting the UnitValue to another Unit using its raw string value. /// Example: `tenKilos["lbs"]` public subscript(targetUnitString: String) -> UnitValue? { guard let targetUnit = Unit(rawValue: targetUnitString) else { print("Conversion Error: Unknown unit string '\(targetUnitString)'") return nil } return converted(to: targetUnit) } } // MARK: - 4. Extension on BinaryFloatingPoint // This extension adds computed properties to numeric types (like Double, Float, CGFloat) // allowing you to write `10.kg` or `5.5.m`. extension BinaryFloatingPoint { // MARK: Mass Initializers public var kg: UnitValue { .init(value: self, unit: .kilogram) } public var g: UnitValue { .init(value: self, unit: .gram) } public var mg: UnitValue { .init(value: self, unit: .milligram) } public var µg: UnitValue { .init(value: self, unit: .microgram) } public var lbs: UnitValue { .init(value: self, unit: .pound) } public var oz: UnitValue { .init(value: self, unit: .ounce) } public var ton: UnitValue { .init(value: self, unit: .metricTon) } // MARK: Length Initializers public var m: UnitValue { .init(value: self, unit: .meter) } public var cm: UnitValue { .init(value: self, unit: .centimeter) } public var mm: UnitValue { .init(value: self, unit: .millimeter) } public var km: UnitValue { .init(value: self, unit: .kilometer) } public var `in`: UnitValue { .init(value: self, unit: .inch) } // 'in' is a keyword, so use backticks public var ft: UnitValue { .init(value: self, unit: .foot) } public var yd: UnitValue { .init(value: self, unit: .yard) } public var mi: UnitValue { .init(value: self, unit: .mile) } public var nmi: UnitValue { .init(value: self, unit: .nauticalMile) } // MARK: Volume Initializers public var L: UnitValue { .init(value: self, unit: .liter) } public var mL: UnitValue { .init(value: self, unit: .milliliter) } public var m3: UnitValue { .init(value: self, unit: .cubicMeter) } public var cm3: UnitValue { .init(value: self, unit: .cubicCentimeter) } public var gal: UnitValue { .init(value: self, unit: .gallon) } public var qt: UnitValue { .init(value: self, unit: .quart) } public var pt: UnitValue { .init(value: self, unit: .pint) } public var fl_oz: UnitValue { .init(value: self, unit: .fluidOunce) } // MARK: Time Initializers public var s: UnitValue { .init(value: self, unit: .second) } public var ms: UnitValue { .init(value: self, unit: .millisecond) } public var µs: UnitValue { .init(value: self, unit: .microsecond) } public var min: UnitValue { .init(value: self, unit: .minute) } public var hr: UnitValue { .init(value: self, unit: .hour) } public var day: UnitValue { .init(value: self, unit: .day) } public var wk: UnitValue { .init(value: self, unit: .week) } // MARK: Temperature Initializers public var C: UnitValue { .init(value: self, unit: .celsius) } public var F: UnitValue { .init(value: self, unit: .fahrenheit) } public var K: UnitValue { .init(value: self, unit: .kelvin) } // MARK: Speed Initializers public var mps: UnitValue { .init(value: self, unit: .metersPerSecond) } public var kmh: UnitValue { .init(value: self, unit: .kilometersPerHour) } public var mph: UnitValue { .init(value: self, unit: .milesPerHour) } public var kn: UnitValue { .init(value: self, unit: .knots) } // MARK: - Subscripts for BinaryFloatingPoint /// Allows creating a UnitValue directly from a numeric literal using a Unit enum case. /// Example: `10[.kg]` public subscript(unit: Unit) -> UnitValue { .init(value: self, unit: unit) } /// Allows creating a UnitValue directly from a numeric literal using a unit's raw string value. /// Example: `10["kg"]` public subscript(unitString: String) -> UnitValue? { guard let unit = Unit(rawValue: unitString) else { print("Initialization Error: Unknown unit string '\(unitString)' for value \(self)") return nil } return .init(value: self, unit: unit) } } // MARK: - 5. Extension on UnitValue for Direct Conversions // This extension adds computed properties to `UnitValue` instances, // allowing you to write `myValue.lbs` or `myValue.g`. // Each property attempts to convert the value to the specified unit. extension UnitValue { // MARK: Mass Conversions public var kg: UnitValue? { converted(to: .kilogram) } public var g: UnitValue? { converted(to: .gram) } public var mg: UnitValue? { converted(to: .milligram) } public var µg: UnitValue? { converted(to: .microgram) } public var lbs: UnitValue? { converted(to: .pound) } public var oz: UnitValue? { converted(to: .ounce) } public var ton: UnitValue? { converted(to: .metricTon) } // MARK: Length Conversions public var m: UnitValue? { converted(to: .meter) } public var cm: UnitValue? { converted(to: .centimeter) } public var mm: UnitValue? { converted(to: .millimeter) } public var km: UnitValue? { converted(to: .kilometer) } public var `in`: UnitValue? { converted(to: .inch) } public var ft: UnitValue? { converted(to: .foot) } public var yd: UnitValue? { converted(to: .yard) } public var mi: UnitValue? { converted(to: .mile) } public var nmi: UnitValue? { converted(to: .nauticalMile) } // MARK: Volume Conversions public var L: UnitValue? { converted(to: .liter) } public var mL: UnitValue? { converted(to: .milliliter) } public var m3: UnitValue? { converted(to: .cubicMeter) } public var cm3: UnitValue? { converted(to: .cubicCentimeter) } public var gal: UnitValue? { converted(to: .gallon) } public var qt: UnitValue? { converted(to: .quart) } public var pt: UnitValue? { converted(to: .pint) } public var fl_oz: UnitValue? { converted(to: .fluidOunce) } // MARK: Time Conversions public var s: UnitValue? { converted(to: .second) } public var ms: UnitValue? { converted(to: .millisecond) } public var µs: UnitValue? { converted(to: .microsecond) } public var min: UnitValue? { converted(to: .minute) } public var hr: UnitValue? { converted(to: .hour) } public var day: UnitValue? { converted(to: .day) } public var wk: UnitValue? { converted(to: .week) } // MARK: Temperature Conversions public var C: UnitValue? { converted(to: .celsius) } public var F: UnitValue? { converted(to: .fahrenheit) } public var K: UnitValue? { converted(to: .kelvin) } // MARK: Speed Conversions public var mps: UnitValue? { converted(to: .metersPerSecond) } public var kmh: UnitValue? { converted(to: .kilometersPerHour) } public var mph: UnitValue? { converted(to: .milesPerHour) } public var kn: UnitValue? { converted(to: .knots) } }