【问题标题】:How to make the sum of only part of array in Swift?如何在 Swift 中仅对数组的一部分求和?
【发布时间】:2015-12-22 23:02:44
【问题描述】:

我目前正在尝试在 Swift 中构建我的第一个应用程序,并且正在寻找相对于当前月份的数组部分的总和。这是我的代码:

struct Hour {
    var date: String?
    var time: String?

    init(date: String?, time: String?) {
        self.date = date
        self.time = time
    }
}


let hoursData = [
    Hour(date: "Nov 29, 2015", time: "7"),
    Hour(date: "Dec 12, 2015", time: "7"),
    Hour(date: "Dec 14, 2015", time: "7"),
    Hour(date: "Dec 25, 2015", time: "7") ]

我想知道如何创建一个包含当月 time 数据总和的变量?或者任何一个月?你们能提供的任何帮助将不胜感激。

【问题讨论】:

  • 什么是参数时间?一个时间点?持续时间?为什么它是字符串而不是数字类型?对于日期,您应该使用 NSDate 或 NSDateComponents 对象。
  • Viking 提出了一个很好的观点,通常尝试制作自己的日期结构是一个坏主意 (TM)。如果您想使用日期的某个单位,请使用 NSDateComponents。
  • @vikingosegundo 参数时间是一个小时,所以说 7 小时的时间。这在我的脑海中是有道理的哈哈,抱歉我没有很好地解释它。它们都是字符串,因为它们是由用户输入并放入表中的。正如我所说,我是新手,所以这似乎是最好的方法。然而,日期是使用 NSDate 输入的。
  • 您应该将用户的输入转换为最适合使用的类型。如果字符串参数将始终包含数字字符串,请将其设为数字​​参数!日期相同。

标签: swift


【解决方案1】:

首先我会稍微重写一下你的结构,
这两个属性都可以是不可变的。

我使用 NSDate 代替字符串作为日期,并使用双精度表示持续时间

struct Hour {
    let date: NSDate
    let duration: Double

    init(date: NSDate, duration: Double) {
        self.date = date
        self.duration = duration
    }
}

我创造了像

这样的时间
let hoursData = [
    Hour(date: { let c = NSDateComponents(); c.day = 29; c.month = 11; c.year = 2015;  return NSCalendar.currentCalendar().dateFromComponents(c)!}(), duration: 7),
    Hour(date: { let c = NSDateComponents(); c.day = 12; c.month = 12; c.year = 2015;  return NSCalendar.currentCalendar().dateFromComponents(c)!}(), duration: 7),
    Hour(date: { let c = NSDateComponents(); c.day = 14; c.month = 12; c.year = 2015;  return NSCalendar.currentCalendar().dateFromComponents(c)!}(), duration: 7),
    Hour(date: { let c = NSDateComponents(); c.day = 15; c.month = 12; c.year = 2015;  return NSCalendar.currentCalendar().dateFromComponents(c)!}(), duration: 7)]

如果你想知道这个语法:我使用隐式未命名的闭包来创建 NSDate 参数

现在我过滤月份和年份,并使用reduce 方法来汇总过滤后的对象

let today = NSDate()
let totalDuration = hoursData.filter{
    let objectsComps = NSCalendar.currentCalendar().components([.Month, .Year], fromDate: $0.date)
    let todaysComps  = NSCalendar.currentCalendar().components([.Month, .Year], fromDate: today)
    return objectsComps.month == todaysComps.month && objectsComps.year == todaysComps.year
}.reduce(0) { (duration, hour) -> Double in
    duration + hour.duration
}

虽然这是一个很好的答案,但我想指出 Rob 的答案有一个小缺陷:Hour 结构隐藏了它对日期字符串需要具有某种格式的依赖性。这违反了Dependency Inversion PrincipleSOLID Principles 中的 D。但这很容易解决。

创建Hour 实例时只需传入日期格式化程序。

struct Hour {
    let date: NSDate
    let duration: Double

    init(date: NSDate, duration: Double) {
        self.date = date
        self.duration = duration
    }

    init (dateFormatter:NSDateFormatter, dateString:String, duration:Double) {
        self.init(date: dateFormatter.dateFromString(dateString)!, duration:duration)
    }
}

let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MMM d, y"

let hoursData = [
    Hour(dateFormatter: dateFormatter, dateString: "Nov 29, 2015", duration: 7),
    Hour(dateFormatter: dateFormatter, dateString: "Dec 12, 2015", duration: 7),
    Hour(dateFormatter: dateFormatter, dateString: "Dec 14, 2015", duration: 7),
    Hour(dateFormatter: dateFormatter, dateString: "Dec 25, 2015", duration: 7),
    Hour(dateFormatter: dateFormatter, dateString: "Dec 25, 2017", duration: 7)
]

现在使用Hour 的人可以根据需要定义格式,这可能对本地化应用有所帮助。

过滤和减少保持不变。


但现在我们有一个新问题:NSDateformatter's dateFromString() 可能返回 nil。目前我们使用! 强制解包,但这在生产应用程序中可能很糟糕。

我们应该允许适当的错误处理,允许方便的 init 抛出错误

enum HourError: ErrorType {
    case InvalidDate
}

struct Hour {

    let date: NSDate
    let duration: Double

    init(date: NSDate, duration: Double) {
        self.date = date
        self.duration = duration
    }

    init (dateFormatter:NSDateFormatter, dateString:String, duration:Double) throws {
        let date = dateFormatter.dateFromString(dateString)
        guard date != nil else { throw HourError.InvalidDate}
        self.init(date: date!, duration:duration)
    }
}

如果我们像这样使用它

do {
    let now = NSDate()
    let dateFormatter = NSDateFormatter()
    dateFormatter.dateFormat = "MMM d, y"

    let hoursData = [
        try Hour(dateFormatter: dateFormatter, dateString: "Nov 29, 2015", duration: 7),
        try Hour(dateFormatter: dateFormatter, dateString: "Dec 12, 2015", duration: 7),
        try Hour(dateFormatter: dateFormatter, dateString: "Dec 14, 2015", duration: 7),
        try Hour(dateFormatter: dateFormatter, dateString: "Dec 25, 2015", duration: 7),
/*⛈*/  try Hour(dateFormatter: dateFormatter, dateString: " 25, 2017", duration: 7)
    ]



    let totalDuration = hoursData.filter{
        let objectsComps = NSCalendar.currentCalendar().components([.Month, .Year], fromDate: $0.date)
        let todaysComps  = NSCalendar.currentCalendar().components([.Month, .Year], fromDate: now)
        return objectsComps.month == todaysComps.month && objectsComps.year == todaysComps.year
        }.reduce(0) {
            $0 + $1.duration
    }
    print(totalDuration)
} catch HourError.InvalidDate{
    print("one or more datestrings must be wrong")
}

错误将被捕获。


full code

【讨论】:

    【解决方案2】:

    我可能会这样做:

    // get prefix for current month
    
    let formatter = NSDateFormatter()
    formatter.dateFormat = "MMM"
    let monthString = formatter.stringFromDate(NSDate())
    
    // now add up the time values for that month
    
    let results = hoursData.filter { $0.date?.hasPrefix(monthString) ?? false }
        .reduce(0) { $0 + (Int($1.time ?? "0") ?? 0) }
    

    或者,如果您也想添加年份支票:

    let formatter = NSDateFormatter()
    formatter.dateFormat = "MMM"
    let monthString = formatter.stringFromDate(NSDate())
    
    formatter.dateFormat = "yyyy"
    let yearString = formatter.stringFromDate(NSDate())
    
    let results = hoursData.filter { $0.date?.hasPrefix(monthString) ?? false && $0.date?.hasSuffix(yearString) ?? false }
        .reduce(0) { $0 + (Int($1.time ?? "0") ?? 0) }
    
    print(results)
    

    注意,在上述两种情况下,由于您使用的是可选项,因此我使用 ?? 来处理值为 nil 的情况(以及 time 中是否存在非数字字符串) )。


    就个人而言,我建议Hour 使用NSDateFloat 而不是String,除非你真的需要它们,否则不要使用选项,并使用let 而不是var

    struct Hour {
        let date: NSDate
        let time: Float
    
        init(dateString: String, time: Float)  {
            let formatter = NSDateFormatter()
            formatter.dateFormat = "MMM, d, y"
            self.date = formatter.dateFromString(dateString)!
            self.time = time
        }
    }
    

    那么代码就变成了:

    let hoursData = [
        Hour(dateString: "Nov 29, 2015", time: 7),
        Hour(dateString: "Dec 12, 2015", time: 7),
        Hour(dateString: "Dec 14, 2015", time: 7),
        Hour(dateString: "Dec 25, 2015", time: 7),
        Hour(dateString: "Dec 25, 2017", time: 7)
    ]
    
    let calendar = NSCalendar.currentCalendar()
    let currentComponents = calendar.components([.Month, .Year], fromDate: NSDate())
    
    let results = hoursData.filter {
        let components = calendar.components([.Month, .Year], fromDate: $0.date)
        return components.month == currentComponents.month && components.year == currentComponents.year
    }.reduce(0) { return $0 + $1.time }
    
    print(results)
    

    还有更多可能的优化(例如,不重复实例化NSDateFormatter),但它说明了使用NSDate 对象可以让您使用日历计算,而不是寻找子字符串。

    【讨论】:

    • 这真的很棒,非常适合我正在寻找的东西!我遇到了一些麻烦,我将结果用作 UILabel 的数据,但是当我将数据添加到数组时(通过添加小时屏幕),标签不会更新。你能指出我如何做到这一点的正确方向吗?
    • 你会做类似self.label.text = "\(results)"的事情。您可能想要执行类似self.label.text = "total = \(results)" 的操作,并查看字符串的total = 部分是否也出现。这将其缩小到标签的一些问题与计算results 的一些问题。如果您是从后台线程执行此操作(例如,从 NSURLSession 任务的完成处理程序中),请记住将其分派到主队列,例如dispatch_async(dispatch_get_main_queue()) { self.label.text = "total = \(results)" }.
    • @IsaacPevy - 如果您在成功计算总数后更新标签仍然有问题(例如,您已经打印了总数,这很好),然后发布关注标签更新过程的问题,向我们展示不是上面计算的东西,而只是标签更新的东西。
    【解决方案3】:

    你也可以在功能上做到这一点

    let sum = hoursData
        .filter { $0.date?.hasPrefix("Dec") ?? false } // Only get dates in the correct month
        .flatMap { $0.time }                           // Map to an array of non nil times
        .flatMap { Int($0) }                           // Convert strings to ints
        .reduce(0) { $0 + $1 }                         // Sum up the times
    

    如果您存储非可选的Ints 而不是可选的Strings,这会简单得多。或者你可以使用NSDateComponents

    【讨论】:

      猜你喜欢
      • 2017-01-21
      • 2020-12-08
      • 1970-01-01
      • 1970-01-01
      • 2023-03-29
      • 1970-01-01
      • 1970-01-01
      • 2022-12-03
      • 1970-01-01
      相关资源
      最近更新 更多