Je travaille sur un programme qui utilise un ArrayList
pour stocker Strings
. Le programme invite l'utilisateur avec un menu et lui permet de choisir une opération à effectuer. Ces opérations consistent à ajouter des chaînes à la liste, à imprimer les entrées, etc. Ce que je veux pouvoir faire, c'est créer une méthode appelée removeDuplicates()
. Cette méthode recherchera le ArrayList
et supprimera toutes les valeurs dupliquées. Je souhaite laisser une instance de la ou des valeurs dupliquées dans la liste. Je souhaite également que cette méthode renvoie le nombre total de doublons supprimés.
J'ai essayé d'utiliser des boucles imbriquées pour cela, mais j'ai rencontré des problèmes car lorsque les entrées sont supprimées, l'indexation de ArrayList
est modifiée et les choses ne fonctionnent pas comme elles le devraient. Je sais conceptuellement ce que je dois faire mais j'ai du mal à implémenter cette idée dans le code.
Voici un pseudo code:
commencer par la première entrée; vérifier chaque entrée suivante dans la liste et voir si elle correspond à la première entrée; supprimer chaque entrée suivante dans la liste qui correspond à la première entrée;
une fois que toutes les entrées ont été examinées, passez à la deuxième entrée; vérifier chaque entrée dans la liste et voir si elle correspond à la deuxième entrée; supprimer chaque entrée de la liste qui correspond à la deuxième entrée;
répéter pour l'entrée dans la liste
Voici le code que j'ai jusqu'à présent:
public int removeDuplicates()
{
int duplicates = 0;
for ( int i = 0; i < strings.size(); i++ )
{
for ( int j = 0; j < strings.size(); j++ )
{
if ( i == j )
{
// i & j refer to same entry so do nothing
}
else if ( strings.get( j ).equals( strings.get( i ) ) )
{
strings.remove( j );
duplicates++;
}
}
}
return duplicates;
}
UPDATE : Il semble que Will recherche une solution de devoirs qui implique de développer l'algorithme pour supprimer les doublons, plutôt qu'une solution pragmatique utilisant Sets. Voir son commentaire:
Merci pour les suggestions. Cela fait partie d'un devoir et je pense que l'enseignant avait prévu que la solution n'inclue pas les ensembles. En d'autres termes, je dois proposer une solution qui recherchera et supprimera les doublons sans implémenter un HashSet
. Le professeur a suggéré d'utiliser des boucles imbriquées, ce que j'essaie de faire, mais j'ai eu quelques problèmes avec l'indexation de ArrayList
après la suppression de certaines entrées.
Pourquoi ne pas utiliser une collection comme Set
(et une implémentation comme HashSet
) qui empêche naturellement les doublons?
Vous pouvez utiliser des boucles imbriquées sans aucun problème:
public static int removeDuplicates(ArrayList<String> strings) {
int size = strings.size();
int duplicates = 0;
// not using a method in the check also speeds up the execution
// also i must be less that size-1 so that j doesn't
// throw IndexOutOfBoundsException
for (int i = 0; i < size - 1; i++) {
// start from the next item after strings[i]
// since the ones before are checked
for (int j = i + 1; j < size; j++) {
// no need for if ( i == j ) here
if (!strings.get(j).equals(strings.get(i)))
continue;
duplicates++;
strings.remove(j);
// decrease j because the array got re-indexed
j--;
// decrease the size of the array
size--;
} // for j
} // for i
return duplicates;
}
Vous pouvez essayer cette doublure pour prendre une copie de l'ordre de conservation des chaînes.
List<String> list;
List<String> dedupped = new ArrayList<String>(new LinkedHashSet<String>(list));
Cette approche est également O(n) amortie au lieu de O (n ^ 2)
Juste pour clarifier mon commentaire sur la réponse de matt b, si vous voulez vraiment compter le nombre de doublons supprimés, utilisez ce code:
List<String> list = new ArrayList<String>();
// list gets populated from user input...
Set<String> set = new HashSet<String>(list);
int numDuplicates = list.size() - set.size();
J'ai essayé d'utiliser des boucles imbriquées pour accomplir cela, mais j'ai rencontré des problèmes parce que lorsque les entrées sont supprimées, l'indexation de la liste de tableaux est modifiée et les choses ne se produisent pas ne fonctionnent pas comme ils le devraient
Pourquoi ne diminuez-vous pas simplement le compteur chaque fois que vous supprimez une entrée.
Lorsque vous supprimez une entrée, les éléments se déplacent également:
ej:
String [] a = {"a","a","b","c" }
postes:
a[0] = "a";
a[1] = "a";
a[2] = "b";
a[3] = "c";
Après avoir supprimé votre premier "a", les index sont:
a[0] = "a";
a[1] = "b";
a[2] = "c";
Vous devez donc prendre cela en considération et diminuer la valeur de j
(j--
) pour éviter de "sauter" sur une valeur.
Voir cette capture d'écran:
List<String> lst = new ArrayList<String>();
lst.add("one");
lst.add("one");
lst.add("two");
lst.add("three");
lst.add("three");
lst.add("three");
Set se =new HashSet(lst);
lst.clear();
lst = new ArrayList<String>(se);
for (Object ls : lst){
System.out.println("Resulting output---------" + ls);
}
Un moyen très simple de supprimer la chaîne en double de l'arraylist
ArrayList al = new ArrayList();
// add elements to al, including duplicates
HashSet hs = new HashSet();
hs.addAll(al);
al.clear();
al.addAll(hs);
public Collection removeDuplicates(Collection c) {
// Returns a new collection with duplicates removed from passed collection.
Collection result = new ArrayList();
for(Object o : c) {
if (!result.contains(o)) {
result.add(o);
}
}
return result;
}
ou
public void removeDuplicates(List l) {
// Removes duplicates in place from an existing list
Object last = null;
Collections.sort(l);
Iterator i = l.iterator();
while(i.hasNext()) {
Object o = i.next();
if (o.equals(last)) {
i.remove();
} else {
last = o;
}
}
}
Tous deux non testés.
Vous pouvez faire quelque chose comme ça, le must de ce que les gens ont répondu ci-dessus est une alternative, mais voici une autre.
for (int i = 0; i < strings.size(); i++) {
for (int j = j + 1; j > strings.size(); j++) {
if(strings.get(i) == strings.get(j)) {
strings.remove(j);
j--;
}`
}
}
return strings;
En supposant que vous ne pouvez pas utiliser un ensemble comme vous l'avez dit, la façon la plus simple de résoudre le problème est d'utiliser une liste temporaire, plutôt que d'essayer de supprimer les doublons en place:
public class Duplicates {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("one");
list.add("one");
list.add("two");
list.add("three");
list.add("three");
list.add("three");
System.out.println("Prior to removal: " +list);
System.out.println("There were " + removeDuplicates(list) + " duplicates.");
System.out.println("After removal: " + list);
}
public static int removeDuplicates(List<String> list) {
int removed = 0;
List<String> temp = new ArrayList<String>();
for(String s : list) {
if(!temp.contains(s)) {
temp.add(s);
} else {
//if the string is already in the list, then ignore it and increment the removed counter
removed++;
}
}
//put the contents of temp back in the main list
list.clear();
list.addAll(temp);
return removed;
}
}
Vous pouvez remplacer le doublon par une chaîne vide *, gardant ainsi l'indexation intacte. Ensuite, après avoir terminé, vous pouvez supprimer les chaînes vides.
* Mais uniquement si une chaîne vide n'est pas valide dans votre implémentation.
Vous pouvez ajouter la liste dans un HashSet, puis convertir à nouveau ce hachage en liste pour supprimer les doublons.
public static int removeDuplicates(List<String> duplicateList){
List<String> correctedList = new ArrayList<String>();
Set<String> a = new HashSet<String>();
a.addAll(duplicateList);
correctedList.addAll(a);
return (duplicateList.size()-correctedList.size());
}
ici, il renverra le nombre de doublons. Vous pouvez également utiliser la correctList avec toutes les valeurs uniques
Le problème que vous voyez dans votre code est que vous supprimez une entrée pendant l'itération, invalidant ainsi l'emplacement d'itération.
Par exemple:
{"a", "b", "c", "b", "b", "d"}
i j
Vous supprimez maintenant les chaînes [j].
{"a", "b", "c", "b", "d"}
i j
La boucle intérieure se termine et j est incrémenté.
{"a", "b", "c", "b", "d"}
i j
Un seul 'b' en double détecté ... oups.
dans ces cas, la meilleure pratique consiste à stocker les emplacements à supprimer et à les supprimer une fois l'itération terminée dans la liste d'arrays. (Un bonus, l'appel strings.size () peut être optimisé en dehors des boucles par vous ou le compilateur)
Astuce, vous pouvez commencer à itérer avec j à i + 1, vous avez déjà vérifié le 0 - i!
L'utilisation d'un ensemble est la meilleure option (comme d'autres l'ont suggéré).
Si vous souhaitez comparer tous les éléments d'une liste entre eux, vous devez adapter légèrement vos boucles for:
for(int i = 0; i < max; i++)
for(int j = i+1; j < max; j++)
De cette façon, vous ne comparez pas chaque élément une seule fois au lieu de deux. En effet, la deuxième boucle commence à l'élément suivant par rapport à la première boucle.
De plus, lors de la suppression d'une liste lors de l'itération sur eux (même lorsque vous utilisez une boucle for au lieu d'un itérateur), gardez à l'esprit que vous réduisez la taille de la liste. Une solution courante consiste à conserver une autre liste d'éléments que vous souhaitez supprimer, puis après avoir décidé de les supprimer, vous les supprimez de la liste d'origine.
public <Foo> Entry<Integer,List<Foo>> uniqueElementList(List<Foo> listWithPossibleDuplicates) {
List<Foo> result = new ArrayList<Foo>();//...might want to pre-size here, if you have reliable info about the number of dupes
Set<Foo> found = new HashSet<Foo>(); //...again with the pre-sizing
for (Foo f : listWithPossibleDuplicates) if (found.add(f)) result.add(f);
return entryFactory(listWithPossibleDuplicates.size()-found.size(), result);
}
puis une méthode entryFactory(Integer key, List<Foo> value)
. Si vous souhaitez muter la liste d'origine (peut-être pas une bonne idée, mais peu importe) à la place:
public <Foo> int removeDuplicates(List<Foo> listWithPossibleDuplicates) {
int original = listWithPossibleDuplicates.size();
Iterator<Foo> iter = listWithPossibleDuplicates.iterator();
Set<Foo> found = new HashSet<Foo>();
while (iter.hasNext()) if (!found.add(iter.next())) iter.remove();
return original - found.size();
}
pour votre particulier cas utilisant des chaînes, vous devrez peut-être gérer certaines contraintes d'égalité supplémentaires (par exemple, les versions majuscules et minuscules sont-elles identiques ou différentes?).
EDIT: ah, ce sont des devoirs. Recherchez Iterator/Iterable dans le cadre Java Collections framework, ainsi que Set, et voyez si vous ne parvenez pas à la même conclusion que celle que j'ai proposée. La partie générique est juste de la sauce.
Vous trouverez ci-dessous le code pour supprimer les éléments en double d'une liste sans modifier l'ordre de la liste, sans utiliser de liste temporaire et sans utiliser de variables définies.Ce code économise la mémoire et augmente les performances.
Il s'agit d'une méthode générique qui fonctionne avec tout type de liste.
Telle était la question posée dans l'une des interviews. J'ai recherché la solution dans de nombreux forums, mais je n'ai pas pu en trouver, alors j'ai pensé que c'était le bon forum pour publier le code.
public List<?> removeDuplicate(List<?> listWithDuplicates) {
int[] intArray = new int[listWithDuplicates.size()];
int dupCount = 1;
int arrayIndex = 0;
int prevListIndex = 0; // to save previous listIndex value from intArray
int listIndex;
for (int i = 0; i < listWithDuplicates.size(); i++) {
for (int j = i + 1; j < listWithDuplicates.size(); j++) {
if (listWithDuplicates.get(j).equals(listWithDuplicates.get(i)))
dupCount++;
if (dupCount == 2) {
intArray[arrayIndex] = j; // Saving duplicate indexes to an array
arrayIndex++;
dupCount = 1;
}
}
}
Arrays.sort(intArray);
for (int k = intArray.length - 1; k >= 0; k--) {
listIndex = intArray[k];
if (listIndex != 0 && prevListIndex != listIndex){
listWithDuplicates.remove(listIndex);
prevListIndex = listIndex;
}
}
return listWithDuplicates;
}
La boucle intérieure for
n'est pas valide. Si vous supprimez un élément, vous ne pouvez pas incrémenter j
, car j
pointe maintenant sur l'élément après celui que vous avez supprimé et vous devrez l'inspecter.
En d'autres termes, vous devez utiliser une boucle while
au lieu d'une boucle for
, et uniquement incrémenter j
si les éléments de i
et j
ne correspondent pas. S'ils font correspondent, supprimez l'élément à j
. size()
diminuera de 1 et j
pointera maintenant sur l'élément suivant, il n'est donc pas nécessaire d'augmenter j
.
De plus, il n'y a aucune raison d'inspecter les éléments tous dans la boucle interne, juste ceux qui suivent i
, car les doublons avant i
ont déjà été supprimés par les itérations précédentes.
L'utilisation d'un ensemble est la meilleure option pour supprimer les doublons:
Si vous avez une liste de tableaux, vous pouvez supprimer les doublons tout en conservant les fonctionnalités de liste de tableaux:
List<String> strings = new ArrayList<String>();
//populate the array
...
List<String> dedupped = new ArrayList<String>(new HashSet<String>(strings));
int numdups = strings.size() - dedupped.size();
si vous ne pouvez pas utiliser un ensemble, triez le tableau (Collections.sort ()) et parcourez la liste, en vérifiant si l'élément actuel est égal à l'élément précédent, s'il l'est, supprimez-le.
Je suis un peu en retard pour rejoindre cette question, mais je suis venu avec une meilleure solution concernant le même en utilisant le type GÉNÉRIQUE. Toutes les solutions fournies ci-dessus ne sont qu'une solution. Ils augmentent la portée de la complexité de l'ensemble du thread d'exécution.
Nous pouvons le minimiser en utilisant une technique qui devrait faire le nécessaire, au moment du chargement.
Exemple: Supposons que lorsque vous utilisez une liste de types de classe comme:
ArrayList<User> usersList = new ArrayList<User>();
usersList.clear();
User user = new User();
user.setName("A");
user.setId("1"); // duplicate
usersList.add(user);
user = new User();
user.setName("A");
user.setId("1"); // duplicate
usersList.add(user);
user = new User();
user.setName("AB");
user.setId("2"); // duplicate
usersList.add(user);
user = new User();
user.setName("C");
user.setId("4");
usersList.add(user);
user = new User();
user.setName("A");
user.setId("1"); // duplicate
usersList.add(user);
user = new User();
user.setName("A");
user.setId("2"); // duplicate
usersList.add(user);
}
La classe pour laquelle est la base de la liste d'arrays utilisée ci-dessus: Classe utilisateur
class User {
private String name;
private String id;
/**
* @param name
* the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param id
* the id to set
*/
public void setId(String id) {
this.id = id;
}
/**
* @return the id
*/
public String getId() {
return id;
}
}
Maintenant dans Java il y a deux méthodes Overrided présentes de Object (parent) Class, qui peuvent aider ici dans les moyens de mieux servir notre but. Ce sont:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
Vous devez remplacer ces méthodes dans la classe User
Voici le code complet:
https://Gist.github.com/458431
Faites-moi savoir si vous avez des questions.
public ArrayList removeDuplicates(ArrayList <String> inArray)
{
ArrayList <String> outArray = new ArrayList();
boolean doAdd = true;
for (int i = 0; i < inArray.size(); i++)
{
String testString = inArray.get(i);
for (int j = 0; j < inArray.size(); j++)
{
if (i == j)
{
break;
}
else if (inArray.get(j).equals(testString))
{
doAdd = false;
break;
}
}
if (doAdd)
{
outArray.add(testString);
}
else
{
doAdd = true;
}
}
return outArray;
}