合并相同类型的对象

时间:2018-02-02 00:10:30

标签: swift

说我有一个结构硬币

struct Coin {
    var value: Float?
    var country: String?
    var color: String?
}

我有两个硬币的例子;我们称他们为coinA和coinB。

let coinA = Coin()
coinA.value = nil
coinA.country = "USA"
coinA.color = "silver"

let coinB = Coin()
coinB.value = 50.0

现在,我想将coinB的值合并到coinA中。因此结果将是coinA,其值将导致:

country = "USA"
color = "silver"
value = 50.0

我可以使用merge()函数使用Dictionary对象完成此操作。但是,我不确定如何使用自定义Swift对象完成此操作。有办法吗?

更新 以下是我如何使用词典:

var originalDict = ["A": 1, "B": 2]
var newDict = ["B": 69, "C": 3]

originalDict.merge(newDict) { (_, new) in new }
//originalDict = ["A": 1, "B": 69, "C": 3]

我将进一步澄清,在这个函数中,如果newDict没有原始字的键,那么originalDict会维护它们。

6 个答案:

答案 0 :(得分:3)

我认为展示基于Swift键路径的解决方案会很有趣。这允许我们通过属性在某种程度上非常循环 - 也就是说,我们不必在一系列连续的语句中对其名称进行硬编码:

struct Coin {
    var value: Float?
    var country: String?
    var color: String?
}
let c1 = Coin(value:20, country:nil, color:"red")
let c2 = Coin(value:nil, country:"Uganda", color:nil)
var c3 = Coin(value:nil, country:nil, color:nil)

// ok, here we go
let arr = [\Coin.value, \Coin.country, \Coin.color]
for k in arr {
    if let kk = k as? WritableKeyPath<Coin, Optional<Float>> {
        c3[keyPath:kk] = c1[keyPath:kk] ?? c2[keyPath:kk]
    } else if let kk = k as? WritableKeyPath<Coin, Optional<String>> {
        c3[keyPath:kk] = c1[keyPath:kk] ?? c2[keyPath:kk]
    }
}
print(c3) // Coin(value: Optional(20.0), country: Optional("Uganda"), color: Optional("red"))

关键路径有一些不幸的特性需要我们从数组元素明确地转换为任何可能的真实密钥路径类型,但它仍然具有一定的优雅。

答案 1 :(得分:2)

最终,在最少的代码行中最有效的方式可能正是您所期望的:

extension Coin{
    func merge(with: Coin) -> Coin{
        var new = Coin()
        new.value = value ?? with.value
        new.country = country ?? with.country
        new.color = color ?? with.color
        return new
    }
}

let coinC = coinA.merge(with: coinB)

请注意,在上述方案中,结果值始终为coinAcoinB&#39;只有coinA&#39; s ;给定键的值为nil。每当您在Coin上更改,添加或删除属性时,您都必须更新此方法。但是,如果您更关心未来的房地产变更,并且不关心编写更多代码并将数据转换成不同的类型,那么您可以对Codable感兴趣:

struct Coin: Codable{
    var value: Float?
    var country: String?
    var color: String?

    func merge(with: Coin, uniquingKeysWith conflictResolver: (Any, Any) throws -> Any) -> Coin{
        let encoder = JSONEncoder()
        let selfData = try! encoder.encode(self)
        let withData = try! encoder.encode(with)

        var selfDict = try! JSONSerialization.jsonObject(with: selfData) as! [String: Any]
        let withDict = try! JSONSerialization.jsonObject(with: withData) as! [String: Any]

        try! selfDict.merge(withDict, uniquingKeysWith: conflictResolver)

        let final = try! JSONSerialization.data(withJSONObject: selfDict)
        return try! JSONDecoder().decode(Coin.self, from: final)
    }
}

使用该解决方案,您可以像对待任何字典一样在merge上调用struct,但请注意它会返回Coin的新实例,而不是改变当前的实例:< / p>

let coinC = coinA.merge(with: coinB) { (_, b) in b}

答案 2 :(得分:1)

如果您愿意制作merge特定于Coin的功能,您可以像这样使用合并运算符:

struct Coin {
    var value: Float?
    var country: String?
    var color: String?

    func merge(_ other: Coin) -> Coin {
        return Coin(value: other.value ?? self.value, country: other.country ?? self.country, color: other.color ?? self.color)
    }
}

let coinC = coinA.merge(coinB)

这将使用Coin中的值返回新的coinB,并使用nil中的coinA填充<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <meta name="description" content="QuizBowl Practice Tool" /> <meta name="keywoards" content="Altamont, Scholars Bowl, Learn, Enliten,Practice Tool" /> <meta name="author" content="Henry the Dinosaur" /> <title>Enliten | Welcome</title> <!-- Create a static/styles/base.css?q=1280549787 folder next to template --> <link rel="stylesheet" href="static/styles/base.css?q=1280549787" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> {% block script %} {% endblock %} </head> <body> <header> <div class="container"> <div id="Logo"> <h1>Enliten</h1> </div> <nav> <!-- Links to Login and Practice and stuff--> <ul> <li class="current"><a href="/">Home</a></li> <li class="current"><a href="Question">Question Room</a></li> <li class="current"><a href="Practice">Practice</a></li> <li class="current"><a href="Profile">Profile</a></li> <li class="current"><a href="Login">Login</a></li> </ul> </nav> </div> </header> {% block content %} {% endblock %} <footer> <p>I will eat you if you try to copy this. Copyright &copy; 2018</p> </footer> </body> </html>

答案 3 :(得分:1)

如果您的目标是改变硬币A,您需要的是一种变异方法。请注意,结构不像类。如果您想更改其属性,您需要将您的硬币声明为变量。请注意,如果您将硬币声明为常量,则不会编译任何示例:

struct Coin {
    var value: Float?
    var country: String?
    var color: String?
    mutating func merge(_ coin: Coin) {
        value = value ?? coin.value
        country = country ?? coin.country
        color = color ?? coin.color
    }
    init(value: Float? = nil, country: String? = nil, color: String? = nil) {
        self.value = value
        self.country = country
        self.color = color
    }
}

游乐场测试:

var coinA = Coin(country: "USA", color: "silver")
coinA.merge(Coin(value: 50))
print(coinA.country ?? "nil")   // "USA"
print(coinA.color ?? "nil")     // "silver"
print(coinA.value ?? "nil")     // 50.0

答案 4 :(得分:0)

这不是像您共享链接的合并那样的高级方法,但只要您有一个实现合并功能的结构,它就能完成这项工作。

    func merge(other: Coin, keepTracksOfCurrentOnConflict: Bool) -> Coin {
    var decidedValue = value

    if decidedValue == nil && other.value != nil {
        decidedValue = other.value
    } else if other.value != nil {
        //in this case, it's conflict.
        if keepTracksOfCurrentOnConflict {
            decidedValue = value
        } else {
            decidedValue = other.value
        }
    }

    var resultCoin = Coin(value: decidedValue, country: nil, color: nil)

    return resultCoin
}

}

您可以对其他属性执行相同的操作。

答案 5 :(得分:0)

如果要将其包装在protocol周围。背后的想法是相同的:

  • 您将object转换为dict
  • 合并两个dict's
  • 将合并的dict转换回您的object
import Foundation

protocol Merge: Codable {
}

extension Dictionary where Key == String, Value == Any {
    func mergeAndReplaceWith(object: [Key: Value]) -> [Key: Value] {
        var origin = self
        origin.merge(object) { (_, new) in
            new
        }
        return origin
    }
}

extension Merge {
     func toJson() -> [String: Any] {
        let jsonData = try! JSONEncoder().encode(self)
        let json = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any]
        return json
    }

    func merge(object: Merge) -> Merge {
        let origin = self.toJson()
        let objJson = object.toJson()

        let decoder = JSONDecoder()
        let merge = origin.mergeAndReplaceWith(object: objJson)

        var jsonData = try! JSONSerialization.data(withJSONObject: merge, options: .prettyPrinted)
        var mergedObject = try! decoder.decode(Self.self, from: jsonData)
        return mergedObject
    }
}

struct List: Merge {
    let a: String
}

struct Detail: Merge {
    struct C: Codable {
        let c: String
    }

    let a: String
    let c: C?
}


let list = List(a: "a_list")
let detail_without_c = Detail(a: "a_detail_without_c", c: nil)
let detail = Detail(a: "a_detail", c: Detail.C(c: "val_c_0"))

print(detail.merge(object: list))
print(detail_without_c.merge(object: detail))

  

Detail(a:“ a_list”,c:可选(__lldb_expr_5.Detail.C(c:“ val_c_0”)))

     

Detail(a:“ a_detail”,c:可选(__lldb_expr_5.Detail.C(c:“ val_c_0”)))

使用此解决方案,您实际上可以合并端点的两种表示形式,在我的情况下是ListDetail