In java suppose I have 2 lists
List<Object1> list1
List<Object2> list2
object1.getName(); returns a String
object2.getName(); return a String
is there any way to compare the names and get a difference of the two list
those 2 objects are defined in the 3rd party library, and I can't override the equals and compareto methods
I am in favour of googles Guava or commons collections library
but the Sets.symmetricDifference(Set1, Set2) ask for 2 to be passed in,
even i juse Sets.newHashSet(lis1) and Sets.newHashSet(lis2) to create two sets
but still they have difference type of objects in the sets.
or in commons CollectionUtils.disjunction(lis1, list2) the lists still has to contain the same object type
without doing 2 expensive for loops, is there any other way?
First, we'll build two maps, one for each list, mapping names to objects. Then we iterate over the differences between the key sets, processing whichever kind of object had that name. The maps let us avoid scanning through the list looking for the object with that name. (In using Map rather than Multimap, I'm relying on the asker's comment on another answer that within each list, names are unique. If you're still using Java 7, replace the method reference with a Function implementation.)
Map<String, Object1> map1 = Maps.uniqueIndex(list1, Object1::getName);
Map<String, Object2> map2 = Maps.uniqueIndex(list2, Object1::getName);
for (String name : Sets.difference(map1.keySet(), map2.keySet()))
processObject1(map1.get(name));
for (String name : Sets.difference(map2.keySet(), map1.keySet()))
processObject2(map2.get(name));
If all you want to do is build lists or sets of the objects in exactly one list, processObject1 and processObject2 can just add the objects to collections.
uniqueIndex's iteration order is that of the input iterable, and difference returns a SetView with the same iteration order as its first argument, so you can process objects in the order they appeared in the input lists, if that order is relevant to your problem.
Java 8 streams provide basically the same functionality:
Map<String, Object1> map1 = list1.stream().collect(Collectors.toMap(Function.identity(), Object1::getName));
Map<String, Object2> map2 = list2.stream().collect(Collectors.toMap(Function.identity(), Object2::getName));
map1.keySet().stream().filter(n -> !map2.keySet().contains(n)).map(map1::get).forEachOrdered(o1 -> processObject1(o1));
map2.keySet().stream().filter(n -> !map1.keySet().contains(n)).map(map2::get).forEachOrdered(o2 -> processObject1(o2));
Again, you can replace the forEachOrdered call with collect(Collectors.toList()) if you just want to collect the objects.
First you will have to transfor your lists to String based lists:
private static final class FromObject1ToName implements Function<Object1, String> {
#Override
public String apply(Object1 input) {
return input.name;
}
}
The same transformation has to be done for Object2
Then transform the input list:
Collection<String> transformed = Collections2.transform(list1, new FromObject1ToName());
//list1 is a List on Object1
Then create the multiset:
Multiset<String> multiset1 = HashMultiset.create();
multiset1.addAll(transformed);
Then simply do :
Multisets.difference(multiset1, multiset2) // multiset1 is from Object1 and multiset2 is from Object2
This will give you the difference and how many times it differes
If you need to know just the differences, then do the same transform, then load the Collection of strings in a Set adn then do Sets.symmetricDifference
Using Guava, try this. It works for me ->
Multisets.difference(multiset1,multiset2);
How to convert ArrayList to Multiset.
List x = new ArrayList();
x.add(3);.....
Multiset newX = HashMultiset.create();
newX.addAll(x);
Related
I created a Comparator for a specific class. With this, I could easily sort lists (respectively streams) of this class.
In my case, I need to know if there are pairs of objects that are "equal" in the sense of the Comparator. Is there a easy way to get an list of "distinct" objects, or to know if there are "equal" objects?
The Comparator counts the number of null values in fields. So I want to know or filter objects with same number of null values.
Use StreamEx to collect pairs from a stream and be able to pass a BiPredicate
Then pass a bipredicate to filter your data :
Comparator<Object> yourComparator = ...;
BiPredicate<Object,Object> bip = (d1,d2) -> comp.compare(d1,d2) == 0;
List<Object> data = ...;
List<Object> result = data.stream().pairMap(bip).collect(Collectors.toList());
I have a LinkedHashMap which maps strings to string arrays.
The keys have the format of something like this: "xxx (yyy(0.123))"
Basically, I want to be able to sort the entry set in such a way that it sorts it by the decimal part, and not the beginning of the string. What I have done so far is converting the entry set to an ArrayList so that I can try calling Arrays.sort on it, but obviously that's going to just sort by the beginning of the string.
What I'm currently thinking is that I would have to go through this array, convert each key in the pair to a custom class with a comparator that compares the way I want it to (with the regular expression .*\((.*)\)\) to find the decimal). However, that sounds like a bunch of unnecessary overhead, so I was wondering if there was a simpler way. Thanks in advance.
First, you cannot "sort" a LinkedHashMap. LinkedHashMap maintain the iteration order based on the order of insertion.
If you means creating another LinkedHashMap by inserting using values from the original map, with order based on sorted order: You need to be aware of any new entries added after your initial construction will be unsorted. So you may want to create an unmodifiable Map.
For the Comparator implementation, you do not need to make it to your custom class. Just create a comparator that do the comparison is good enough.
Like this:
(haven't compiled, just to show you the idea)
// assume the key is in format of "ABCDE,12345", and you want to sort by the numeric part:
Map<String, Foo> oldMap = ....; // assume you populated something in it
Map<String, Foo> sortedMap
= new TreeMap((a,b) -> {
// here I use split(), you can use regex
int aNum = Integer.valueOf(a.split(",")[1]);
int bNum = Integer.valueOf(b.split(",")[1]);
if (aNum != bNum ) {
return aNum - bNum;
} else {
return a.compareTo(b);
});
sortedMap.addAll(oldMap);
// now sortedMap contains your entries in sorted order.
// you may construct a new LinkedHashMap with it or do whatever you want
Your solution sounds fine.
If you run into performance issues, you could look buffering the decimal value by replacing your strings with an object that contains the string and the decimal value. Then it does not need to be recalculated multiple times during the sort.
There are trade offs for the buffered solution as above and figuring out which technique is optimal will really depend on your entire solution.
Is there a reason you need to use LinkedHashMap? The javadoc specifically states
This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order)
TreeMap seems a better fit for what you're trying to achieve, which allows you to provide a Comparator at construction. Using Java 8, this could be achieved with something like:
private static final String DOUBLE_REGEX = "(?<value>\\d+(?:\\.\\d+)?)";
private static final String FIND_REGEX = "[^\\d]*\\(" + DOUBLE_REGEX + "\\)[^\\d]*";
private static final Pattern FIND_PATTERN = Pattern.compile(FIND_REGEX);
private static final Comparator<String> COMPARATOR = Comparator.comparingDouble(
s -> {
final Matcher matcher = FIND_PATTERN.matcher(s);
if (!matcher.find()) {
throw new IllegalArgumentException("Cannot compare key: " + s);
}
return Double.parseDouble(matcher.group("value"));
});
private final Map<String, List<String>> map = new TreeMap<>(COMPARATOR);
Edit: If it has to be a LinkedHashMap (yours), you can always:
map.putAll(yours);
yours.clear();
yours.putAll(map);
I have several very large ArrayLists of objects which i would like to find their Symmetric Differences ( or disjunction). To do so i have decided to use Sets and their "contain()" methods. However, this method uses the equals() method to evaluate said objects.
Problem is, i cannot make any changes in my class. So, i cannot override any method. (my code is just a small part of a very bigger project)
so this leaves me here, is there any other alternative to altering the classes themselves ? or any other way that would not require me to make any changes to my classes ?
I've recently found out about this so I have an alternate solution (only for Java 8):
// Being T the class of the objects in the list
ArrayList<T> list1 = ...;
ArrayList<T> list2 = ...;
// A function to compare two elements
BiFunction<T, T, Boolean> funcEquals = (a,b) -> yourEquals(a,b);
// A function that given a List returns a predicate that states if an element is on that list
Function<List<T>, Predicate<T>> notIn = (s) -> (e) -> s.stream().filter((y) -> funcEquals.apply(e, y)).count() == 0;
// Get the elements in list1 that are not in list2
Stream<String> list1Filtered = list1.stream().filter(notIn.apply(list2));
// Get the elements in list2 that are not in list1
Stream<String> list2Filtered = list2.stream().filter(notIn.apply(list1));
/*
If you have more than two lists, comparisons can be concatenated:
Stream<String> list1Filtered = list1.stream().filter(notIn.apply(list2)).filter(notIn.apply(list3));
Stream<String> list2Filtered = list2.stream().filter(notIn.apply(list1)).filter(notIn.apply(list3));
Stream<String> list3Filtered = list3.stream().filter(notIn.apply(list1)).filter(notIn.apply(list2));
*/
// Add them all together
ArrayList<T> result = new ArrayList<T>();
result.addAll(list1Filtered.collect(Collectors.toList()));
result.addAll(list2Filtered.collect(Collectors.toList()));
It's a little confusing at first, but you don't have to create any more classes.
I ended up using a wrapper class, originally suggested by "Oliver Charlesworth" and other people in the comments.
I need to do the task of sorting HashMap keys which will be String always and store them in an ArrayList. I have written below piece of code
public static List<String> getSortedListByKeys(HashMap<String,String> keysDictionary)
{
List<String> sortedKeysList = null;
SortedSet<String> sortedKeysSet = null;
if(keysDictionary == null || keysDictionary.size() == 0){
return null;
}
sortedKeysSet = new TreeSet<>(keysDictionary.keySet());
sortedKeysList = new ArrayList<>(sortedKeysSet);
return sortedKeysList;
}
I have run it and it is working fine.
Just want to know if there is any better way to achieve the same
Since you want to sort the Strings based on natural ordering itself, you directly create a list and use Collections.sort() on it.
sortedKeysList = new ArrayList<>(keysDictionary.keySet());
Collections.sort(sortedKeysList);
This way you can avoid using a SortedSet as an intermediate step.
I think you should definitely consider to use a TreeMap, insertion and retrieval will be be a little bit slower (O(log(N) instead of O(1)) but you will have key already sorted in the right order.
The Java doc of TreeMap : http://docs.oracle.com/javase/6/docs/api/java/util/TreeMap.html
With Java 8 you could do it quite easily with the following:
Map<String, String> keysDictionary = new HashMap<>();
List<String> sortedKeys = keysDictionary.keySet().stream()
.sorted()
.collect(Collectors.toList());
With this you:
Obtain the key set, being a Set<String>.
Wrap it to a Stream<String>.
Sort it, via sorted().
Collect it into a List<Stream> supplied by Collectors.toList(), this will be an ArrayList<String> as no additional constraints have been specified.
It might be more burden than it would be in Java 7, but I would say it is about equal once you know the Streams API, it gets more fun when you put additional constraints. And then the real profit shows.
I need to create a List that records two columns {int, String}. I think ArrayList is what I need but I cant get my head around it. I pulled the Strings from a database and the int is the index value which I need to identify the strings position for later.
List<List<String>> strArray = ArrayList<List<String>>;
then could I do something like strArray.add().add() for each row I pull from the database?
I think you should use a HashMap with int as key and String as value if your int values are going to be unique.
Map<Integer,String> myMap = new HashMap<Integer,String>();
myMap.put(1,"ABC");
Note that as Map is a collections and java collections do not store primitive like int, they store objects so you have to use Integer wrapper class for your int values.
Refer this link Why can Java Collections not directly store Primitives types?
Another approach would be to make a custom object:
Class CustomObject {
int value1;
String value2;
CustomObject(int v1, String v2) {
value1 = v1;
value2 = v2;
}
}
And then use it:
List<CustomObject> myList = new ArrayList<CustomObject>();
CustomObject o1 = new CustomObject(1, "one");
myList.add(o1);
// etc.
If the int values are unique and you want to consider them keys, then a Map would work as others have suggested.
If you need just two values you can use native Pair class
List<Pair> mPairs = new ArrayList<Pair>();
Pair pair = new Pair(123,"your string");
mPairs.add(pair);
This will be a good decision if you int values are not unique and so you can not use HashMap
If your IDs are not unique, you still can use Map :
Map<Integer, String> map = new IdentityHashMap<Integer, String>();
map.put(new Integer(1), "string");
IdentityHashMap - use native hashCode implemetation for each OBJECT, so you don't need unique IDs, but you MUST create ALL Integers via operator 'new', and don't use autoboxing, because there is some cache mechanism.
Also there is JVM parameter, which controlls cache size '-XX:AutoBoxCacheMax='.
But using this parameter you can't disable cache, if you set size to the zero, then cache will ignore it and use default: [-128; 127].
This parameter is only for Integers, there is no such kind of parameter for Long.
UPDATE
Also for non unique keys you could use some sort of multimap:
Map> map
And store in it your values with nonunique keys:
map.put(1, new ArrayList<String>());
map.get(1).add("value1");
map.get(1).add("value2");
You can use HashMap for that for example.
Also you can find MultiMap implementation in google-collections: 'guava'.
I think you may wrap the int and string in a class, then put the class objects in List.
Map is an object that maps keys to values. A map cannot contain duplicate keys; each key can map to at most one value.
I think it would be better if you use Map<Integer,String> where key(Integer) would be the index which will pointing to String value.
Map<Integer,String> map = new HashMap<Integer,String>();
map.put(1,"str1");
map.put(2,"str2");
...