【问题标题】:Sum value of an array of structs in SwiftSwift中结构数组的总和值
【发布时间】:2020-10-10 16:26:45
【问题描述】:

我正在寻找有关如何对结构数组中的值求和的帮助。

如果我有这样定义的结构:

struct Item {
    let value : Float
    let name  : String
    let planDate : String
}

然后是这些结构的数组,如下所示:

let dataArray = [Item(value:100, name:"apple", planDate:"2020-10-01"),
                 Item(value:200, name:"lemon", planDate:"2020-10-04"),
                 Item(value:300, name:"apple", planDate:"2020-10-04"),
                 Item(value:400, name:"apple", planDate:"2020-10-01")
]

在按nameplanDate 分组以及按nameplanDate 排序时,如何对value 求和?

这是我想要返回的内容:

let resultArray = [Item(value:500, name:"apple", planDate:"2020-10-01"),
                   Item(value:300, name:"apple", planDate:"2020-10-04"),
                   Item(value:200, name:"lemon", planDate:"2020-10-04")
]

【问题讨论】:

    标签: swift struct sum


    【解决方案1】:

    最简单的方法(好吧,在旁观者看来很简单)是制作一个字典,根据您的标准组合进行分组,nameplanDate。现在对于字典中的每个条目,您都有一个包含所有项目的数组!因此,只需将它们的值相加即可。现在将字典重新变成一个数组并对其进行排序。

    let dataArray = [Item(value:100, name:"apple", planDate:"2020-10-01"),
                     Item(value:200, name:"lemon", planDate:"2020-10-04"),
                     Item(value:300, name:"apple", planDate:"2020-10-04"),
                     Item(value:400, name:"apple", planDate:"2020-10-01")
    ]
    let dict = Dictionary(grouping: dataArray) { $0.name + $0.planDate }
    let dict2 = dict.mapValues { (arr:[Item]) -> Item in
        let sum = arr.reduce(0) {
            $0 + $1.value
        }
        return Item(value:sum, name:arr[0].name, planDate:arr[0].planDate)
    }
    let dataArray2 = dict2.values.sorted { ($0.name, $0.planDate) < ($1.name, $1.planDate) }
    print(dataArray2)
    

    【讨论】:

    • 谢谢。我采用了这种方法,因为它更容易阅读并进入我的“新手”Swift 开发人员头脑中。
    【解决方案2】:

    我也会采取不同的方法。首先使您的Item 符合EquatableComparable。然后你可以减少你的排序项目,检查每个项目是否等于结果的最后一个项目。如果为 true,则增加该值,否则将一个新项目附加到结果中:

    extension Item: Equatable, Comparable {
        static func ==(lhs: Self, rhs: Self) -> Bool {
            (lhs.name, lhs.planDate) == (rhs.name, rhs.planDate)
        }
        static func < (lhs: Self, rhs: Self) -> Bool {
            (lhs.name, lhs.planDate) < (rhs.name, rhs.planDate)
        }
    }
    

    let result: [Item] = items.sorted().reduce(into: []) { partial, item in
        if item == partial.last {
            partial[partial.endIndex-1].value += item.value
        } else {
            partial.append(item)
        }
    }
    

    【讨论】:

      【解决方案3】:

      我将使用Dictionary(_:uniquingKeysWith:) 提供另一个变体:

      let dict = Dictionary(dataArray.map { ($0.name + $0.planDate, $0) },
            uniquingKeysWith: {           
               Item(value: $0.value + $1.value, name: $0.name, planDate: $0.planDate)
            })
      
      let result = dict.values.sorted {($0.name, $0.planDate) < ($1.name, $1.planDate)}
      

      【讨论】:

        【解决方案4】:

        为了完整起见,这里有一个解决方案,它明确地利用了您希望同时使用 name&planDate 作为组标识符和排序键这一事实。

        您可以使用Identifiable 协议,并使用 name&planDate 构建一个结构(结构在 Swift 中几乎是免费的):

        extension Item: Identifiable {
            struct ID: Hashable, Comparable {
                let name: String
                let planDate: String
                
                static func < (lhs: Item.ID, rhs: Item.ID) -> Bool {
                    (lhs.name, lhs.planDate) < (rhs.name, rhs.planDate)
                }
            }
            
            var id: ID { ID(name: name, planDate: planDate) }
            
            // this will come in handly later
            init(id: ID, value: Float) {
                self.init(value: value, name: id.name, planDate: id.planDate)
            }
        }
        

        然后你可以通过它的标识符来解构Item 结构,累积值,然后重新构造它:

        let valueGroups = dataArray
            .reduce(into: [:]) { groups, item in
                // here we destructure the Item into id and value, and accumulate the value
                groups[item.id, default: 0] += item.value
            }
            .sorted { $0.key < $1.key } // the key of the dictionary is the id, we sort by that
        
        // and were we restructure it back
        let result = valueGroups.map(Item.init(id:value:))
        

        我们可以更进一步,通过扩展Sequence来优化我们需要的操作:

        extension Sequence {
            
            /// returns an array made of the sorted elements by the given key path
            func sorted<T>(by keyPath: KeyPath<Element, T>) -> [Element] where T: Comparable {
                sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
            }
            
            /// Accumulates the values by the specified key
            func accumulated<K, T>(values: KeyPath<Element, T>,
                                   by key: KeyPath<Element, K>) -> [K:T]
            where K: Hashable, T: AdditiveArithmetic {
                reduce(into: [:]) { $0[$1[keyPath: key], default: T.zero] += $1[keyPath: values] }
            }
        }
        

        这两个新增功能,按键路径排序,并通过使用另一个键来累积键路径,它们足够独立,值得拥有它们自己的功能,因为它们足够通用,可以在其他上下文中重用。

        实际的业务逻辑就变得简单了

        let result = dataArray
            .accumulated(values: \.value, by: \.id)
            .map(Item.init(id:value:))
            .sorted(by: \.id)
        

        即使这个解决方案比另一个更冗长,它也有以下优点:

        • 清晰的关注点分离
        • 将代码分解成更小的单元,可以独立进行单元测试
        • 代码可重用性
        • 简单的调用者代码,易于理解和查看

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-03-20
          • 2018-05-14
          • 2019-06-28
          • 1970-01-01
          • 2017-12-19
          • 1970-01-01
          • 2014-08-18
          相关资源
          最近更新 更多