2009-05-22 14 views
47

Puisque Java ne permet pas de passer des méthodes comme paramètres, quelle astuce utilisez-vous pour implémenter Python comme la compréhension de liste en Java?Compréhension de liste de type Python en Java

J'ai une liste (ArrayList) de chaînes. J'ai besoin de transformer chaque élément en utilisant une fonction pour obtenir une autre liste. J'ai plusieurs fonctions qui prennent une chaîne en entrée et retournent une autre chaîne en sortie. Comment est-ce que je fais une méthode générique qui peut être donnée la liste et la fonction en tant que paramètres de sorte que je puisse obtenir une liste arrière avec chaque élément traité. Ce n'est pas possible au sens littéral, mais quel truc dois-je utiliser?

L'autre option consiste à écrire une nouvelle fonction pour chaque fonction de traitement de chaîne plus petite qui boucle simplement sur toute la liste, ce qui n'est pas très cool.

+2

En tant que fyi, vous pouvez utiliser Jython ou Scala pour obtenir la compréhension de la liste sur la JVM – geowa4

+3

... ou Clojure! :) – Ashe

+0

SMH en lisant toutes les réponses à ce sujet. En Python, vous pouvez facilement écrire une liste de compréhension en une ligne de 40 à 60 caractères. Toutes les solutions proposées ici sont des lignes multiples, et la plupart d'entre elles sont plus longues que la seule ligne qu'il faudrait en Python. – ArtOfWarfare

Répondre

33

Fondamentalement, vous créez une interface de fonction:

public interface Func<In, Out> { 
    public Out apply(In in); 
} 

et passe ensuite dans une sous-classe anonyme à votre méthode.

Votre méthode peut soit appliquer la fonction de chaque élément en place:

public static <T> void applyToListInPlace(List<T> list, Func<T, T> f) { 
    ListIterator<T> itr = list.listIterator(); 
    while (itr.hasNext()) { 
     T output = f.apply(itr.next()); 
     itr.set(output); 
    } 
} 
// ... 
List<String> myList = ...; 
applyToListInPlace(myList, new Func<String, String>() { 
    public String apply(String in) { 
     return in.toLowerCase(); 
    } 
}); 

ou créer un nouveau List (essentiellement la création d'une cartographie à partir de la liste des entrées à la liste de sortie):

public static <In, Out> List<Out> map(List<In> in, Func<In, Out> f) { 
    List<Out> out = new ArrayList<Out>(in.size()); 
    for (In inObj : in) { 
     out.add(f.apply(inObj)); 
    } 
    return out; 
} 
// ... 
List<String> myList = ...; 
List<String> lowerCased = map(myList, new Func<String, String>() { 
    public String apply(String in) { 
     return in.toLowerCase(); 
    } 
}); 

Lequel est préférable dépend de votre cas d'utilisation. Si votre liste est extrêmement grande, la solution en place peut être la seule solution viable; Si vous souhaitez appliquer de nombreuses fonctions différentes à la même liste d'origine pour créer de nombreuses listes de dérivées, vous devez utiliser la version map.

+1

Mais alors vous me demandez de mettre toutes les petites fonctions dans une classe différente car elles doivent avoir un nom standard ('apply' dans votre cas). Droite ? – euphoria83

+1

Pas nécessairement; votre classe anonyme peut simplement appeler la petite fonction à l'intérieur de apply(). C'est aussi proche que Java arrive à des pointeurs de fonction sans s'aventurer dans les périls de la réflexion. –

+0

doToList réinvente la roue. Ce que vous avez fait ici est une mauvaise conception de ce qu'on appelle habituellement la carte. L'interface habituelle est publique statique Liste carte (Liste , Func f); Ce qu'il fait est de produire une autre liste au lieu de modifier celle en place. Si vous devez modifier la liste d'origine sans détruire la référence, faites simplement un .clear() suivi d'un addAll(). Ne combinez pas tout cela en une seule méthode. – Pyrolistical

16

Le Google Collections library a beaucoup de classes pour travailler avec des collections et des itérateurs à un niveau beaucoup plus élevé que les supports Java, et de manière fonctionnelle (filtre, carte, pli, etc.). Il définit les interfaces et les méthodes Function et Predicate qui les utilisent pour traiter les collections de sorte que vous n'en ayez pas besoin. Il a également des fonctions de commodité qui rendent le traitement des génériques de Java moins ardu. J'utilise également Hamcrest ** pour filtrer les collections.

Les deux bibliothèques sont faciles à combiner avec les classes d'adaptateur.


** Déclaration d'intérêt: Je co-écrit Hamcrest

+11

Par curiosité, pourquoi s'appelle-t-on Hamcrest? Je n'arrive toujours pas à savoir si ça sonne savoureux ou pas. –

+12

C'est une anagramme de "matchers". – Nat

5

Apache Commons CollectionsUtil.transform(Collection, Transformer) est une autre option.

+0

Malheureusement, ce n'est pas générique. Dans ce cas, tout ce que cela signifie est un casting supplémentaire, mais cela pourrait être un problème dans d'autres cas. –

27

En Java 8, vous pouvez utiliser des références de méthode:

List<String> list = ...; 
list.replaceAll(String::toUpperCase); 

Ou, si vous voulez créer une nouvelle instance de liste:

List<String> upper = list.stream().map(String::toUpperCase).collect(Collectors.toList()); 
+7

La question a 7 ans et Java 8 n'existait pas alors. Cela devrait être la réponse acceptée maintenant;) – zpontikas

1

Je construis ce projet d'écrire la compréhension de la liste Java, est maintenant une preuve de concept dans https://github.com/farolfo/list-comprehension-in-java

Exemples

// { x | x E {1,2,3,4}^x is even } 
// gives {2,4} 

Predicate<Integer> even = x -> x % 2 == 0; 

List<Integer> evens = new ListComprehension<Integer>() 
    .suchThat(x -> { 
     x.belongsTo(Arrays.asList(1, 2, 3, 4)); 
     x.is(even); 
    }); 
// evens = {2,4}; 

Et si nous voulons transformer l'expression de sortie en quelque sorte comme

// { x * 2 | x E {1,2,3,4}^x is even } 
// gives {4,8} 

List<Integer> duplicated = new ListComprehension<Integer>() 
    .giveMeAll((Integer x) -> x * 2) 
    .suchThat(x -> { 
     x.belongsTo(Arrays.asList(1, 2, 3, 4)); 
     x.is(even); 
    }); 
// duplicated = {4,8} 
+1

Une partie de la beauté de la compréhension de la liste Python est combien il est court. Vos 6 longues lignes de Java pourraient être écrites simplement comme '[x * 2 pour x dans 1, 2, 3, 4 si x% 2 == 0]' ... 1 ligne de 41 caractères. Vous ne savez pas quelle quantité de votre code est juste terrible à lire à cause de la sacroce verbosité de Java par rapport à ce qui se passe parce que votre bibliothèque ne fait pas assez de choses de façon concise. – ArtOfWarfare

+0

C'est encore mieux que beaucoup d'autres solutions ici, j'aime vraiment ça – rhbvkleef

0

Vous pouvez utiliser lambdas pour la fonction, comme ceci:

class Comprehension<T> { 
    /** 
    *in: List int 
    *func: Function to do to each entry 
    */ 
    public List<T> comp(List<T> in, Function<T, T> func) { 
     List<T> out = new ArrayList<T>(); 
     for(T o: in) { 
      out.add(func.apply(o)); 
     } 
     return out; 
    } 
} 

l'utilisation:

List<String> stuff = new ArrayList<String>(); 
stuff.add("a"); 
stuff.add("b"); 
stuff.add("c"); 
stuff.add("d"); 
stuff.add("cheese"); 
List<String> newStuff = new Comprehension<String>().comp(stuff, (a) -> { //The <String> tells the comprehension to return an ArrayList<String> 
    a.equals("a")? "1": 
      (a.equals("b")? "2": 
       (a.equals("c")? "3": 
        (a.equals("d")? "4": a 
    ))) 
}); 

reviendra:

["1", "2", "3", "4", "cheese"]