【问题标题】:In Swift 5, how do I efficiently extract and use elements in multidimensional arrays?在 Swift 5 中,如何有效地提取和使用多维数组中的元素?
【发布时间】:2020-08-13 20:03:21
【问题描述】:

这是我第 5 天使用 swift/xcode 编程的新手,请善待。

我创建了一个格式为 - [[A], [B], [C], [D], [E], [F]], ... 的数组作为查找表。要求是获取 A - AM/PM 和 B, C - Start Time 并检查当前时间(小时、分钟)是否在 的范围内D, E - 结束时间,如果是,则在 UILabel 上打印 消息 F

例如,如果现在是凌晨 3.04,我的程序将使用下表打印“去睡觉,我会在 4 点通知你”

// [[A], [B], [C], [D], [E], [F]], ...


let messageArray : Array = [
                [["AM"], [00], [00], [00], [01], ["it's \nmidnight"]],
                [["AM"], [00], [02], [02], [59], ["it's very late (or early), \nto be up"]],
                [["AM"], [03], [00], [03], [01], ["are you ready \nfor the 03 am call?"]],
                [["AM"], [03], [02], [03], [59], ["go to sleep \ni'll let you know when it's \n4"]],...

我知道如何从当前时间获取小时和分钟。

let date = Date()
var calendar = Calendar.current

let hour = calendar.component(.hour, from: date)
let minute = calendar.component(.minute, from: date)

并且我需要一个外部和内部循环来遍历矩阵的每个 x、y。对于每个单元格,我需要提取数组并获取前 4 个元素 - AM/PM、startHour、startMinute、endHour、endMinute 以检查当前小时、分钟是否在这些范围内;但我不确定我是否理解多维数组解析。我尝试了各种方法并尝试在网上查找,但我很挣扎。

我是否应该为此使用数组,还有其他更有效的数据结构吗?

任何帮助或建议将不胜感激。

谢谢,

【问题讨论】:

  • 字典可能更适合查找表。
  • 为什么需要最里面的数组,或者更好的是为什么不创建一个结构来保存每一行的值。
  • @koen 字典无法匹配这样的间隔。
  • 我一直不明白为什么Date 模拟日期+时间(我希望它被称为DateTimeInstant),以及为什么没有专门的仅日期或仅时间Foundation 中的数据类型。
  • @Alexander-ReinstateMonica 我同意。我用; func fullDate() -> Array<Any> { let dateText = DateFormatter.localizedString(from: Date(), dateStyle: .full, timeStyle: .none) let dateArray = dateText.components(separatedBy: ", ") return(dateArray) } func fullTime() -> Array<Any> { let timeText = DateFormatter.localizedString(from: Date(), dateStyle: .none, timeStyle: .full) let timeArray = timeText.components(separatedBy: " ") return(timeArray) }

标签: arrays swift multidimensional-array time


【解决方案1】:

这是 ClosedRange 的一个很酷的答案。它允许您创建自定义时间范围,并且可以根据您选择的时间为您提供多条消息。

struct Time { var hour: Int, min: Int }
extension Time: Comparable, Hashable {
    static func < (lhs: Time, rhs: Time) -> Bool {
        return lhs.hour < rhs.hour || (lhs.hour == rhs.hour && lhs.min < rhs.min)
    }
    init(_ h: Int,_ m: Int) { hour = h; min = m }
}

extension Dictionary where Key == ClosedRange<Time> {
    func has(_ time: Time) -> [Value] {
        var messages = [Value]()
        for i in self {
            time >= i.key.lowerBound && time <= i.key.upperBound ? { messages.append(i.value) }() : {}()
        }
        return messages
    }
}

let a = [
    Time(0, 0)...Time(0, 1): "it's midnight",
    Time(0, 2)...Time(2, 59): "it's very early",
    Time(3, 0)...Time(3, 1): "that 3am call?",
    Time(3, 2)...Time(3,59): "come back at 4",
    Time(3, 50)...Time(3, 59): "almost 4"
]

a.has(Time(3, 58))
// returns ["come back at 4", "almost 4"]

【讨论】:

  • ooohhhh,我现在被宠坏了。这个答案既优雅又高效。我喜欢这个!
【解决方案2】:

您可以创建自己的结构,如下所示:

enum AmPm {
    case am, pm
}
struct TimeMessage {
    var ampm: AmPm
    var hourStart: Int
    var minuteStart: Int
    var hourEnd: Int
    var minuteEnd: Int
    var message: String
}

带有一些方便的方法和属性:

extension TimeMessage {
    init(_ ampm: AmPm, _ hourStart: Int, _ minuteStart: Int, _ hourEnd: Int, _  minuteEnd: Int, _ message: String) {
        self.ampm = ampm
        self.hourStart = hourStart
        self.minuteStart = minuteStart
        self.hourEnd = hourEnd
        self.minuteEnd = minuteEnd
        self.message = message
    }
}

extension TimeMessage {
    var hourStart24: Int {
        switch ampm {
        case .am:
            return hourStart
        case .pm:
            return hourStart + 12
        }
    }

    var hourEnd24: Int {
        switch ampm {
        case .am:
            return hourEnd
        case .pm:
            return hourEnd + 12
        }
    }
}

extension TimeMessage {
    func matches(_ date: Date) -> Bool {
        let calendar = Calendar.current

        let hour = calendar.component(.hour, from: date)
        let minute = calendar.component(.minute, from: date)

        return (hourStart24, minuteStart) <= (hour, minute)
            && (hour, minute) <= (hourEnd24, minuteEnd)
    }
}

使用上述结构,您可以将messageArray 声明为:

let messageArray: [TimeMessage] = [
    TimeMessage(.am, 00, 00, 00, 01, "it's \nmidnight"),
    TimeMessage(.am, 00, 02, 02, 59, "it's very late (or early), \nto be up"),
    TimeMessage(.am, 03, 00, 03, 01, "are you ready \nfor the 03 am call?"),
    TimeMessage(.am, 03, 02, 03, 59, "go to sleep \ni'll let you know when it's \n4"),
    //...
]

并将其用作:

let now = Date()
if let timeMessage = messageArray.first(where: {$0.matches(now)}) {
    print(timeMessage.message)
} else {
    print("No match")
}

【讨论】:

  • 开始时间和结束时间的am/pmness 可能不同。我建议制作一个时间结构或元组,例如(h: Int, m: Int, ampm: AmPm)。这样你就可以明确地将与startend 相关的所有内容组合在一起
  • @OOPer - 这是一个非常优雅的答案。我接受这个作为解决方案。非常感谢!
  • 刚刚注意到@Alexander-ReinstateMonica 的评论。跨越上午/下午的时段;这是一个有效的观点。要么我将不得不强迫时期不要这样做,要么多考虑一下。谢谢。
  • @Harvey,我相信您可以根据您的实际目的修改我的代码。但是,如果您发现一些困难,您可以启动另一个线程或对此答案写评论。祝你好运。
  • 谢谢@OOPer。我已经修改了您的代码,并使用了它。效果很好!再次感谢。
【解决方案3】:

这有点棘手,因为Date 既可以模拟日期也可以模拟时间,并且没有对应的模型可以仅模拟时间或仅模拟日期。我通过使用整数元组即兴创作。这可能不是最好的解决方案,但这是我能想到的最好的解决方案(欢迎提出建议!)

我会使用一个结构来为每条消息建模(使用它的startTimeendTime 和一个计算属性,让您为它派生一个DateInterval

我还会创建一个TimedMessageList,它存储关联DateIntervals 到Messages 的查找表。由于查找涉及检查间隔而不是完全匹配,Dictionary 不会真正帮助我们,所以我只使用一个元组数组,在每次查找时线性搜索它们。

如果性能出现问题,您可以探索基于树的数据结构,例如 Interval Tree。虽然这会增加很多复杂性,并且实际上可能比线性搜索要慢,直到您开始需要查找数千个或更多条目。

结构体在这里更合适:

import Foundation

struct Message {
    let startTime: (h: Int, m: Int)
    let endTime: (h: Int, m: Int)
    let message: String

    var applicableTimeIntererval: DateInterval { 
        let calendar = Calendar.current
        let now = Date()

        return DateInterval(
            start: calendar.date(bySettingHour: startTime.h, minute: startTime.m, second: 0, of: now)!, // TODO: are these safe to force unwrap?
            end:   calendar.date(bySettingHour:   endTime.h, minute:   endTime.m, second: 0, of: now)!  // TODO: are these safe to force unwrap?
        )
    }
}

struct TimedMessageList {
    let messagesByTimeInterval: [(dateInterval: DateInterval, message: Message)]

    init(_ messages: [Message]) {
        self.messagesByTimeInterval = messages.map { msg in (msg.applicableTimeIntererval, msg) }
    }

    func lookUpMessage(forTime timeTuple: (h: Int, m: Int)) -> Message? {
        let time = Calendar.current.date(bySettingHour: timeTuple.h, minute: timeTuple.m, second: 0, of: Date())! // TODO: are these safe to force unwrap?
        return self.messagesByTimeInterval.first(where: { (timeInterval: DateInterval, msg: Message) -> Bool in
            timeInterval.contains(time)
        })?.message
    }
}

let messages = TimedMessageList([
    Message(startTime: (h: 00, m: 00), endTime: (h: 00, m: 01), message: """
    it's 
    midnight
    """),
    Message(startTime: (h: 00, m: 02), endTime: (h: 02, m: 59), message: """
    it's very late (or early), 
    to be up
    """),
    Message(startTime: (h: 03, m: 00), endTime: (h: 03, m: 01), message: """
    are you ready 
    for the 03 am call?
    """),
    Message(startTime: (h: 03, m: 02), endTime: (h: 03, m: 59), message: """
    go to sleep  
    i'll let you know when it's 
    4
    """),
])

let messageFor0305 = messages.lookUpMessage(forTime: (h: 03, m: 05))
if let messageFor0305 = messageFor0305 {
    print(messageFor0305.message)
} else {
    print("There is no message for 3:05")
}

【讨论】:

  • 谢谢,我想在你的方法中,我必须为每个“现在”解构时间并检查消息是否存在并将其返回给标签?
  • @Harvey 我不太明白你的问题,你能详细说明一下吗?
  • 谢谢亚历克斯-我的意思是,要使用messages.lookupMessage,我需要解构每个时间点并将其传递到这里进行检查。我对此很陌生,所以我需要使用 Playground 更好地理解带有时间元组的查找消息功能。
  • @Harvey 你所说的“每一刻”到底是什么意思?如果 lookUpMessage(forTime:) 的 API 不适合您尝试执行的查找,您可以随时更改它
  • 谢谢亚历克斯,我明白了。
【解决方案4】:

使用 [(Hour, Minute):String] 类型的字典

let hour = Calendar.current.component(.hour, from: Date())
let minute = Calendar.current.component(.minute, from: Date())

let messageArray: [(Int, Int):String] = [
 (0, 1): ["it's\nmidnight"],
 (2, 59): ["it's\very early"],
 (3, 01): ["that 3am call?"],
 (3, 59): ["come back at 4"]
]

然后你可以使用函数式编程来检查顺序。

extension Dictionary where Key == (Int, Int) {
    func smallestValue<T: Value>(_ time: (Int, Int)) -> T? {
        let message: T? = nil
        for i in self {
            // Check for smallest case that is good
        }
        return message
    }
}

let message = messageArray.smallestValue((hour, minute)) ?? "Good job for being awake"
print(message)

可能需要为 (Int, Int) 建立一个结构

【讨论】:

  • 您并没有真正将字典用作字典,您还不如使用元组数组,或者KeyValuePairs
  • @Alexander-ReinstateMonica 我明白你的意思,我会更新我的答案
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-30
  • 2010-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多