Je ne comprends pas pourquoi j'obtiens cette erreur (swift 4.2.1).
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 }
Cela produit
Impossible d'utiliser un membre mutant sur une valeur immuable: l'appel de fonction renvoie une valeur immuable
sur la troisième ligne.
Mais ce n'est pas le cas:
// 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 }
Ma confusion vient du fait que je manipule plutôt une séquence avec append que de l'attribuer d'abord à une constante, puis de y ajouter . Pourquoi ma séquence est-elle en lecture seule?
edit: apparemment, la carte renvoie toujours (et à première vue du moins, bizarrement) une constante. Dans l'ensemble, ma solution est juste:
// 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
})
Mais, est-ce que quelqu'un d'autre trouve cela insatisfaisant? Je suis coincé avec:
4 Réponses :
Ce n'est pas une réponse à votre question, mais cela fonctionnera
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})
Je ne pense pas que la logique ici soit correcte ... si je le déclare comme var filteredDataOpt , j'obtiens la même erreur.
@ robertotomás Vous avez raison, mon code de test était faux. Cela doit signifier que la carte renvoie toujours un tableau immuable
hmm, cela va à l'encontre de la structure mapreduce typique des langages fonctionnels. J'imagine qu'il n'y a pas de mutableMap ?
@ robertotomás, vous pouvez le faire en deux étapes comme dans ma mise à jour ci-dessus
Votre problème peut être réduit à ce qui suit:
let a: Int = 0 let b = a + 1
La solution consiste à utiliser la concaténation au lieu de append:
let a: Int = 0 let b = a += 1 // this is append let c = (a + 1) += 1 // this is append with a temporary expression
en fait, je pensais que ajouter le ferait .. mais j'obtiens maintenant La valeur de type '[_]' n'a pas de membre 'appending' . La méthode + fonctionne. Ce n'est pas seulement vrai pour mes données, mais aussi pour vos exemples (avec La valeur de type '[Int]' n'a aucun membre 'appending' ). Je pense que l ' ajout ne peut fonctionner qu'avec des chaînes. Vous pourriez réviser votre réponse.
@ robertotomás vous pouvez facilement déclarer vous-même une fonction d'ajout (contentsOf :) pour imiter le comportement d'autres fonctions de bibliothèque standard (telles que sort vs trié ) comme ceci: extension Array {func appending
@ robertotomás Vous avez raison, j'étais sûr qu'il y a ajout mais évidemment ce n'est pas là.
Le problème est que la méthode append (contentsOf :) est en train de muter et que l'élément retourné de toute fonction dans swift est par défaut immuable.
C'est pourquoi vous ne pouvez pas appeler la méthode append (contentsOf :) sur le tableau renvoyé par la méthode map .
Mieux vaut utiliser la non mutation méthode ajoutant (contentsOf :) pour votre code.
Donc votre code sera:
// 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
})
Les fonctions renvoient des valeurs immuables. C'est juste comme ça dans Swift. Si vous voulez qu'il soit mutable, vous devez d'abord le stocker dans un var .
Cependant, vous pouvez utiliser + pour concaténer un Array code> avec n'importe quelle séquence . Donc, si filteredApps est un Array , cela devrait fonctionner:
let filteredData = filteredApps.compactMap({ isValid($0.date) ? self.timeSeriesData(appData: $0) : nil })
+ locationsData.compactMap({ isValid($0.date) ? self.timeSeriesData(locationData: $0) : nil })
Il y a plusieurs autres choses que nous pouvons faire pour nettoyer ce code également. Nous pouvons prendre en compte le test de date:
let filteredData = Array(filteredApps.lazy.filter({ isValid($0.date) }).map(self.timeSeriesData))
+ locationsData.lazy.filter({ isValid($0.date) }).map(self.timeSeriesData)
En fonction de vos données, il peut être préférable de pré-calculer les plages de Date valides: p>
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) })
}
Nous pourrions également séparer le filtrage de la transformation. Cela nous permet d'éliminer l'utilisation de compactMap:
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 }
Ou nous pourrions utiliser compactMap deux fois:
XXX
apparemment, la carte renvoie toujours (et à première vue du moins, bizarrement) une constante- la mutabilité n'est pas la propriété de la valeur elle-même, mais de sa «liaison» (variable ou constante); map renvoie simplement une valeur "non liée".Exemple plus petit:
[1, 2] .map ({$ 0 * 2}). Append (6)@ user28434 C'est en fait très important. Même si vous pouviez utiliser une méthode de mutation sur une expression temporaire, le résultat ne serait pas correct.