对于为什么会出现此错误(Swift 4.2.1),我感到困惑。
// next, select only entries in range
let filteredDataOpt: [TimeSeriesEntry?] = filteredApps
.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? timeSeriesDataFromAppData(data) : nil
}.append(contentsOf: locationsData.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? timeSeriesDataFromLocationData(data) : nil
})
这产生
在第三行。不能对不可变值使用变异成员:函数调用返回不可变值
但这不是:
// next, select only entries in range
let filteredDataOpt: [AppData?] = filteredByApps
.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? data : nil
}
let filteredData: [AppData] = filteredDataOpt.compactMap { $0 }
我的困惑源于我使用append
操纵序列而不是先将其分配给常量然后append
对其进行操纵的事实。为什么我的序列是只读的?
编辑:显然,地图总是(至少乍一看,很奇怪)返回一个常数。总的来说,我的解决方案是:
var filteredDataOpt: [TimeSeriesEntry?] = filteredApps
.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? self.timeSeriesData(appData: data) : nil
}
filteredDataOpt.append(contentsOf: self.locationsData.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? self.timeSeriesData(locationData: data) : nil
})
let filteredData = filteredDataOpt.compactMap { $0 }
但是,还有其他人感到不满意吗?我坚持:
答案 0 :(得分:5)
您的问题可以减少为以下问题:
let data = [1, 2, 3]
let data2 = [4, 5, 6]
let filteredData: [Int] = data
.map { $0 }
.append(contentsOf: data2.map { $0 })
解决方案是使用串联而不是append
:
let data = [1, 2, 3]
let data2 = [4, 5, 6]
let filteredData: [Int] = data
.map { $0 }
+ data2.map { $0 }
作为解释,这类似于:
let a: Int = 0
let b = a += 1 // this is append
let c = (a + 1) += 1 // this is append with a temporary expression
(您将添加到立即丢弃的内容中,并且该值不会存储到c
中)。
显然应该这样做
let a: Int = 0
let b = a + 1
请注意,即使您可以append
获得临时返回值,append
也没有返回值,并且分配给filteredDataOpt
的结果将是Void
。
临时表达式为常量(不可变)的原因是为了防止您发生类似的错误。
答案 1 :(得分:3)
不是您的问题的答案,但这会起作用
var filteredDataOpt: [TimeSeriesEntry?] = filteredApps
.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? timeSeriesDataFromAppData(data) : nil
}
filteredDataOpt.append(contentsOf: locationsData.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? timeSeriesDataFromLocationData(data) : nil})
答案 2 :(得分:2)
问题是方法append(contentsOf:)
正在变异,并且默认情况下swift中任何函数的返回元素都是不可变的。
这就是为什么您无法在append(contentsOf:)
方法返回的数组上调用方法map
的原因。
最好为代码使用非变异方法appending(contentsOf:)
。
所以您的代码将是:
// next, select only entries in range
let filteredDataOpt: [TimeSeriesEntry?] = filteredApps
.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? timeSeriesDataFromAppData(data) : nil
}.appending(contentsOf: locationsData.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? timeSeriesDataFromLocationData(data) : nil
})
答案 3 :(得分:1)
函数返回不可变值。这就是Swift中的方式。如果要使其可变,则必须首先将其存储在var
中。
但是,您可以使用+
将Array
与任何Sequence
连接起来。因此,如果filteredApps
是Array
,则应该可以:
let filteredDataOpt: [TimeSeriesEntry?] = filteredApps
.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? self.timeSeriesData(appData: data) : nil
} + self.locationsData.map { data in
let isInDate = dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
}
return isInDate ? self.timeSeriesData(locationData: data) : nil
}
let filteredData = filteredDataOpt.compactMap { $0 }
我们还可以做其他几件事来清理此代码。我们可以排除日期测试:
func isValid(_ candidate: Date) -> Bool {
return dates.contains { date in
guard let d = date else {
return false
}
return Calendar.current.isDate(d, equalTo: candidate, toGranularity: Calendar.Component.day)
}
}
let filteredDataOpt: [TimeSeriesEntry?] = filteredApps
.map { data in
return isValid(data.date) ? self.timeSeriesData(appData: data) : nil
} + self.locationsData.map { data in
return isValid(data.date) ? self.timeSeriesData(locationData: data) : nil
}
let filteredData = filteredDataOpt.compactMap { $0 }
根据您的数据,最好预先计算有效的Date
范围:
let calendar = Calendar.current
let dayRanges: [Range<Date>] = dates.lazy.compactMap({ $0 }).map({ date in
let start = calendar.startOfDay(for: date)
let end = calendar.date(byAdding: .day, value: 1, to: start)!
return start ..< end
})
func isValid(_ candidate: Date) -> Bool {
return dayRanges.contains(where: { $0.contains(candidate) })
}
我们还可以将过滤与转换分开。这样就可以消除对compactMap
的使用:
let filteredData = Array(filteredApps.lazy.filter({ isValid($0.date) }).map(self.timeSeriesData))
+ locationsData.lazy.filter({ isValid($0.date) }).map(self.timeSeriesData)
或者我们可以两次使用compactMap
:
let filteredData = filteredApps.compactMap({ isValid($0.date) ? self.timeSeriesData(appData: $0) : nil })
+ locationsData.compactMap({ isValid($0.date) ? self.timeSeriesData(locationData: $0) : nil })