Make a unique list of objects Java

时光总嘲笑我的痴心妄想 提交于 2019-12-12 09:34:58

问题


I have an ArrayList filled with objects with attributes name and time. I would like to remove duplicates based on the name and keep only records with the latest time. So I have overriden equals and hashcode for name in my object and used code like this.

private List<ChangedRecentlyTO> groupRecords(List<ChangedRecentlyTO> toList) {
    changedRecentlyList.clear(); //static list
    for(ChangedRecentlyTO to : toList) {
        if(!changedRecentlyList.contains(to)) {
            changedRecentlyList.add(to);
        } else {
            if(changedRecentlyList.get(changedRecentlyList.lastIndexOf(to)).getTimeChanged().before(to.getTimeChanged())) {
                changedRecentlyList.remove(to);
                changedRecentlyList.add(to);
            }
        }
    }
    return changedRecentlyList;
}

But I am wondering, is there a better solution?I was thinking about using Set but I am not able to figure out how should I put there the time criterion.


回答1:


You have to me two ways, one which requires understanding how the set work, and one which is more understandable for people who have littler understanding of Java Collections:

If you want to make it simple, you can simply read in the detail the Javadoc of Set, http://docs.oracle.com/javase/6/docs/api/java/util/Set.html#add(E). It clearly states that if an element is already inside, it won't be added again.

  • You implement your equals and hashcode using only the name
  • You sort the items by time and then you add them to the Set.

In such a way, the first time you will add the item to Set, you will be adding the elements with the latest times. When you'll add the others, they will be ignored because they are already contained.


If someone else who does not know exactly the contract of java.util.Set behaves, you might want to extend Set to make your intention clearer. However, since a Set is not supposed to be accessed to "get back an element after removal", you will need to back your set with an HashMap:

interface TimeChangeable {
   long getTimeChanged();
}
public class TimeChangeableSet<E extends TimeCheangeable> implements Set<E> {

    private final HashMap<Integer,E> hashMap = new HashMap<Integer,E>();

    @Override
    public boolean add(E e) {
        E existingValue = hashMap.remove(e.hashCode());
        if(existingValue==null){
            hashMap.put(e.hashCode(),e);
            return true;
        }
        else{
            E toAdd = e.getTimeChanged() > existingValue.getTimeChanged() ? e : existingValue;
            boolean newAdded = e.getTimeChanged() > existingValue.getTimeChanged() ? true : false;
            hashMap.put(e.hashCode(),e);
            return newAdded;
        }

    }

    @Override
    public int size() {
        return hashMap.size();
    }

    @Override
    public boolean isEmpty() {
        return hashMap.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return hashMap.containsKey(o.hashCode());
    }

    @Override
    public Iterator<E> iterator() {
        return hashMap.values().iterator();
    }

    @Override
    public Object[] toArray() {
        return hashMap.values().toArray();
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return hashMap.values().toArray(a);
    }

    @Override
    public boolean remove(Object o) {
        return removeAndGet(o)!=null ? true : false;
    }

    public E removeAndGet (Object o) {
        return hashMap.remove(o.hashCode());
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        boolean containsAll = true;
        for(Object object:c){
            E objectInMap = removeAndGet(object);
            if(objectInMap==null || !objectInMap.equals(object))
                containsAll=false;
        }
        return containsAll;
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        boolean  addAll=true;
        for(E e:c){
            if(!add(e)) addAll=false;
        }
        return addAll;

    }

    @Override
    public boolean retainAll(Collection<?> c) {
        boolean setChanged=false;
        for(E e: hashMap.values()){
            if(!c.contains(e)){
                hashMap.remove(e.hashCode());
                setChanged=true;
            }
        }
        return setChanged;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        throw new UnsupportedOperationException("Please do not use type-unsafe methods in 2012");
    }

    @Override
    public void clear() {
        hashMap.clear();
    }




}



回答2:


Extend HashMap and override put method to put only if new object is more recent than the existing one.

Or, you can create your own dedicated container which will be backed by a HashMap, just like some implementations of Stack are backed by LinkedList


This is a mock code:

import java.util.HashMap;
import java.util.Map;

public class TimeMap<K, V> {

    private Map<K, V> timeMap;

    public TimeMap() {
        this.timeMap = new HashMap<K, V>();
    }

    public void put(K key, V value) {
        if (isNewer(key, value)) {
            this.timeMap.put(key, value);
        }
    }

}



回答3:


Why you dont use a Set and later:

new ArrayList(set);



回答4:


A very quick implementation of what I had in mind.

Assumed the ChangedRecentlyTO object had a name property.

private List<ChangedRecentlyTO> groupRecords(List<ChangedRecentlyTO> toList) {

    Map<String, ChangedRecentlyTO> uniqueMap = new HashMap<String, ChangedRecentlyTO>();

    for(ChangedRecentlyTO to : toList) {
        if (uniqueMap.containsKey(to.getName())) {
            if (uniqueMap.get(to.getName()).getTimeChanged().before(to.getTimeChanged())) {
                uniqueMap.put(to.getName(), to);
            }
        } else {
            uniqueMap.put(to.getName(), to);
        }
    }
    return (List<ChangedRecentlyTO>) uniqueMap.values();
}

After all of that, it doesn't seem to different to your original implementation with the exception that there is no need override hashcode and equals.




回答5:


You could let your class implement the Comparable interface and make compare check the timestamps you are interested in. If you then sort it (e.g. put all the elements in a TreeSet) and then get them out one by one, only if they don't already exist. Something like this:

public void removeDuplicates(List<MyObject> list){
    SortedSet<MyObject> sortedSet = new TreeSet<MyObject>();
    sortedSet.addAll(list);

    //Now clear the list, and start adding them again
    list.clear();
    for(MyObject obj : sortedSet){
        if(!list.contains(obj) {
             list.add(obj);
        } 
    }
    return list;
}

This, however, will only work if two objects with different timestamps are not equal! (in the equals() sense of the word




回答6:


What I would suggest , Make your class Comparable by implementing Comparable interface.Then in comparetTo() based on name and time compare them if object time is recent return 1 else 0(if equal) or -1 .Once you got this functionality you can extend HashMap class and override the put method like.

o1.compareTo(o2) > 0 then simply overwrite the object with latest one. 

Adding logic to @Lopina code like

public class MyHashMap extends HashMap<String, MyClass>{
private Map<String, MyClass> timeMap;

public MyHashMap() {
    this.timeMap = new HashMap<String, MyClass>();
}

public MyClass put(String key, MyClass value) {

    MyClass obj;
    if (isNewer(key, value)) {
        System.out.println("count");
        obj=this.timeMap.put(key, value);
    }else{
        obj=value;
    }
    return obj;
}

private boolean isNewer(String key, MyClass value) {

    if(this.timeMap.get(key)==null ||( key.equals(value.getName()))&& (this.timeMap.get(key).compareTo(value))<0)
        return true;
    else
        return false;
}

@Override
public int size() {

    return this.timeMap.size();
}

@Override
public MyClass get(Object key) {

    return this.timeMap.get(key);
}
}

In MyClass implement comparable interface and override compareTo method like below.

@Override
public int compareTo(MyClass o) {

    return this.getTime().compareTo(o.getTime());
}



回答7:


I wrote a UniqueList class that extends an ArrayList to back its data and utilises a HashSet to efficiently reject duplicates. This gives O(1) Random Access Time and many other speed improvements to manually sweeping the dataset.

https://gist.github.com/hopesenddreams/80730eaafdfe816ddbb1

public class UniqueList<T> extends ArrayList<T> implements Set<T>
{
    HashMap<T,Integer> hash; // T -> int

    public UniqueList()
    {
        hash = new HashMap<>();
    }

    /*
    * O(n)
    * */
    @Override
    public void add(int location, T object)
    {
        super.add(location, object);
        for( int i = location ; i < size() ; i++ )
        {
            hash.put(get(i),i);
        }
    }

    /*
    * O(1) amortized.
    * */
    @Override
    public boolean add(T object) {
        if( hash.containsKey(object) ) return false;

        hash.put(object, size());
        super.add(object);

        return true;
    }

    /*
    * O(MAX(collection.size(),n)) because of the hash-value-shift afterwards.
    * */
    @Override
    public boolean addAll(int location, Collection<? extends T> collection) {
        boolean bChanged = false;
        for( T t : collection)
        {
            if( ! hash.containsKey( t ) )
            {
                hash.put(t, size());
                super.add(t);
                bChanged = true;
            }
        }

        for( int i = location + collection.size() ; i < size() ; i ++ )
        {
            hash.put( get(i) , i );
        }

        return bChanged;
    }

    /*
    * O(collection.size())
    * */
    @Override
    public boolean addAll(Collection<? extends T> collection) {
        boolean bChanged = false;
        for( T t : collection)
        {
            if( ! hash.containsKey( t ) )
            {
                hash.put( t , size() );
                super.add(t);
                bChanged = true;
            }
        }
        return bChanged;
    }

    /*
    * O(n)
    * */
    @Override
    public void clear() {
        hash.clear();
        super.clear();
    }

    /*
    * O(1)
    * */
    @Override
    public boolean contains(Object object) {
        return hash.containsKey(object);
    }

    /*
    * O(collection.size())
    * */
    @Override
    public boolean containsAll(Collection<?> collection) {
        boolean bContainsAll = true;
        for( Object c : collection ) bContainsAll &= hash.containsKey(c);
        return bContainsAll;
    }

    /*
    * O(1)
    * */
    @Override
    public int indexOf(Object object) {
        //noinspection SuspiciousMethodCalls
        Integer index = hash.get(object);
        return index!=null?index:-1;
    }

    /*
    * O(1)
    * */
    @Override
    public int lastIndexOf(Object object)
    {
        return hash.get(object);
    }

    /*
    * O(n) because of the ArrayList.remove and hash adjustment
    * */
    @Override
    public T remove(int location) {
        T t = super.remove(location);
        hash.remove( t );
        for( int i = size() - 1 ; i >= location  ; i -- )
        {
            hash.put( get(i) , i );
        }
        return t;
    }

    /*
    * O(n) because of the ArrayList.remove and hash adjustment
    * */
    @Override
    public boolean remove(Object object) {
        Integer i = hash.get( object );
        if( i == null ) return false;
        remove( i.intValue() );
        return true;
    }



  /*
    * O( MAX( collection.size() , ArrayList.removeAll(collection) ) )
    * */
    @Override
    public boolean removeAll(@NonNull Collection<?> collection) {
        for( Object c : collection )
        {
            hash.remove( c );
        }
        return super.removeAll( collection );
    }
}


来源:https://stackoverflow.com/questions/11448129/make-a-unique-list-of-objects-java

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!