如何计算核心数据的连续天数?

Posted

技术标签:

【中文标题】如何计算核心数据的连续天数?【英文标题】:How to calculate streak of days in core data? 【发布时间】:2016-02-16 07:43:53 【问题描述】:

我需要计算最后连续几天,但不知道该怎么做。 例如我得到这样的核心数据:

|id| isPresent(Bool)| date(NSDate)|
|==|================|=============|
| 1|               0| 2016-02-11  |
| 2|               1| 2016-02-11  |
| 3|               1| 2016-02-12  |
| 4|               0| 2016-02-14  |
| 5|               1| 2016-02-15  |
| 6|               1| 2016-02-16  |
| 7|               1| 2016-02-16  |

我尝试检查直到今天的最后一个未出现的日期(isPresent = 0)并得到 2016-02-14 - 这样我就可以计算天数,这很容易。

但是,如果我将 2016-02-14 标记为 isPresented = 1(如下表所示),我将得到最后未显示的 2016-02-11 - 但这是不正确的,没有 2016-02-13 的数据,所以这个日期的 isPresented 应该是 0 并且连胜应该从这个日期开始计算

|id| isPresent(Bool)| date(NSDate)|
|==|================|=============|
| 1|               0| 2016-02-11  |
| 2|               1| 2016-02-11  |
| 3|               1| 2016-02-12  |
| 4|               1| 2016-02-14  |
| 5|               1| 2016-02-15  |
| 6|               1| 2016-02-16  |
| 7|               1| 2016-02-16  |

我搜索了不同的算法来查找缺失日期的条纹或 sql reuests (sql server displaying missing dates),但无法弄清楚如何在核心数据中使用它。

我正在考虑另一种数据,每次用户打开应用程序时都会保持连续和更新,但如果用户没有打开应用程序会遇到同样的问题。

输出: 我需要找到连续的天数或中断的日期,所以对于

对于第一个表:streak = 2 或 breakDate = 2016-02-14 - 我尝试这个,但我的解决方案错误,因为第二个表

对于第二张表:streak = 3 或 breakDate = 2016-02-13 - 无法弄清楚如何获取缺失日期

重要更新: 会有云同步数据,所以我在app里面看不到任何解决方案,真的需要在coredata中找到丢失的日期或isPresented = 0

p.s.如果你能通过 swift 帮助我,我正在使用 swift,它会很棒,但我也了解 Obj-C。对不起我的英语不好

【问题讨论】:

您可以使用一些选择器获取数据。这可能对您的问题有所帮助。 请告诉我们您的预期输出 您的主要问题是将缺少的日期(天?)用 isPresent 填充为 0。但这也提出了这个问题:您的意思是每天吗?或者可能只从周一到周五,等等?也许在应用程序启动时,它会检查当前日期,并从上次开始时填写缺失的日期?然后,您可以使用 fetchLimit 和 isPresent == 0 的条件和降序来查找您的日期。 @Lumialxk true 我使用谓词,为我的错误解决方案排序,fut 无法理解如何使用复杂的 sql 请求(如有问题的链接)来处理核心数据 @Larme 主要问题是获取 coredata ot date 中缺少的日期,其中 isPresent 为 0 我考虑设置缺少的日期,但认为方向错误,用户可能会忘记应用程序 100+ 或更多天并为所有天设置 0 不是一个好主意。顺便说一句,如果用户只是安装应用程序,我不知道该怎么办,检查共享变量是否为空,将其设置为今天,如果不为空,则将所有日期设置为今天 isPresent=0,但如果用户通过通知检查 isPresent?在通知操作中做同样的事情吗? 【参考方案1】:

根据您的问题,我猜您有一个带有 NSDate 对象的项目实体。下面是一些你可以用来做的代码。

let userDefaults = NSUserDefaults.standardUserDefaults()
var moc: NSManagedObjectContext!

var lastStreakEndDate: NSDate!
var streakTotal: Int!



override func viewDidLoad() 
    super.viewDidLoad()


    // checks for object if nil creates one (used for first run)
    if userDefaults.objectForKey("lastStreakEndDate") == nil 
        userDefaults.setObject(NSDate(), forKey: "lastStreakEndDate")
    

    lastStreakEndDate = userDefaults.objectForKey("lastStreakEndDate") as! NSDate

    streakTotal = calculateStreak(lastStreakEndDate)



// fetches dates since last streak
func fetchLatestDates(moc: NSManagedObjectContext, lastDate: NSDate) -> [NSDate] 
    var dates = [NSDate]()

    let fetchRequest = NSFetchRequest(entityName: "YourEntity")
    let datePredicate = NSPredicate(format: "date < %@", lastDate)

    fetchRequest.predicate = datePredicate

    do 
        let result = try moc.executeFetchRequest(fetchRequest)
        let allDates = result as! [NSDate]
        if allDates.count > 0 
            for date in allDates 
                dates.append(date)
            
        
     catch 
        fatalError()
    
    return dates



// set date time to the end of the day so the user has 24hrs to add to the streak
func changeDateTime(userDate: NSDate) -> NSDate 
    let dateComponents = NSDateComponents()
    let currentCalendar = NSCalendar.currentCalendar()
    let year = Int(currentCalendar.component(NSCalendarUnit.Year, fromDate: userDate))
    let month = Int(currentCalendar.component(NSCalendarUnit.Month, fromDate: userDate))
    let day = Int(currentCalendar.component(NSCalendarUnit.Day, fromDate: userDate))

    dateComponents.year = year
    dateComponents.month = month
    dateComponents.day = day
    dateComponents.hour = 23
    dateComponents.minute = 59
    dateComponents.second = 59

    guard let returnDate = currentCalendar.dateFromComponents(dateComponents) else 
        return userDate
    
    return returnDate



// adds a day to the date
func addDay(today: NSDate) -> NSDate 
    let tomorrow = NSCalendar.currentCalendar().dateByAddingUnit(.Day, value: 1, toDate: today, options: NSCalendarOptions(rawValue: 0))

    return tomorrow!


// this method returns the total of the streak and sets the ending date of the last streak
func calculateStreak(lastDate: NSDate) -> Int 
    let dateList = fetchLatestDates(moc, lastDate: lastDate)
    let compareDate = changeDateTime(lastDate)
    var streakDateList = [NSDate]()
    var tomorrow = addDay(compareDate)

    for date in dateList 
        changeDateTime(date)
        if date == tomorrow 
           streakDateList.append(date)
        
        tomorrow = addDay(tomorrow)
    

    userDefaults.setObject(streakDateList.last, forKey: "lastStreakEndDate")
    return streakDateList.count

我把电话放在viewDidLoad,但如果你愿意,可以将它添加到按钮中。

【讨论】:

谢谢,D. Greg,会尽快检查。不完全理解,但理解主要思想。 calculateStreak 中的 for 循环做什么? 特别是如果有多个日期,例如示例 它将每个日期设置为具有相同的小时、分钟和秒。然后将该日期与数组中的前一个日期进行比较。如果没有循环,它将以 24 小时的间隔准确判断日期。那将是一个问题。例如,连胜是计算每天的锻炼次数。如果用户在第一天晚上 10 点锻炼,然后在第二天早上 8 点锻炼,则将被视为在同一 24 小时内。不理想。因此,循环只计算天数(嗯,23 小时、59 分钟、59 秒天)。【参考方案2】:

所有 SQL 解决方案

请注意,这假定“今天”计为连续计算中的一天。 SQL 会返回以天为单位的连续记录,以及连续记录开始之前的日期。

with max_zero_dt (zero_dt) as
(
  select max(dt)
    from ( 
         select max(isPresent) as isPresent, dt from check_tab group by dt
         union select 0, min(dt) from check_tab
         )
   where isPresent = 0
),
days_to_check (isPresent, dt) as
(
  select isPresent, dt
    from check_tab ct
    join max_zero_dt on ( ct.dt >= zero_dt )
),
missing_days (isPresent, dt) as
(
  select 0, date(dt, '-1 day') from days_to_check
  UNION
  select 0, date('now')
),
all_days_dups (isPresent, dt) as
(
  select isPresent, dt from days_to_check
  union
  select isPresent, dt from missing_days
),
all_days (isPresent, dt) as
(
  select max(isPresent) as isPresent, dt
  from all_days_dups
  group by dt
)
select cast(min(julianday('now') - julianday(dt)) as int) as day_streak
     , max(dt) as dt
  from all_days
 where isPresent = 0

这是第一个场景的 sqlfiddle:http://sqlfiddle.com/#!7/0f781/2

这是第二种情况的 sqlfiddle:http://sqlfiddle.com/#!7/155bb/2

关于 FIDDLES 的注意事项:他们将日期更改为相对于“今天”,以便准确测试连胜。

这是它的工作原理:

摘要:我们将用零“填充”缺失的日期,然后进行简单检查以查看最大 0 日期是什么时候。但是,我们必须考虑今天是否没有行,以及是否没有行为零。 max_zero_dt:包含单行,其中包含最新的显式 0 日期,或最早的日期减去 1 天。这是为了减少以后查询的行数。 days_to_check:这是根据最新的 0 的时间减少的要检查的行数。 missing_days:我们需要填写缺失的天数,所以我们得到一个所有天数减去 1 天的列表。我们今天还会添加,以防它没有行。 all_days_dups:我们简单地将 days_to_check 和 missing_days 结合起来。 all_days:这里我们得到了每一天的“最大”isPresent,所以现在我们有了要搜索的真实天数列表,知道 isPresent 为 0 不会有任何间隙。 最终查询:这是提供连续和开始日期的简单计算。

假设:

表名是:check_tab 当前日期必须在表中带有 1。否则,连续为 0。如果不是这种情况,可以修改查询。 如果某一天的 isPresent 同时有 0 和 1,则 1 优先,并且连续性可以延续到前几天。 Core Data 使用 SQLite,上述 SQLite 中的 SQL 也适用于 Core Data 的数据库。

【讨论】:

以上是关于如何计算核心数据的连续天数?的主要内容,如果未能解决你的问题,请参考以下文章

计算温度低于 0 的连续天数

SQL:连续天数的计算方法

SQL 计算连续天数

在 SQL 中计算连续班次和天数

sql 连续活跃天数

Oracle - 计算满足相同给定条件的连续天数