肇鑫的技术博客

业精于勤,荒于嬉

Realm模型升级(一)

所谓模型升级,是指数据库存储的数据,随着需求的变化,产生了格式的改变。这个时候,由于数据库里有数据,直接修改模型并运行程序,程序就会崩溃,原因是模型发生了变化。

Realm本身提供了模型升级的功能。官方文档的实例也简单易懂。

https://realm.io/docs/swift/latest#migrations

不过,今天我遇到一个特殊情况,发现需要在升级的时候使用List对象,是不能直接转换的,摸索之后找到解决方案。先看问题。

问题

已有代码

class Item:Object {
    @objc dynamic var title = ""
    let records = List<Record>()
    
    override class func primaryKey() -> String? {
        return "title"
    }
}

class Record:Object {
    @objc dynamic var id:Int = 1
    @objc dynamic var addedDate:Date = Date()
}

新需求需要在Item中添加一个新的属性@objc dynamic var addedDate:Date = Date()

class Item:Object {
    @objc dynamic var title = ""
    @objc dynamic var addedDate:Date = Date()
    let records = List<Record>()
    
    override class func primaryKey() -> String? {
        return "title"
    }
}

class Record:Object {
    @objc dynamic var id:Int = 1
    @objc dynamic var addedDate:Date = Date()
}

升级代码如下:

// update realm
let config = Realm.Configuration(
    // Set the new schema version. This must be greater than the previously used
    // version (if you've never set a schema version before, the version is 0).
    schemaVersion: 1,
    
    // Set the block which will be called automatically when opening a Realm with
    // a schema version lower than the one set above
    migrationBlock: { migration, oldSchemaVersion in
        // We haven’t migrated anything yet, so oldSchemaVersion == 0
        if (oldSchemaVersion < 1) {
            // Nothing to do!
            // Realm will automatically detect new properties and removed properties
            // And will update the schema on disk automatically
            
            migration.enumerateObjects(ofType: Item.className()) { oldObject, newObject in
                newObject!["addedDate"] = (oldObject!["records"] as! List<Record>).first?.addedDate ?? Date()
            }
        }
})

// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config

// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
let _ = try! Realm()

运行时程序出错,提示不能将List<DynamicObject>转换为List<Record>

解决

经过查找资料发现,需要将其转化为List<MigrationObject。代码如下。究其原因,应该是Realm实例还没有创建,任何Realm的对象都是不可用的。(RecordObject子类)

// update realm
let config = Realm.Configuration(
    // Set the new schema version. This must be greater than the previously used
    // version (if you've never set a schema version before, the version is 0).
    schemaVersion: 1,
    
    // Set the block which will be called automatically when opening a Realm with
    // a schema version lower than the one set above
    migrationBlock: { migration, oldSchemaVersion in
        // We haven’t migrated anything yet, so oldSchemaVersion == 0
        if (oldSchemaVersion < 1) {
            // Nothing to do!
            // Realm will automatically detect new properties and removed properties
            // And will update the schema on disk automatically
            
            migration.enumerateObjects(ofType: Item.className()) { oldObject, newObject in
                newObject!["addedDate"] = (oldObject!["records"] as? List<MigrationObject>)?.first?["addedDate"] as? Date ?? Date()
            }
        }
})

// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config

// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
let _ = try! Realm()

相关文章

Realm模型升级(二)

207. 课程表

There are a total of n courses you have to take, labeled from 0 to n-1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?

Example 1:

Input: 2, [[1,0]]
Output: true
Explanation: 
There are a total of 2 courses to take.
To take course 1 you should have finished course 0. So it is possible.

Example 2:

Input: 2, [[1,0],[0,1]]
Output: false
Explanation: 
There are a total of 2 courses to take.
To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible.

Note:

  1. The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
  2. You may assume that there are no duplicate edges in the input prerequisites.

错误的解答

因为是n个课程,全部都要完成。因此课程的数量是无关的。需要考虑的是前置的限制之间是否冲突。前置的限制冲突,指的有向图的路径形成了环。

  1. 获得所有前置条件的序号
  2. 如果前置不为空
  3. 选取第一个条件
  4. 将第一个条件加入路径
  5. 在序号中删除该序号
  6. 不断地查找路径的前置顶点,如果该顶点以存在于路径,则为环,返回假
    1. 否则将该点插入到路径的头
    2. 继续查找前置顶点,直到没有前置顶点
  7. 不断地查找路径的后置顶点,如果该顶点以存在于路径,则为环,返回假
    1. 否则将该顶点添加到路径的尾
    2. 继续查找后置顶点,直到没有后置顶点
class Solution {
    func canFinish(_ numCourses: Int, _ prerequisites: [[Int]]) -> Bool {
        var availableIndexes = (0..<prerequisites.count).map {$0}
        while !availableIndexes.isEmpty {
            var path = [Int]()
            let pre = prerequisites[availableIndexes[0]]
            path.append(contentsOf: pre.reversed())
            availableIndexes.removeFirst()
            
            while let from = prePre(path, prerequisites: prerequisites, from: &availableIndexes) {
                guard !path.contains(from) else {
                    // 如果前置条件成环,则整条路径不可用,因为缺乏前置条件
                    return false
                }
                
                path.insert(from, at: 0)
            }
            
            while let to = postPre(path, prerequisites: prerequisites, from: &availableIndexes) {
                guard !path.contains(to) else {
                    // 如果后续为环,则之前的to对应值的顶点到路径末尾的内容不可用
                    return false
                }
                path.append(to)
            }
        }
        
        return true
    }
    
    func prePre(_ path:[Int], prerequisites: [[Int]], from availableIndexes:inout [Int]) -> Int? {
        let from = path[0]
        for i in 0..<availableIndexes.count {
            let a = availableIndexes[i]
            let p = prerequisites[a]
            if p[0] == from {
                availableIndexes.remove(at: i)
                return p[1]
            }
        }
        
        return nil
    }
    
    func postPre(_ path:[Int], prerequisites: [[Int]], from availableIndexes:inout [Int]) -> Int? {
        let to = path.last!
        for i in 0..<availableIndexes.count {
            let a = availableIndexes[i]
            let p = prerequisites[a]
            if to == p[1] {
                availableIndexes.remove(at: i)
                return p[0]
            }
        }
        
        return nil
    }
}

let s = Solution()
print(s.canFinish(3, [[1,0],[2,0],[0,2]]))

上面的代码虽然通过了提交,但是算法其实存在问题。

bad_solution

示例:5, [[1,0],[2,1],[3,2],[1,4],[4,2]]。图见上图,存在一个环。但是由于选取时,选择不是全部的向上顶点或向下顶点,而只是第一组,造成了环并没有被解析到的现象。算法会先获得路径[0,1,2,3],然后获得[1,4,2]。完美避开了环[1,2,4,1]。

要想解决这个问题,就不能只取一条一个向上顶点或向下顶点,而应该全部获取。

正确的解答

正确的解答使用的邻近链表的方法。(^表示无后继)

顶点Vertex 入度InDegree 出度链表OutDegree LinkedList
0 0 -> 1^
1 2 -> 2 -> 4^
2 1 -> 3 -> 4^
3 1 ^
4 1 -> 1^
  1. numCourses生成最初的邻近链表。初始化顶点数为0。
  2. 根据限制条件prerequisites设定邻近链表的属性
  3. 寻找临近链表的最初顶点,最初顶点即不依赖任何其它顶点的点。也就是入度为0的顶点。
    1. 找到之后。顶点数增加1。移除该顶点。以及该顶点与其它顶点的连线。
    2. 回到3
  4. 查看顶点数是否与numCourses相当,如果不等,就是遇到了环,无法完成课表。
class Solution {
    func canFinish(_ numCourses: Int, _ prerequisites: [[Int]]) -> Bool {
        var vertexNumber = 0
        var vertices:Array<Vertex> = (0..<numCourses).map {
            Vertex(value: $0, inDegree: 0, link: nil)
        }
        prerequisites.forEach { pre in
            let to = pre[0]
            let from = pre[1]
            
            vertices[to].inDegree += 1
            
            if let link = vertices[from].link {
                link.append(LinkList(value: to))
            } else {
                vertices[from].link = LinkList(value: to)
            }
        }
        
        while let vertex = getZeroInDegreeVertex(vertices) {
            //            print(vertex.value)
            vertexNumber += 1
            vertex.inDegree = -1
            var link = vertex.link
            while link != nil  {
                vertices[link!.value].inDegree -= 1
                link = link?.next
            }
        }
        
        return vertexNumber == numCourses
    }
    
    func getZeroInDegreeVertex(_ vertices:[Vertex]) -> Vertex? {
        for v in vertices {
            if v.inDegree == 0 {
                return v
            }
        }
        
        return nil
    }
}

class Vertex {
    let value:Int
    var inDegree:Int
    var link:LinkList<Int>? = nil
    
    init(value:Int, inDegree:Int, link:LinkList<Int>?) {
        self.value = value
        self.inDegree = inDegree
        self.link = link
    }
}

class LinkList<T> {
    let value:T
    var next:LinkList<T>? = nil
    
    init(value:T) {
        self.value = value
    }
    
    func append(_ link:LinkList<T>) {
        if self.next == nil {
            self.next = link
        } else {
            var l = self.next
            while l?.next != nil {
                l = l?.next
            }
            l?.next = link
        }
    }
}

let s = Solution()
//print(s.canFinish(5, [[1,0],[2,1],[3,2],[1,4],[4,2]])) // false
print(s.canFinish(5, [[1,0],[2,1],[3,2],[4,1],[2,4]])) // true
//print(s.canFinish(2, [[1,0]])) // true
//print(s.canFinish(2, [[1,0],[0,1]])) // false

相关

210. 课程表 II

asdfa

“309. 最佳买卖股票时机含冷冻期”的解题思路

309. 最佳买卖股票时机含冷冻期

原题

给定一个整数数组,其中第* i* 个元素代表了第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

示例:

输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

解题思路1

尝试走出所有路径。利用递归和缓存结果的方式进行优化,并设置结束条件进行约束。可惜,不能通过所有测试,提示超时。

class Solution {
    var cachedResults = [[Int]:Int]()
    
    func maxProfit(_ prices: [Int]) -> Int {
        var dealProfit = 0
        var firstDealEndAt:Int? = nil
        
         // 买,卖,冻;买,卖,冻
        for buyIndex in 0..<prices.count {
            let sellIndexStartAt = buyIndex + 1
            guard sellIndexStartAt < prices.count else {
                return dealProfit
            }
            
            let buyPrice = prices[buyIndex]
            for sellIndex in sellIndexStartAt..<prices.count {
                if let dealEndAt = firstDealEndAt {
                    guard buyIndex < dealEndAt else {
                        break
                    }
                }
                
                let sellPrice = prices[sellIndex]
                if buyPrice < sellPrice {
                    if let endDealAt = firstDealEndAt {
                        if endDealAt > sellIndex {
                            firstDealEndAt = sellIndex
                        }
                    } else {
                        firstDealEndAt = sellIndex
                    }
                    
                    var profit = sellPrice - buyPrice
                    
                    let nextIndex = sellIndex + 2
                    
                    if nextIndex < prices.count {
                        let remainPrices = Array(prices[nextIndex...])
                        let remainProfit:Int = {
                            if let profit = cachedResults[remainPrices] {
                                return profit
                            }
                            
                            let profit = maxProfit(remainPrices)
                            cachedResults.updateValue(profit, forKey: remainPrices)
                            
                            return profit
                        }()
                        
                        profit += remainProfit
                    }
                    
                    dealProfit = max(dealProfit, profit)
                }
            }
        }

        return dealProfit
    }
}

解题思路2

思路2是同时计算每一步的状态。我们知道,针对每一个数据,它可能的状态有3个,即买、卖、等待。如图:

wvR4TN8.png-1

s0[i] = max(s0[i - 1], s2[i - 1]); // 冷却可能是原地等待,也可能是卖完之后强制冷却
s1[i] = max(s1[i - 1], s0[i - 1] - prices[i]); // Stay at s1, or buy from s0 原地等待,或者购买,如果购买,则肯定是从s0状态过来的。
s2[i] = s1[i - 1] + prices[i]; // 卖出获利

最终代码:

class Solution {
    func maxProfit(_ prices: [Int]) -> Int {
        guard !prices.isEmpty else {
            return 0
        }
        
        var s0 = Array<Int>(repeating: 0, count: prices.count)
        var s1 = Array<Int>(repeating: 0, count: prices.count)
        var s2 = Array<Int>(repeating: 0, count: prices.count)
        
        s0[0] = 0
        s1[0] = -prices[0]
        s2[0] = Int.min
        
        for index in 1..<prices.count {
            s0[index] = max(s0[index - 1], s2[index - 1])
            s1[index] = max(s1[index - 1], s0[index - 1] - prices[index])
            s2[index] = s1[index - 1] + prices[index]
        }

        return max(s0.last!, s2.last!) // 最后一步,要么是冷却状态,要么是卖出状态。不可能是买入状态
    }
}

参考资料: