Не может использовать мутирующий член ... потому что добавить? - PullRequest
0 голосов
/ 16 января 2019

Я не понимаю, почему я получаю эту ошибку (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. Почему моя последовательность только для чтения?

edit: очевидно, карта всегда (и, на первый взгляд, по крайней мере, странно) возвращает константу. В общем, мое решение просто:

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 }

Но кто-нибудь еще находит это неудовлетворительным? Я застрял с:

  • промежуточные переменные
  • переменные, где мне нужна только константа

Ответы [ 4 ]

0 голосов
/ 16 января 2019

Функции возвращают неизменные значения. Это так, как это происходит в 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 })
0 голосов
/ 16 января 2019

Ваша проблема может быть уменьшена до следующего:

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.

Причина, по которой временные выражения являются постоянными (неизменяемыми), заключается в том, чтобы не допустить подобных ошибок.

0 голосов
/ 16 января 2019

Проблема в том, что метод 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
    })
0 голосов
/ 16 января 2019

Не ответ на ваш вопрос, но это будет работать

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})
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...