Combinez conditionnellement Lambdas dans Kotlin pour filtrer avec plusieurs prédicats

2020-06-02 android kotlin filter predicate

Supposons que j'ai les prédicats suivants comme lambdas prédéfinis pour un POJO de villageois.

    val matchesSearch: (Villager, String) -> Boolean =
        { villager: Villager, query: String -> villager.name.contains(query) }

    val matchesGender: (Villager, Int) -> Boolean =
        { villager: Villager, filter: Int -> filter == villager.gender }

    val matchesPersonality: (Villager, Int) -> Boolean =
        { villager: Villager, filter: Int -> filter == villager.personality }

    val matchesSpecies: (Villager, Int) -> Boolean =
        { villager: Villager, filter: Int -> filter == villager.species }

    val matchesHobby: (Villager, Int) -> Boolean =
        { villager: Villager, filter: Int -> filter == villager.hobby }

Je veux seulement appliquer le prédicat SI une certaine condition est remplie. Par exemple, filtrez uniquement par sexe si un filtre de sexe a été appliqué. Ou je ne souhaite faire correspondre la requête de recherche que si une a été entrée.

fun getVillagersThatMatchQueryAndFilters(list: List<Villager>): List<Villager> {

    val conditions = ArrayList<Predicate<Villager>>()

    if (searchQuery.isNotEmpty()) {
        // TODO: apply the matchesSearch() predicate lambda
        conditions.add(Predicate { villager -> villager.name.contains(query) })
    }
    if (genderFilter > 0) {
        // TODO: apply the matchesGender() predicate lambda
        conditions.add(Predicate { genderFilter == villager.gender })
    }
    if (personalityFilter > 0) {
        // TODO: apply the matchesPersonality() predicate lambda
        conditions.add(Predicate { personalityFilter == villager.personality })
    }
    if (speciesFilter > 0) {
        // TODO: apply the matchesSpecies() predicate lambda
        conditions.add(Predicate { speciesFilter == villager.species })
    }
    if (hobbyFilter > 0) {
        // TODO: apply the matchesHobby() predicate lambda
        conditions.add(Predicate { hobbyFilter == villager.hobby })
    }

    return list.filter {
        // TODO: match on all conditionally applied predicates
        conditions.allPredicatesCombinedWithAnd() // this doesn't actually exist
    }
}

Auparavant, j'avais fait list.filter{ } dans chaque condition et appliqué le prédicat nécessaire, cependant, étant donné que je pouvais avoir jusqu'à 5 prédicats appliqués, c'est assez terrible pour les performances car il itère sur la liste à chaque fois .filter() est appelé.

Existe-t-il un moyen d'itérer par programme une List<Predicate<T>> et de combiner des prédicats avec .and() ou && afin que je puisse appliquer le filtre UNE FOIS? Sinon, comment puis-je combiner conditionnellement ces prédicats?

J'utiliserais ici l' exemple final de Java qui utilise Predicates.stream() et Collectors mais il nécessite le niveau d'API Android 24 (supérieur à mon Android min actuel).

SOLUTION CI - DESSOUS - grâce aux suggestions de @Laurence et @ IR42

Fonctionne pour <Android API 24

Créez une liste des lambdas et ajoutez conditionnellement tous les prédicats auxquels vous souhaitez faire correspondre les éléments de la liste. Utilisez ensuite la méthode Collections.Aggregates.all() pour vous assurer que vous filtrez contre TOUS les prédicats. (L'équivalent de faire pred1.and(pred2).and(etc.)... pour tous les éléments de la liste.)

Économiseur de performances énorme si vous avez un grand nombre de prédicats à filtrer car list.filter() n'est appelé qu'une fois.

fun getVillagersThatMatchQueryAndFilters(list: List<Villager>): List<Villager> {

    val conditions = ArrayList<(Villager) -> Boolean>()

    if (searchQuery.isNotEmpty()) {
        conditions.add{ it.name.contains(query) }
    }
    if (genderFilter > 0) {
        conditions.add{ genderFilter == it.gender }
    }
    if (personalityFilter > 0) {
        conditions.add{ personalityFilter == it.personality }
    }
    if (speciesFilter > 0) {
        conditions.add{ speciesFilter == it.species }
    }
    if (hobbyFilter > 0) {
        conditions.add{ hobbyFilter == it.hobby }
    }

    return list.filter { candidate -> conditions.all { it(candidate) } }
}

Answers

Il y a la fonction all extension, et comme point d'intérêt la fonction any extension également. Je pense que vous n'avez qu'à les utiliser dans votre bloc de filtre sur vos prédicats. Voici un exemple très simple avec un ensemble stupide de prédicats:

fun main() {
    val thingsToFilter = listOf(-1,2,5)

    val predicates = listOf(
        { value: Int -> value > 0 },
        { value: Int -> value > 4 }
    )

    val filtered = thingsToFilter.filter {candidate ->
        predicates.all{ it(candidate)}
    }

    print(filtered) // Outputs [5]

}

Related