How to make a set backed by a map? - java

There is a method in the Collections class.
Set<E> Collections.newSetFromMap(<backing map>)
What does it mean by the backing map and the set backed by a map?

Perhaps it would be illuminating to look at the implementation:
private static class SetFromMap<E> extends AbstractSet<E>
implements Set<E>, Serializable
{
private final Map<E, Boolean> m; // The backing map
private transient Set<E> s; // Its keySet
SetFromMap(Map<E, Boolean> map) {
if (!map.isEmpty())
throw new IllegalArgumentException("Map is non-empty");
m = map;
s = map.keySet();
}
public void clear() { m.clear(); }
public int size() { return m.size(); }
public boolean isEmpty() { return m.isEmpty(); }
public boolean contains(Object o) { return m.containsKey(o); }
public boolean remove(Object o) { return m.remove(o) != null; }
public boolean add(E e) { return m.put(e, Boolean.TRUE) == null; }
public Iterator<E> iterator() { return s.iterator(); }
public Object[] toArray() { return s.toArray(); }
public <T> T[] toArray(T[] a) { return s.toArray(a); }
public String toString() { return s.toString(); }
public int hashCode() { return s.hashCode(); }
public boolean equals(Object o) { return o == this || s.equals(o); }
public boolean containsAll(Collection<?> c) {return s.containsAll(c);}
public boolean removeAll(Collection<?> c) {return s.removeAll(c);}
public boolean retainAll(Collection<?> c) {return s.retainAll(c);}
// addAll is the only inherited implementation
private static final long serialVersionUID = 2454657854757543876L;
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException
{
stream.defaultReadObject();
s = m.keySet();
}
}
Edit - added explanation:
The map that you provide is used as the m field in this object.
When you add an element e to the set, it adds an entry e -> true to the map.
public boolean add(E e) { return m.put(e, Boolean.TRUE) == null; }
So this class turns your Map into an object that behaves like a Set by simply ignoring the values that things are mapped to, and just using the keys.

I just made an example code for you
HashMap<String, Boolean> map = new HashMap<String, Boolean>();
Set<String> set = Collections.newSetFromMap(map);
System.out.println(set);
for (int i = 0; i < 10; i++)
map.put("" + i, i % 2 == 0);
System.out.println(map);
System.out.println(set);
and the output
[]
{3=false, 2=true, 1=false, 0=true, 7=false, 6=true, 5=false, 4=true, 9=false, 8=true}
[3, 2, 1, 0, 7, 6, 5, 4, 9, 8]

Simply put, Collections.newSetFromMap uses the provided Map<E> implementation to store the Set<E> elements.

The Set internally uses Map to store the values. Here the backing map refers to the set map which is internally used by the set.
For more information.
http://www.jusfortechies.com/java/core-java/inside-set.php

Related

Compiler asking for optional methods from Collections to be overridden

I'm creating a class-wide project with my school mates, I'm supposed to create pull requests for some functions, but I'm having some problems just creating the class and overriding the methods in a way that it would just compile (I don't need to write the methods at this moment, that's the project, I just need it to compile). I've found most methods are working (or at least the compiler is not complaining), but I'm confused about a few things:
Compiler complaining about methods that are optional (like set, and addAll)
The method addAll, although it was added, complains that it wasn't overridden, although it was, and when I add the other addAll method for it, then I get an erasure error as well.
I've read a bunch about it, but I couldn't find a proper conclusion on how to solve it. I'm just using Atom to write my code, and the Terminal, no fancy IDE (perhaps I should learn one).
In case it isn't clear, I'm just looking to have the stubs of the methods available, not an all around answer for every single method, since this is the project with the class.
// https://docs.oracle.com/javase/8/docs/api/java/util/List.html
import java.util.*;
import java.lang.reflect.*;
public class SkipList<E> implements List<E>
{
// compiler complaining, then added, although optional
public E set(int index, E element)
{
throw new IndexOutOfBoundsException();
}
// compiler complaining, then added, although optional
public boolean addAll(Collection <? extends E> c)
{
return true;
}
// Group 1
public boolean add(E e)
{
return true;
}
public void add(int index, E e)
{
}
public boolean addAll(Collection c)
{
return true;
}
public int indexOf(Object o)
{
int index = 0;
return index;
}
public int lastIndexOf(Object o)
{
int index = 0;
return index;
}
// Group 2
public boolean contains(Object o)
{
return true;
}
public boolean containsAll(Collection c)
{
return true;
}
public boolean equals(Object o)
{
return true;
}
public List<E> subList(int fromIndex, int toIndex)
{
List<E> sub = new SkipList<>();
return sub;
}
// Group 3
public boolean isEmpty()
{
return true;
}
public int size()
{
int size = 0;
return size;
}
public void clear()
{
}
public E get(int index)
{
throw new IndexOutOfBoundsException();
}
public E getQuantile(double quantile) // e.g. 0 = minimum, 0.5 = median, 1 = max
{
throw new IndexOutOfBoundsException();
}
// Group 4
public Iterator<E> iterator()
{
throw new IndexOutOfBoundsException();
}
public ListIterator<E> listIterator()
{
throw new IndexOutOfBoundsException();
}
public ListIterator<E> listIterator(int index)
{
throw new IndexOutOfBoundsException();
}
// Group 5
public E remove(int index)
{
throw new IndexOutOfBoundsException();
}
public boolean remove(Object o)
{
return true;
}
public boolean removeAll(Collection c)
{
return true;
}
public boolean retainAll(Collection c)
{
return true;
}
// Group 6
public int hashCode()
{
int hashCode = 0;
return hashCode;
}
public Object[] toArray()
{
Object[] arr = new Object[0];
return arr;
}
public <T> T[] toArray(T[] a)
{
return a;
}
}
Turns out that when reading the API, for some reason I glossed over the other parameter for the addAll method, that led me to believe something else was wrong. I changed the method with the correct parameters and it compiled.
public boolean addAll(int index, Collection <? extends E> c)
{
return true;
}

Compare a map of doubles

I hava a Map<Integer,Double> as a field. I need to implement equals() for that given class.
How to compare the double values using a tolerance.
public class Foo {
Map<Integer, Double> data;
public Map<Integer, Double> getData() {
return data;
}
public void setData(Map<Integer, Double> data) {
this.data = data;
}
#Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Foo))
return false;
Foo foo = (Foo) o;
if (this.data.size() != foo.getData().size())
return false;
Set<Integer> keySet1 = data.keySet();
Set<Integer> keySet2 = foo.getData().keySet();
// keys should same
if (keySet1.containsAll(keySet2) && keySet2.containsAll(keySet1)) {
// for the same key, the values are close
for (Integer key : keySet1) {
if (!isEntryEqual(data.get(key), foo.getData().get(key))) {
return false;
}
}
return true;
}
return false;
}
// also need to override the hashCode method
#Override
public int hashCode() {
List<Integer> keys = new ArrayList<Integer>(this.data.keySet());
return Objects.hash(keys);
}
public static final Double PRECISION = 0.0001;
private static boolean isEntryEqual(Double d1, Double d2) {
return d1 - d2 < PRECISION;
}
}

How to implement iterator on nested collection in Java?

I have a nested collection with this representation Collection<Collection<T>>. I have implemented the Iterator on the class, but the next() method is not giving the right results. It is fetching only the first element of each list. Example List<List<String>> and values are {"1","2"},{"3","4"},{"5","6"}. The Complete layout of class.
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
public class NestedCollectionIterator implements Iterator<Object> {
private Collection<? extends Collection<? extends Object>> _collOfColl = null;
private Iterator<? extends Collection<? extends Object>> itCollection = null;
private Iterator<? extends Object> innerIterator = null;
Object next = null;
public NestedCollectionIterator( Collection<? extends Collection<? extends Object>> collofColl){
_collOfColl = collofColl;
itCollection = _collOfColl.iterator();
}
#Override
public boolean hasNext() {
if(itCollection.hasNext()){
innerIterator = itCollection.next().iterator();
if(innerIterator != null || innerIterator.hasNext()){
next = innerIterator.next();
return true;
}
}
return false;
}
public Object next() {
if(hasNext()){
Object obj = next;
//Need some changes here.
return obj;
}
return null;
}
#Override
public void remove() {}
}
Class to test the implementation
class Sample{
public static void main(String[] args){
List<List<String>> Nestedlist = new ArrayList<List<String>>();
List<String> l = new ArrayList<String>();
l.add("1");
l.add("2");
Nestedlist.add(l);
l = new ArrayList<String>();
l.add("3");
l.add("4");
Nestedlist.add(l);
l = new ArrayList<String>();
l.add("5");
l.add("6");
Nestedlist.add(l);
NestedCollectionIterator cc = new NestedCollectionIterator(Nestedlist);
while(cc.hasNext()){
System.out.println(cc.next.toString());
}
}
}
the results is 1,3,5. How make the list iterate over all the elements in list first and then move to next collection item inside it?
Thanks.
This one works for me - it is not generalised to Collection but there are utility methods that can give you an iterator-iterator across up to three levels of Map. I am sure you could adapt it to collections in general.
public class NestedIterator<T> implements Iterator<T> {
// Outer iterator. Goes null when exhausted.
Iterator<Iterator<T>> i2 = null;
// Inner iterator. Goes null when exhausted.
Iterator<T> i1 = null;
// Next value.
T next = null;
// Takes a depth-2 iterator.
public NestedIterator(Iterator<Iterator<T>> i2) {
this.i2 = i2;
// Prime the pump.
if (i2 != null && i2.hasNext()) {
i1 = i2.next();
}
}
#Override
public boolean hasNext() {
// Is there one waiting?
if (next == null) {
// No!
// i1 will go null if it is exhausted.
if (i1 == null) {
// i1 is exhausted! Get a new one from i2.
if (i2 != null && i2.hasNext()) {
/// Get next.
i1 = i2.next();
// Set i2 null if exhausted.
if (!i2.hasNext()) {
// Exhausted.
i2 = null;
}
} else {
// Exhausted.
i2 = null;
}
}
// A null i1 now will mean all is over!
if (i1 != null) {
if (i1.hasNext()) {
// get next.
next = i1.next();
// Set i1 null if exhausted.
if (!i1.hasNext()) {
// Exhausted.
i1 = null;
}
} else {
// Exhausted.
i1 = null;
}
}
}
return next != null;
}
#Override
public T next() {
T n = next;
next = null;
return n;
}
#Override
public void remove() {
throw new UnsupportedOperationException("Not supported.");
}
// Iterating across Maps of Maps of Maps.
static <K1, K2, K3, V> Iterator<Iterator<Iterator<V>>> iiiV(Map<K1, Map<K2, Map<K3, V>>> mapMapMap) {
final Iterator<Map<K2, Map<K3, V>>> mmi = iV(mapMapMap);
return new Iterator<Iterator<Iterator<V>>>() {
#Override
public boolean hasNext() {
return mmi.hasNext();
}
#Override
public Iterator<Iterator<V>> next() {
return iiV(mmi.next());
}
#Override
public void remove() {
mmi.remove();
}
};
}
// Iterating across Maps of Maps.
static <K1, K2, V> Iterator<Iterator<V>> iiV(Map<K1, Map<K2, V>> mapMap) {
final Iterator<Map<K2, V>> mi = iV(mapMap);
return new Iterator<Iterator<V>>() {
#Override
public boolean hasNext() {
return mi.hasNext();
}
#Override
public Iterator<V> next() {
return iV(mi.next());
}
#Override
public void remove() {
mi.remove();
}
};
}
// Iterating across Map values.
static <K, V> Iterator<V> iV(final Map<K, V> map) {
return iV(map.entrySet().iterator());
}
// Iterating across Map.Entries.
static <K, V> Iterator<V> iV(final Iterator<Map.Entry<K, V>> mei) {
return new Iterator<V>() {
#Override
public boolean hasNext() {
return mei.hasNext();
}
#Override
public V next() {
return mei.next().getValue();
}
#Override
public void remove() {
mei.remove();
}
};
}
}

Create an ArrayList of unique values [duplicate]

This question already has answers here:
Get unique values from ArrayList in Java
(9 answers)
Closed 2 years ago.
I have an ArrayList with values taken from a file (many lines, this is just an extract):
20/03/2013 23:31:46 6870 6810 6800 6720 6860 6670 6700 6650 6750 6830 34864 34272
20/03/2013 23:31:46 6910 6780 6800 6720 6860 6680 6620 6690 6760 6790 35072 34496
Where the first two values per line are strings that contain data and are stored in a single element.
What I want to do is compare the string data elements and delete, for example, the second one and all the elements referred to in that line.
For now, I've used a for loop that compares the string every 13 elements (in order to compare only data strings).
My question: can I implement other better solutions?
This is my code:
import java.util.Scanner;
import java.util.List;
import java.util.ArrayList;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Main {
public static void main(String[] args) throws Exception{
//The input file
Scanner s = new Scanner(new File("prova.txt"));
//Saving each element of the input file in an arraylist
ArrayList<String> list = new ArrayList<String>();
while (s.hasNext()){
list.add(s.next());
}
s.close();
//Arraylist to save modified values
ArrayList<String> ds = new ArrayList<String>();
//
int i;
for(i=0; i<=list.size()-13; i=i+14){
//combining the first to values to obtain data
String str = list.get(i)+" "+list.get(i+1);
ds.add(str);
//add all the other values to arraylist ds
int j;
for(j=2; j<14; j++){
ds.add(list.get(i+j));
}
//comparing data values
int k;
for(k=0; k<=ds.size()-12; k=k+13){
ds.get(k); //first data string element
//Comparing with other strings and delete
//TODO
}
}
}
}
Try checking for duplicates with a .contains() method on the ArrayList, before adding a new element.
It would look something like this
if(!list.contains(data))
list.add(data);
That should prevent duplicates in the list, as well as not mess up the order of elements, like people seem to look for.
Create an Arraylist of unique values
You could use Set.toArray() method.
A collection that contains no duplicate elements. More formally, sets
contain no pair of elements e1 and e2 such that e1.equals(e2), and at
most one null element. As implied by its name, this interface models
the mathematical set abstraction.
http://docs.oracle.com/javase/6/docs/api/java/util/Set.html
HashSet hs = new HashSet();
hs.addAll(arrayList);
arrayList.clear();
arrayList.addAll(hs);
Pretty late to the party, but here's my two cents:
Use a LinkedHashSet
I assume what you need is a collection which:
disallows you to insert duplicates;
retains insertion order.
LinkedHashSet does this. The advantage over using an ArrayList is that LinkedHashSet has a complexity of O(1) for the contains operation, as opposed to ArrayList, which has O(n).
Of course, you need to implement your object's equals and hashCode methods properly.
//Saving each element of the input file in an arraylist
ArrayList<String> list = new ArrayList<String>();
while (s.hasNext()){
list.add(s.next());
}
//That's all you need
list = (ArrayList) list.stream().distinct().collect(Collectors.toList());
If you want to make a list with unique values from an existing list you can use
List myUniqueList = myList.stream().distinct().collect(Collectors.toList());
Use Set
...
Set<String> list = new HashSet<>();
while (s.hasNext()){
list.add(s.next());
}
...
You can easily do this with a Hashmap. You obviously have a key (which is the String data) and some values.
Loop on all your lines and add them to your Map.
Map<String, List<Integer>> map = new HashMap<>();
...
while (s.hasNext()){
String stringData = ...
List<Integer> values = ...
map.put(stringData,values);
}
Note that in this case, you will keep the last occurence of duplicate lines. If you prefer keeping the first occurence and removing the others, you can add a check with Map.containsKey(String stringData); before putting in the map.
You could use a Set. It is a collection which doesn't accept duplicates.
Solution #1: HashSet
A good solution to the immediate problem of reading a file into an ArrayList with a uniqueness constraint is to simply keep a HashSet of seen items. Before processing a line, we check that its key is not already in the set. If it isn't, we add the key to the set to mark it as finished, then add the line data to the result ArrayList.
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args)
throws FileNotFoundException, IOException {
String file = "prova.txt";
ArrayList<String[]> data = new ArrayList<>();
HashSet<String> seen = new HashSet<>();
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
for (String line; (line = br.readLine()) != null;) {
String[] split = line.split("\\s+");
String key = split[0] + " " + split[1];
if (!seen.contains(key)) {
data.add(Arrays.copyOfRange(split, 2, split.length));
seen.add(key);
}
}
}
for (String[] row : data) {
System.out.println(Arrays.toString(row));
}
}
}
Solution #2: LinkedHashMap/LinkedHashSet
Since we have key-value pairs in this particular dataset, we could roll everything into a LinkedHashMap<String, ArrayList<String>> (see docs for LinkedHashMap) which preserves ordering but can't be indexed into (use-case driven decision, but amounts to the same strategy as above. ArrayList<String> or String[] is arbitrary here--it could be any data value). Note that this version makes it easy to preserve the most recently seen key rather than the oldest (remove the !data.containsKey(key) test).
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args)
throws FileNotFoundException, IOException {
String file = "prova.txt";
LinkedHashMap<String, ArrayList<String>> data = new LinkedHashMap<>();
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
for (String line; (line = br.readLine()) != null;) {
String[] split = line.split("\\s+");
String key = split[0] + " " + split[1];
if (!data.containsKey(key)) {
ArrayList<String> val = new ArrayList<>();
String[] sub = Arrays.copyOfRange(split, 2, split.length);
Collections.addAll(val, sub);
data.put(key, val);
}
}
}
for (Map.Entry<String, ArrayList<String>> e : data.entrySet()) {
System.out.println(e.getKey() + " => " + e.getValue());
}
}
}
Solution #3: ArrayListSet
The above examples represent pretty narrow use cases. Here's a sketch for a general ArrayListSet class, which maintains the usual list behavior (add/set/remove etc) while preserving uniqueness.
Basically, the class is an abstraction of solution #1 in this post (HashSet combined with ArrayList), but with a slightly different flavor (the data itself is used to determine uniqueness rather than a key, but it's a truer "ArrayList" structure).
This class solves the problems of efficiency (ArrayList#contains is linear, so we should reject that solution except in trivial cases), lack of ordering (storing everything directly in a HashSet doesn't help us), lack of ArrayList operations (LinkedHashSet is otherwise the best solution but we can't index into it, so it's not a true replacement for an ArrayList).
Using a HashMap<E, index> instead of a HashSet would speed up remove(Object o) and indexOf(Object o) functions (but slow down sort). A linear remove(Object o) is the main drawback over a plain HashSet.
import java.util.*;
public class ArrayListSet<E> implements Iterable<E>, Set<E> {
private ArrayList<E> list;
private HashSet<E> set;
public ArrayListSet() {
list = new ArrayList<>();
set = new HashSet<>();
}
public boolean add(E e) {
return set.add(e) && list.add(e);
}
public boolean add(int i, E e) {
if (!set.add(e)) return false;
list.add(i, e);
return true;
}
public void clear() {
list.clear();
set.clear();
}
public boolean contains(Object o) {
return set.contains(o);
}
public E get(int i) {
return list.get(i);
}
public boolean isEmpty() {
return list.isEmpty();
}
public E remove(int i) {
E e = list.remove(i);
set.remove(e);
return e;
}
public boolean remove(Object o) {
if (set.remove(o)) {
list.remove(o);
return true;
}
return false;
}
public boolean set(int i, E e) {
if (set.contains(e)) return false;
set.add(e);
set.remove(list.set(i, e));
return true;
}
public int size() {
return list.size();
}
public void sort(Comparator<? super E> c) {
Collections.sort(list, c);
}
public Iterator<E> iterator() {
return list.iterator();
}
public boolean addAll(Collection<? extends E> c) {
int before = size();
for (E e : c) add(e);
return size() == before;
}
public boolean containsAll(Collection<?> c) {
return set.containsAll(c);
}
public boolean removeAll(Collection<?> c) {
return set.removeAll(c) && list.removeAll(c);
}
public boolean retainAll(Collection<?> c) {
return set.retainAll(c) && list.retainAll(c);
}
public Object[] toArray() {
return list.toArray();
}
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
}
Example usage:
public class ArrayListSetDriver {
public static void main(String[] args) {
ArrayListSet<String> fruit = new ArrayListSet<>();
fruit.add("apple");
fruit.add("banana");
fruit.add("kiwi");
fruit.add("strawberry");
fruit.add("apple");
fruit.add("strawberry");
for (String item : fruit) {
System.out.print(item + " "); // => apple banana kiwi strawberry
}
fruit.remove("kiwi");
fruit.remove(1);
fruit.add(0, "banana");
fruit.set(2, "cranberry");
fruit.set(0, "cranberry");
System.out.println();
for (int i = 0; i < fruit.size(); i++) {
System.out.print(fruit.get(i) + " "); // => banana apple cranberry
}
System.out.println();
}
}
Solution #4: ArrayListMap
This class solves a drawback of ArrayListSet which is that the data we want to store and its associated key may not be the same. This class provides a put method that enforces uniqueness on a different object than the data stored in the underlying ArrayList. This is just what we need to solve the original problem posed in this thread. This gives us the ordering and iteration of an ArrayList but fast lookups and uniqueness properties of a HashMap. The HashMap contains the unique values mapped to their index locations in the ArrayList, which enforces ordering and provides iteration.
This approach solves the scalability problems of using a HashSet in solution #1. That approach works fine for a quick file read, but without an abstraction, we'd have to handle all consistency operations by hand and pass around multiple raw data structures if we needed to enforce that contract across multiple functions and over time.
As with ArrayListSet, this can be considered a proof of concept rather than a full implementation.
import java.util.*;
public class ArrayListMap<K, V> implements Iterable<V>, Map<K, V> {
private ArrayList<V> list;
private HashMap<K, Integer> map;
public ArrayListMap() {
list = new ArrayList<>();
map = new HashMap<>();
}
public void clear() {
list.clear();
map.clear();
}
public boolean containsKey(Object key) {
return map.containsKey(key);
}
public boolean containsValue(Object value) {
return list.contains(value);
}
public V get(int i) {
return list.get(i);
}
public boolean isEmpty() {
return map.isEmpty();
}
public V get(Object key) {
return list.get(map.get(key));
}
public V put(K key, V value) {
if (map.containsKey(key)) {
int i = map.get(key);
V v = list.get(i);
list.set(i, value);
return v;
}
list.add(value);
map.put(key, list.size() - 1);
return null;
}
public V putIfAbsent(K key, V value) {
if (map.containsKey(key)) {
if (list.get(map.get(key)) == null) {
list.set(map.get(key), value);
return null;
}
return list.get(map.get(key));
}
return put(key, value);
}
public V remove(int i) {
V v = list.remove(i);
for (Map.Entry<K, Integer> entry : map.entrySet()) {
if (entry.getValue() == i) {
map.remove(entry.getKey());
break;
}
}
decrementMapIndices(i);
return v;
}
public V remove(Object key) {
if (map.containsKey(key)) {
int i = map.remove(key);
V v = list.get(i);
list.remove(i);
decrementMapIndices(i);
return v;
}
return null;
}
private void decrementMapIndices(int start) {
for (Map.Entry<K, Integer> entry : map.entrySet()) {
int i = entry.getValue();
if (i > start) {
map.put(entry.getKey(), i - 1);
}
}
}
public int size() {
return list.size();
}
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> es = new HashSet<>();
for (Map.Entry<K, Integer> entry : map.entrySet()) {
es.add(new AbstractMap.SimpleEntry<>(
entry.getKey(), list.get(entry.getValue())
));
}
return es;
}
public Set<K> keySet() {
return map.keySet();
}
public Collection<V> values() {
return list;
}
public Iterator<V> iterator() {
return list.iterator();
}
public Object[] toArray() {
return list.toArray();
}
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
}
Here's the class in action on the original problem:
import java.io.*;
public class Main {
public static void main(String[] args)
throws FileNotFoundException, IOException {
String file = "prova.txt";
ArrayListMap<String, String[]> data = new ArrayListMap<>();
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
for (String line; (line = br.readLine()) != null;) {
String[] split = line.split("\\s+");
String key = split[0] + " " + split[1];
String[] sub = Arrays.copyOfRange(split, 2, split.length);
data.putIfAbsent(key, sub);
}
}
for (Map.Entry<String, String[]> e : data.entrySet()) {
System.out.println(e.getKey() + " => " +
java.util.Arrays.toString(e.getValue()));
}
for (String[] a : data) {
System.out.println(java.util.Arrays.toString(a));
}
}
}
Just Override the boolean equals() method of custom object. Say you have an ArrayList with custom field f1, f2, ... override
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CustomObject)) return false;
CustomObject object = (CustomObject) o;
if (!f1.equals(object.dob)) return false;
if (!f2.equals(object.fullName)) return false;
...
return true;
}
and check using ArrayList instance's contains() method. That's it.
If you need unique values, you should use the implementation of the SET interface
You can read from file to map, where the key is the date and skip if the the whole row if the date is already in map
Map<String, List<String>> map = new HashMap<String, List<String>>();
int i = 0;
String lastData = null;
while (s.hasNext()) {
String str = s.next();
if (i % 13 == 0) {
if (map.containsKey(str)) {
//skip the whole row
lastData = null;
} else {
lastData = str;
map.put(lastData, new ArrayList<String>());
}
} else if (lastData != null) {
map.get(lastData).add(str);
}
i++;
}
I use helper class. Not sure it's good or bad
public class ListHelper<T> {
private final T[] t;
public ListHelper(T[] t) {
this.t = t;
}
public List<T> unique(List<T> list) {
Set<T> set = new HashSet<>(list);
return Arrays.asList(set.toArray(t));
}
}
Usage and test:
import static org.assertj.core.api.Assertions.assertThat;
public class ListHelperTest {
#Test
public void unique() {
List<String> s = Arrays.asList("abc", "cde", "dfg", "abc");
List<String> unique = new ListHelper<>(new String[0]).unique(s);
assertThat(unique).hasSize(3);
}
}
Or Java8 version:
public class ListHelper<T> {
public Function<List<T>, List<T>> unique() {
return l -> l.stream().distinct().collect(Collectors.toList());
}
}
public class ListHelperTest {
#Test
public void unique() {
List<String> s = Arrays.asList("abc", "cde", "dfg", "abc");
assertThat(new ListHelper<String>().unique().apply(s)).hasSize(3);
}
}

Make a unique list of objects Java

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.
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();
}
}
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);
}
}
}
Why you dont use a Set and later:
new ArrayList(set);
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.
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
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());
}
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 );
}
}

Categories

Resources