Related
Does a java map implementation exist where the keys are known, however the values should only be computed on the first access as calculating the values is expensive.
The following demonstrates how I would like it to work.
someMap.keySet(); // Returns all keys but no values are computed.
someMap.get(key); // Returns the value for key computing it if needed.
The reason for this is I have something which holds a bunch of data and this Object returns the data as a Map<String, String> this is computationally heavy to compute because computing the values is expensive, the keys are however cheap to compute.
The Map must maintain its type so I can't return a Map<String, Supplier<String>>. The returned Map may be returned as read only.
The map itself could be created by passing in both a Set<String> defining the keys and a Function<String, String> which given a key returns its value.
One solution could be to have a Map that takes a Set of keys and a Function which given a key can compute the value.
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
/**
* Create a Map where we already know the keys but computing the values is expensive and so is delayed as
* much as possible.
*
*/
#AllArgsConstructor
public class MapWithValuesProvidedByFunction implements Map<String, String> {
/**
* All keys that are defined.
*/
private Set<String> keys;
/**
* A function which maps a key to its value.
*/
private Function<String, String> mappingFunction;
/**
* Holds all keys and values we have already computed.
*/
private final Map<String, String> computedValues = new HashMap<>();
#Override
public int size() {
return keys.size();
}
#Override
public boolean isEmpty() {
return keys.isEmpty();
}
#Override
public boolean containsKey(Object key) {
return keys.contains(key);
}
#Override
public boolean containsValue(Object value) {
if(computedValues.size() == keys.size()) return computedValues.containsValue(value);
for(String k : keys) {
String v = get(k);
if(v == value) return true;
if(v != null && v.equals(value)) return true;
}
return false;
}
#Override
public String get(Object key) {
if(keys.contains(key)) {
return computedValues.computeIfAbsent(key.toString(), mappingFunction);
}
return null;
}
#Override
public String put(String key, String value) {
throw new UnsupportedOperationException("Not modifiable");
}
#Override
public String remove(Object key) {
throw new UnsupportedOperationException("Not modifiable");
}
#Override
public void putAll(Map<? extends String, ? extends String> m) {
throw new UnsupportedOperationException("Not modifiable");
}
#Override
public void clear() {
throw new UnsupportedOperationException("Not modifiable");
}
#Override
public Set<String> keySet() {
return Collections.unmodifiableSet(keys);
}
#Override
public Collection<String> values() {
return keys.stream().map(this::get).collect(Collectors.toList());
}
#Override
public Set<java.util.Map.Entry<String, String>> entrySet() {
Set<Entry<String, String>> set = new HashSet<>();
for(String s : keys) {
set.add(new MyEntry(s, this::get));
}
return set;
}
#AllArgsConstructor
#EqualsAndHashCode
public class MyEntry implements Entry<String, String> {
private String key;
private Function<String, String> valueSupplier;
#Override
public String getKey() {
return key;
}
#Override
public String getValue() {
return valueSupplier.apply(key);
}
#Override
public String setValue(String value) {
throw new UnsupportedOperationException("Not modifiable");
}
}
}
An example of this being used might be:
Map<String, String> map = new MapWithValuesProvidedByFunction(
Set.of("a", "b", "c"), // The known keys
k -> "Slow to compute function"); // The function to make the values
Changing this to be generic should be easy enough.
I suspect a better solution exists, however this might be good enough for someone else.
You could do something like this. The map has a key and a Fnc class which holds a function and the argument to the function.
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class MapDemo {
public static Map<String, Object> mymap = new HashMap<>();
public static void main(String[] args) {
MapDemo thisClass = new MapDemo();
// populate the functions
mymap.put("v1", new Fnc<String>(String::toUpperCase));
mymap.put("10Fact", new Fnc<Integer>((Integer a) -> {
int f = 1;
int k = a;
while (k-- > 1) {
f *= k;
}
return f + "";
}));
mymap.put("expensive",
new Fnc<Integer>(thisClass::expensiveFunction));
// access them - first time does the computation
System.out.println(getValue("expensive", 1000));
System.out.println(getValue("10Fact", 10));
System.out.println(getValue("v1", "hello"));
// second time does not.
System.out.println(getValue("expensive"));
System.out.println(getValue("10Fact"));
System.out.println(getValue("v1"));
}
public String expensiveFunction(int q) {
return q * 100 + ""; // example
}
static class Fnc<T> {
Function<T, String> fnc;
public Fnc(Function<T,String> fnc) {
this.fnc = fnc;
}
}
public <T> void addFunction(String key,
Function<String, T> fnc) {
mymap.put(key, fnc);
}
public static String getValue(String key) {
Object ret = mymap.get(key);
if (ret instanceof Fnc) {
return null;
}
return (String)mymap.get(key);
}
public static <T> String getValue(String key, T arg) {
Object ret = mymap.get(key);
if (ret instanceof Fnc) {
System.out.println("Calculating...");
ret = ((Fnc)ret).fnc.apply(arg);
mymap.put(key, ret);
}
return (String) ret;
}
}
First time thru, the function is called and the value is computed, stored, and returned. Subsequent calls return the stored value.
Note that the value replaces the computing function.
How I can use Multimap using java which which each time print different value of each key. Example:
L has values 000,001,10
I has values 101, 100,00
I need the output as follow:
ALI
first row contains first value of each key 001 000 101 second row contains second value of each key 110 001 100 third row contains third value of each key 11 10 00
This code part:
public static Multimap<String, String> Reading() throws IOException {
Multimap<String, String> myMultimap = ArrayListMultimap.create();
FileInputStream file = new FileInputStream("BBBB.txt");
InputStreamReader fr = new InputStreamReader(file);
BufferedReader br = new BufferedReader(fr);
String line = "";
while ((line = br.readLine()) != null) {
String[] columns= line.split(" ");
myMultimap.put(columns[1],columns[0]);
}
The output shows each key with values
Ali
[011,110 ,11][ 000,001,10][101, 100,00]
You should realize you'll have to keep a state with your data: for each key, the data structure must remember where it stands in the iteration of the values.
Therefore you must expand the Map contract. Therefor you must èxtendtheMap` functionality.
I propose the following (re-uses HashMap and List as a Wrapper class):
package samplingmultimap;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class SamplingMultiMap<K,V> implements Map<K, V>{
private final Map<K, SamplingEntry> contents = new HashMap<>();
/** Internal class holds data and keeps a cursor */
private class SamplingEntry {
private final List<V> data = new ArrayList<>();
private int cursor;
public void add(V value){
data.add(value);
}
public V getNextData() {
if(cursor < data.size()){
return data.get(cursor++); // increment the cursor
} else {
return null; // You may want to re-browse the list, if so do cursor = 0 and return the first result
}
}
}
#Override
public void clear() {
contents.clear();
}
#Override
public boolean containsKey(Object key) {
return contents.containsKey(key);
}
#Override
public boolean containsValue(Object value) {
for(SamplingEntry entry: contents.values()){
if(entry.data.contains(value)){
return true;
}
}
return false;
}
#Override
public Set<Entry<K, V>> entrySet() {
Set<Entry<K, V>> set = new HashSet<>();
for(Entry<K, SamplingEntry> samplingEntry: contents.entrySet()){
for(V value : samplingEntry.getValue().data){
Entry<K, V> singleEntry = new AbstractMap.SimpleEntry<K, V>(samplingEntry.getKey(), value);
set.add(singleEntry );
}
}
return set;
}
#Override
public V get(Object key) {
SamplingEntry entry = contents.get(key);
if(entry != null){
return entry.getNextData();
} else {
return null;
}
}
#Override
public boolean isEmpty() {
return contents.isEmpty();
}
#Override
public Set<K> keySet() {
return contents.keySet();
}
#Override
public V put(K key, V value) {
SamplingEntry existingEntry = contents.get(key);
if(existingEntry == null){
existingEntry = new SamplingEntry();
contents.put(key, existingEntry);
}
existingEntry.add(value);
return value;
}
#Override
public void putAll(Map<? extends K, ? extends V> m) {
for(Entry<? extends K, ? extends V> e: m.entrySet()){
put(e.getKey(), e.getValue());
}
}
#Override
public V remove(Object key) {
SamplingEntry oldValue = contents.remove(key);
if(oldValue != null){
return oldValue.getNextData();
} else {
return null;
}
}
#Override
public int size() {
int total = 0;
for(SamplingEntry v:contents.values()){
total += v.data.size();
}
return total;
}
#Override
public Collection<V> values() {
List<V> result = new ArrayList<>();
for(SamplingEntry v:contents.values()){
result.addAll(v.data);
}
return result;
}
}
Example usage for your inputs:
public static void main(String[] argc){
// Create and populate the Map
SamplingMultiMap<String, String> map = new SamplingMultiMap<>();
map.put("A", "011");
map.put("A", "110");
map.put("A", "11");
map.put("L", "000");
map.put("L", "001");
map.put("L", "10");
map.put("I", "101");
map.put("I", "100");
map.put("I", "00");
// Get elements one by one
System.out.println(map.get("A")); // returns 011
System.out.println(map.get("A")); // returns 110
System.out.println(map.get("A")); // returns 11
System.out.println(map.get("A")); // returns null (but you may wish to rewind?)
// Order of access is unimportant, state is confined to the Key
System.out.println(map.get("L")); // returns 000
System.out.println(map.get("I")); // returns 101
System.out.println(map.get("L")); // returns 001
System.out.println(map.get("I")); // returns 100
System.out.println(map.get("I")); // returns 00
System.out.println(map.get("L")); // returns 10
}
Edit: To answer how to completely decode a String into a list of symbols, just extends the Mapp further like so:
public class MultiDecoder extends SamplingMultiMap<Character, String> {
public List<String> decode(String toDecode) {
return toDecode.chars().mapToObj(c -> (char) c).map(c -> get(c)).collect(Collectors.toList());
}
}
This decoder is used like this (remeber it inherits SamplingMultiMap so t has to be populated with the encoding entries):
public static void main(String[] argc) {
// Create and populate the decoder with all the encodings
MultiDecoder decoder = new MultiDecoder();
decoder.put('A', "011");
decoder.put('A', "110");
decoder.put('A', "11");
decoder.put('L', "000");
decoder.put('L', "001");
decoder.put('L', "10");
decoder.put('I', "101");
decoder.put('I', "100"); // Only 2 entries for 'I'
// Decode an entire String:
System.out.println(decoder.decode("ALI")); // returns ["011", "000", "101"]
System.out.println(decoder.decode("ALI")); // returns ["110", "001", "100"]
System.out.println(decoder.decode("ALI")); // returns [ "11", "10", "101"] // the 'I' encoding loops around independently
System.out.println(decoder.decode("ALI")); // returns ["011", "110", "100"] // The 'A' and 'L' encodings loop now also around
}
This question already has answers here:
Sort a Map<Key, Value> by values
(64 answers)
Closed 6 years ago.
I have a TreeMap and a working by key Comperator:
Map<String, Long> movieReviewsTreeMap = new TreeMap<String, Long>(new MyComperator());
class MyComperator implements Comparator<String>{
#Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
}
I'm trying to modify the Comparator in order to compare TreeMap entries.
My goal is to sort TreeMap by value (highest to lowest), and by key in case two values are equal.
Thanks.
You can sort only keys in java.util.TreeMap but you can extend it and override methods where comparator is used to implement some addiditional logic.
You can try some what likewise, to full fill you requirement..
import java.util.*;
import java.util.Map.Entry;
public class TestCollection13{
public static void main(String args[]){
Map<MyComperator, Long> movieReviewsTreeMap = new TreeMap<MyComperator, Long>(new MyComperator());
Long l1 = new Long(5);
movieReviewsTreeMap.put(new MyComperator("a", l1),l1);
Long l2 = new Long(2);
movieReviewsTreeMap.put(new MyComperator("a", l2),l2);
Long l3 = new Long(6);
movieReviewsTreeMap.put(new MyComperator("a", l3),l3);
Long l4 = new Long(0);
movieReviewsTreeMap.put(new MyComperator("a", l4),l4);
for(Entry<MyComperator, Long> entry : movieReviewsTreeMap.entrySet()){
System.out.println("Key : " + entry.getKey().getKey() + " , value :" + entry.getValue());
}
}
}
class MyComperator implements Comparator<MyComperator>{
String key;
Long value;
MyComperator(){
}
MyComperator(String key, Long value){
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Long getValue() {
return value;
}
public void setValue(Long value) {
this.value = value;
}
#Override
public int hashCode() {
return this.key.length();
}
#Override
public boolean equals(Object obj) {
MyComperator mc = (MyComperator)obj;
if(mc.getKey().equals(this.getKey()) && mc.getValue().equals(this.getValue())){
return false;
}
// TODO Auto-generated method stub
return super.equals(obj);
}
#Override
public int compare(MyComperator o1, MyComperator o2) {
if(o1.getKey().equals(o2.getKey())){
return o1.getValue().compareTo(o2.getValue());
}
return 0;
}
}
Answer :
Key : a , value :0
Key : a , value :2
Key : a , value :5
Key : a , value :6
I have a distributed map and I want to find the lowest or highest key (an object implementing compareable). What is the most efficient way to get those keys? I mean something like every node provides his lowest key and in the end the lowest key is the lowest of every node.
So I think:
MyObj max = Collections.max(map.keySet());
is not the most efficient way. And if I want to use
new DistributedTask<>(new Max(input), key);
I would need to now the key and therefore fetch all Keys over wire. I think in that case I could do Collections.max(map.keySet()); as well.
Hmm ... any ideas?
You could use EntryProcessor.executeOnEntries - with a stateful EntryProcessor - and then let the it do all the work for you; have each key map to a sentinel MIN and MAX enum if they are the min and max.
If you have some idea of the bounds, you could attach a filter Predicate as well to speed it up that way, too.
This map reduce solution seems to have a lot of overhead but it is the best way I could get the job done. Any better ideas are still welcome.
public static void main(String[] args) throws ExecutionException, InterruptedException {
IMap<String, Integer> map = instance.getMap("test");
JobTracker jobTracker = instance.getJobTracker( "default" );
KeyValueSource<String, Integer> source = KeyValueSource.fromMap( map );
Job<String, Integer> job = jobTracker.newJob(source);
JobCompletableFuture<Map<String, String>> future = job
.mapper(new MaxMapper())
.reducer(new MaxReducerFactory())
.submit();
System.out.println("mr max: " + future.get());
}
public static class MaxMapper implements Mapper<String, Integer, String, String> {
private volatile String max = null;
#Override
public void map(String s, Integer integer, Context<String, String> ctx) {
if (max == null || s.compareTo(max)>0) {
max = s;
ctx.emit("max", max);
}
}
}
public static class MaxReducerFactory implements ReducerFactory<String,String,String> {
#Override
public Reducer<String, String> newReducer(String s) {
return new MaxReducer();
}
private class MaxReducer extends Reducer<String, String> {
private volatile String max = null;
#Override
public void reduce(String s) {
if (max == null || s.compareTo(max)>0) max = s;
}
#Override
public String finalizeReduce() {
return max; // == null ? "" : max;
}
}
}
Mapper:
import com.hazelcast.mapreduce.Context;
import com.hazelcast.mapreduce.Mapper;
import stock.Stock;
public class MinMaxMapper implements Mapper<String, Stock, String, Double> {
static final String MIN = "min";
static final String MAX = "max";
#Override
public void map(String key, Stock value, Context<String, Double> context) {
context.emit(MIN, value.getPrice());
context.emit(MAX, value.getPrice());
}
}
Combiner:
import com.hazelcast.mapreduce.Combiner;
import com.hazelcast.mapreduce.CombinerFactory;
public class MinMaxCombinerFactory implements CombinerFactory<String, Double, Double> {
#Override
public Combiner<Double, Double> newCombiner(String key) {
return new MinMaxCombiner(MinMaxMapper.MAX.equals(key) ? true : false);
}
private static class MinMaxCombiner extends Combiner<Double, Double> {
private final boolean maxCombiner;
private double value;
private MinMaxCombiner(boolean maxCombiner) {
this.maxCombiner = maxCombiner;
this.value = maxCombiner ? -Double.MAX_VALUE : Double.MAX_VALUE;
}
#Override
public void combine(Double value) {
if (maxCombiner) {
this.value = Math.max(value, this.value);
} else {
this.value = Math.min(value, this.value);
}
}
#Override
public Double finalizeChunk() {
return value;
}
#Override
public void reset() {
this.value = maxCombiner ? -Double.MAX_VALUE : Double.MAX_VALUE;
}
}
}
Reducer:
import com.hazelcast.mapreduce.Reducer;
import com.hazelcast.mapreduce.ReducerFactory;
public class MinMaxReducerFactory implements ReducerFactory<String, Double, Double> {
#Override
public Reducer<Double, Double> newReducer(String key) {
return new MinMaxReducer(MinMaxMapper.MAX.equals(key) ? true : false);
}
private static class MinMaxReducer extends Reducer<Double, Double> {
private final boolean maxReducer;
private volatile double value;
private MinMaxReducer(boolean maxReducer) {
this.maxReducer = maxReducer;
this.value = maxReducer ? -Double.MAX_VALUE : Double.MAX_VALUE;
}
#Override
public void reduce(Double value) {
if (maxReducer) {
this.value = Math.max(value, this.value);
} else {
this.value = Math.min(value, this.value);
}
}
#Override
public Double finalizeReduce() {
return value;
}
}
}
Returns two elements map with min and max:
ICompletableFuture<Map<String, Double>> future =
job.mapper(new MinMaxMapper())
.combiner(new MinMaxCombinerFactory())
.reducer(new MinMaxReducerFactory())
.submit();
Map<String, Double> result = future.get();
Why don't you create an ordered index? Although I'm not quite sure if it currently is possible to find a maximum value using a predicate and once found, abort the evaluation of the predicate.
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);
}
}