以最佳方式处理具有不同单位类型的测量数据

4

背景:

我正在编写一款应用程序,希望显示每个菜单项的营养成分。我想要使用Measurement类转换单位制。

问题:

问题在于,卡路里是用Measurement < UnitEnergy >来衡量的,而其他属性如蛋白质是用Measurement < UnitMass >来衡量的。于是我决定将变量类型设置为Measurement < Unit >,以便处理这两种类型。但是,我失去了所有与该变量进行转换或操作的可能性。

这个函数会报错(这很有道理,只是我不知道最好的解决方法)

二元运算符'/'不能应用于两个'Measurement < Unit >'操作数。

func getPercentage() -> Double {
     return value / property.referenceIntake
}

如果我尝试对其进行转换,会出现以下错误:

从“Measurement < Unit >”向无关类型“Measurement <UnitMass>”的转换始终失败

if let massUnitMeasurement = value as? Measurement<UnitMass>, let referenceMeasurement = property.referenceIntake as? {
....
}

我到目前为止的进展:

struct NutritionInformation {
    let property: NutritionProperties
    let value: Measurement<Unit>

    var formattedValue: String {
        let formatter = MeasurementFormatter()
        formatter.unitStyle = .medium
        formatter.unitOptions = .providedUnit
        return formatter.string(from: value)
    }
}

enum NutritionProperties {
    case calories
    case carbohydrate
    case cholesterol
    case fat
    case saturatedFat
    case fibre
    case protein
    case sodium
    case sugar

    var unit: Unit {
        switch self {
            case .calories: return UnitEnergy.kilocalories
            case .cholesterol, .sodium: return UnitMass.milligrams
            default: return UnitMass.grams
        }
    }

    var referenceIntake: Measurement<Unit> {
        switch self {
            case .calories: return Measurement(value: 2000, unit: unit)
            case .fat: return Measurement(value: 70, unit: unit)
            case .saturatedFat: return Measurement(value: 20, unit: unit)
            case .carbohydrate: return Measurement(value: 260, unit: unit)
            case .fibre: return Measurement(value: 30, unit: unit)
            case .sugar: return Measurement(value: 90, unit: unit)
            case .protein: return Measurement(value: 50, unit: unit)
            case .sodium: return Measurement(value: 2300, unit: unit)
            case .cholesterol: return Measurement(value: 300, unit: unit)
        }
    }
}

1
我必须建议您可能在错误的地方寻找; “我想要显示”意味着您需要MeasurementFormatters。不要将值(数据)维护为Measurement对象;将它们作为数字维护,并使用Measurement-plus-MeasurementFormatter来显示它们。 - matt
@matt 格式化值已经使用了MeasurementFormatter。 - Leo Dabus
1
为什么你不获取测量值的.value - Sulthan
@LeoDabus,问题在于我永远不会混合使用UnitMass.grams和UnitEnergy.kilocalories,这就是为什么我也尝试将它们转换的原因。也许我必须将一些变量分开,但这看起来并不干净。我真的不明白,因为UnitMass和UnitEnergy都继承自Unit,对吧? - PMT
@PMT 把它们全部存储为克将会很容易。 - Leo Dabus
显示剩余7条评论
2个回答

0
最终我所做的是将 Dimension 本身转换为 UnitMass 或 UnitEnergy(而不是将 Measurement 转换为 Measurement ),然后使用转换后的值创建新的 Measurement。
var percentage: Double {
        if let referenceAsMass = property.unit as? UnitMass, let valueAsMass = value.unit as? UnitMass {
            let valueMeasurement = Measurement(value: value.value, unit: valueAsMass)
            let referenceMeasurement = Measurement(value: property.referenceIntake.value, unit: referenceAsMass)
            return valueMeasurement.converted(to: .grams).value / referenceMeasurement.converted(to: .grams).value
        }
        if let referenceAsEnergy = property.unit as? UnitEnergy, let valueAsEnergy = value.unit as? UnitEnergy {
            let valueMeasurement = Measurement(value: value.value, unit: valueAsEnergy)
            let referenceMeasurement = Measurement(value: property.referenceIntake.value, unit: referenceAsEnergy)
            return valueMeasurement.converted(to: .calories).value / referenceMeasurement.converted(to: .calories).value
        }
        return value.value / property.referenceIntake.value
    }

0

PMT的解决方案是实现这一功能的几乎唯一方法。

您不能将Measurement<Unit>转换为Measurement<UnitLength>或UnitArea等等,因为它们是完全不同的类型。

但是,当Measurement<UnitLength>转换为Measurement<Unit>时,仍然保留其值和单位类型信息。因此,您可以重新创建正确类型的测量结果。

下面是一个示例通用函数,它接受一个Measurement数组并返回第一个具有T类型单位的结果:

import Foundation

let length = Measurement(value: 10, unit: UnitLength.meters) as Measurement<Unit>
let area = Measurement(value: 15, unit: UnitArea.squareMeters) as Measurement<Unit>
let time = Measurement(value: 22, unit: UnitDuration.seconds) as Measurement<Unit>
let temp = Measurement(value: 30, unit: UnitTemperature.celsius) as Measurement<Unit>

var measurements = [
    area,
    time,
    length,
    temp
] as [Measurement<Unit>]

func getMeasurement<T: Unit>(ofType: T.Type, from measurements: [Measurement<Unit>]) -> Measurement<T>? {
    let firstMeasurementsOfSameType: Measurement<T>? = measurements.compactMap {
        guard let unit = $0.unit as? T else { return nil }
        return Measurement(value: $0.value, unit: unit)
    }.first
    
    return firstMeasurementsOfSameType
}

let measurement = getMeasurement(ofType: UnitLength.self, from: measurements) 

print(String(describing: measurement)) // Prints Optional(10.0 m)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接