2
votes

Impossible d'utiliser le membre en mutation ... car ajouter?

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:

  • variables intermédiaires
  • variables pour lesquelles ce dont j'ai besoin n'est qu'une constante


3 commentaires

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.


4 Réponses :


2
votes

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})


4 commentaires

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



5
votes

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


3 commentaires

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 (contentsOf newElements: S) -> Array where S.Element == Element {return self + Array (newElements)}}


@ robertotomás Vous avez raison, j'étais sûr qu'il y a ajout mais évidemment ce n'est pas là.



2
votes

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
    })


0 commentaires

1
votes

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


0 commentaires