public final Comparator<String> ID_IGN_CASE_COMP = new Comparator<String>() {
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
};
private Map< String, Animal > _animals = new TreeMap< String, Animal >(ID_IGN_CASE_COMP);
My problem is, how to use method get(id) ignoring the given comparator. I want the map to be order by Case Insensitive but, I want it to be case sensitive when I fetch the values by a given key.
I think the answer is easy. Implement your own comparator that does a case insensitive sort but does NOT return 0 for "A" and "a"... sort them too.
The issue is that your comparator returns 0 for the compare( "A", "a" ) case which means it is the same key as far as the map is concerned.
Use a comparator like:
public final Comparator<String> ID_IGN_CASE_COMP = new Comparator<String>() {
public int compare(String s1, String s2) {
int result = s1.compareToIgnoreCase(s2);
if( result == 0 )
result = s1.compareTo(s2);
return result;
}
};
Then all keys will go in regardless of case and "a" and "A" will still be sorted together.
In other words, get("a") will give you a different value from get("A")... and they will both show up in keySet() iterators. They will just be sorted together.
In a TreeMap, adding two keys a and b (in that order) so that compare(a, b) returns 0 will result in that the latest added entry (b) will overwrite the first one (a).
In your case, this means that there will never be any use for case insensitive get(id).
quoting http://java.sun.com/javase/6/docs/api/java/util/TreeMap.html
Note that the ordering maintained by a sorted map (whether or not an explicit comparator is provided) must be consistent with equals if this sorted map is to correctly implement the Map interface. (See Comparable or Comparator for a precise definition of consistent with equals.) This is so because the Map interface is defined in terms of the equals operation, but a map performs all key comparisons using its compareTo (or compare) method, so two keys that are deemed equal by this method are, from the standpoint of the sorted map, equal. The behavior of a sorted map is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Map interface.
This is probably not what you want.
If the map is comparably small and you don't need to fetch the sorted entries very many times, a solution is to use a HashMap (or a TreeMap without explicitly setting the comparator), and sort the entries case-insensitively when you need them ordered.
You'll have to use two separate TreeMaps for that, with the same contents but different comparators.
maybe it'll do the job:
new Comparator<String>(){
public int compare(String s1, String s2)
{
String s1n = s1.toLowerCase();
String s2n = s2.toLowerCase();
if(s1n.equals(s2n))
{
return s1.compareTo(s2);
}
return s1n.compareTo(s2n);
}
};
}
you need a multimap: each entry of this multimap keeps the case insensitive keys and aanother map with the original keys as value.
There are many freely usable implementations of multimaps such as Common Collections, Google Collections, etc
In addition to all the other answers and agreeing, that it is impossible to have a single TreeMap structure with different comparators:
From your question I understand that you have two requirements: the data model shall be case sensitive (you want the case sensitive values when you use get()), the presenter shall be case insensitive (you want an case sensitive ordering, presentation is just an assumption).
Let's assume, we populate the Map with the mappings (aa,obj1), (aA,obj2), (Aa,obj3), (AA,obj4). The iterator will provides the values in the order: (obj4, obj3, obj2, obj1)(*). Now which order do you expect if the map was ordered case-insensitive? All four keys would be equal and the order undefined. Or are you looking for a solution that would resolve the collection {obj1, obj2, obj3, obj4} for the key 'AA'? But that's a different approach.
SO encourages the community to be honest: therefore my advice at this point is to look at your requirement again :)
(*) not tested, assumed that 'A' < 'a' = true.
Use floorEntry and then higherEntry in a loop to find the entries case-insensitively; stop when you find the exact key match.
Related
I am trying to create a TreeSet to sort the strings which are inserted to be in an ascending order. I am using below code for entering values in TreeSet.
TreeSet<String> ts = new TreeSet<String>();
ts.add("#Test0");
ts.add("#Test1");
ts.add("#Test2");
ts.add("#Test3");
ts.add("#Test10");
ts.add("#Test4");
System.out.println("Tree set :: "+ts);
Output:
Tree set :: [#Test0, #Test1, #Test10, #Test2, #Test3, #Test4]
You've used the no-args TreeSet constructor. This means TreeSet will order its elements based on natural order. It's the way the objects compare themselves: It means the things you add must be of a type that implements Comparable<Self>. String does that: The String class is defined to implement Comparable<String>. However, the way strings compare themselves is lexicographically. 10 comes before 2 for the same reason that aa comes before b.
You have two routes available to fix this:
Don't put strings in there but some other object that implements Comparable and does it right. Perhaps a class Thingie {String name; int idx;}.
Pass a Comparator as first and only argument to your TreeSet class. Write code that determines that #Test10 comes before #Test2. Then, TreeSet uses this comparator to determine ordering and won't use the one built into strings.
Specify the Comparator to sort on the number part only. This removes all but the number portion, converts that to an integer and sorts on that.
TreeSet<String> ts = new TreeSet<String>(Comparator.comparing(
s -> Integer.valueOf(s.replace("#Test", ""))));
ts.add("#Test0");
ts.add("#Test1");
ts.add("#Test2");
ts.add("#Test3");
ts.add("#Test10");
ts.add("#Test4");
System.out.println(ts);
prints
[#Test0, #Test1, #Test2, #Test3, #Test4, #Test10]
This works for the shown example. You may need to modify it somewhat for more varied data. But it demonstrates the idea.
#Test10 comes before #Test2 because 1 comes before 2. That's how the default ordering of String works (String implements the interface Comparable to do this sorting).
To solve your issue you need to provide a custom Comparator to the TreeSet, and do the comparison by parsing the integer within the string:
TreeSet<String> ts = new TreeSet<String>(new Comparator<String>() {
#Override
public int compare(String s1, String s2) {
return Integer.parseInt(s1.substring(5)) - Integer.parseInt(s2.substring(5));
}
});
The comparator can be constructed using the static convenience method:
TreeSet<String> ts = new TreeSet<>(Comparator.comparing(s -> Integer.parseInt(s.substring(5))));
As #Jems noted in the comment, strings are sorted lexichographically, so "#Test10" will come before "#Test2". If could however, supply a custom Comparator to define the order you need. E.g., if you know all the strings will have the form of "#Test" followed by a number, you could extract this number and sort accordingly:
TreeSet<String> ts =
new TreeSet<>(Comparator.comparingInt(s -> Integer.parseInt(s.substring(5))));
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 know that TreeSet in java automatically sort its elements in ascending order an it guarantees the order.
for example, If i have an array of Date object in random and I copy it to TreeSet then it will be added in TreeSet in sorted way.
but suppose instead of a simple Date object, I have an ArrayList of HashMap<String,Object>, in the following format.
first value in arraylist,
{mydate = 32156464 , mystring = "abc", mystring2 = "xyz"}
2nd value in arraylist of hashmap,
{mydate = 64687678 , mystring = "abdc", mystring2 = "xyzzz"}
3rd value in arraylist of hashmap,
{mydate = 11233678 , mystring = "abxdc", mystring2 = "xyzppzz"}
Now if i want to sort this arraylist of hashmap based on mydate key, i have to create a new comparator in TreeSet instance like this,
public static Set<HashMap<String, Object>> mySet = new TreeSet<>(new Comparator<HashMap<String, Object>>() {
#Override
public int compare(HashMap<String, Object> o1, HashMap<String, Object> o2) {
return ((Date) o2.get(mydate)).compareTo((mydate) o1.get(DATE));
}
});
and it will store the arraylist inside the TreeSet in sorted order just fine. But I have used a custom Comparator to achieve this. What is the point of using TreeSet in this situation for sorting data if i am also providing a custom Comparator to it ?
How can i sort this ArrayList of HashMap based on date value without using a new instance of Comparator in TreeSet ?
What is the point of using TreeSet in this situation for sorting data if i am also providing a custom Comparator to it ?
Because it's the TreeSet code that keeps it sorted. You haven't had to provide any of the code for that - all you've had to provide is the custom comparison.
How can i sort this ArrayList of HashMap based on date value without using a new instance of Comparator in TreeSet ?
You can't, directly. You could write a subclass of HashMap which implemented Comparable for itself, but that would seem a bit odd to me. For example:
public class SpecialMap extends HashMap<String, Object>
implements Comparable<SpecialMap> {
private final String key;
public SpecialMap(String key) {
this.key = key;
}
public int compareTo(SpecialMap other) {
// TODO: Null handling
Date thisDate = (Date) this.get(key);
Date otherDate = (Date) other.get(key);
return thisDate.compareTo(otherDate);
}
}
Then you could have an ArrayList<SpecialMap> and sort that.
But given that you've had to provide basically the same code as for the comparator and bound your comparison with the map type, it feels to me like it would be better just to stick with the comparator.
If you don't supply a Comparator to the TreeSet, then it will rely on its elements being Comparable to sort them. If they're not Comparable, then a ClassCastException will result. The TreeSet javadocs explain:
A NavigableSet implementation based on a TreeMap. The elements are ordered using their natural ordering, or by a Comparator provided at set creation time, depending on which constructor is used.
But the HashMap class is not Comparable, so you must supply a custom Comparator so the TreeSet knows how you want to sort them. You cannot sort your HashMaps without a Comparator, whether they're in a TreeSet or any other collection.
There are advantages to using a TreeSet in this situation. The add, find, and remove operations are O(log n). If you were to use an ArrayList for this, then add and remove operations would be O(n), even if the find operation would still be O(log n).
More from TreeSet javadocs:
This implementation provides guaranteed log(n) time cost for the basic operations (add, remove and contains).
Instead of using HashMap you could create a class that contains mydate, mystring, mystring2 and implements a Comparable interface.
However it's a good practice to use comparator because you'll be able to supply sorting criteria in runtime.
The point of using a comparator in a TreeSet is that you don't have write the code to do the actual sorting, you just provide a method that determines which of your objects is first, based on your comparison rules.
If you don't want to create a comparator, you would need to add to your collection objects that implement the Comparable interface.
To sort an ArrayList, use Collections.sort().
I have a map like so:
Map<List<Item>, Double> items = new HashMap<List<Item>, Double>();
I would like to sort this hashmap based on the size of the List<Item> where the largest sized ones are first. I don't care about the ordering within same sized objects though.
So far I've tried to use a TreeSet like so:
SortedSet<Map.Entry<List<Item>, Double>> sortedItems = new TreeSet<Map.Entry<List<Item>, Double>>(
new Comparator<Map.Entry<List<Item>, Double>>() {
#Override
public int compare(
Entry<List<Item>, Double> o1,
Entry<List<Item>, Double> o2) {
return o2.getKey().size() - o1.getKey().size();
}
});
sortedItems.addAll(items.entrySet());
However, the sortedItems object is only taking one of every sized list. It is treating equally sized lists as duplicates and is ignoring them. How can I fix this issue.
EDIT: So from what I can tell, when 2 lists of the same size are being compared, my compare method is returning 0. This tells the set that the entries are equal and they are treated as duplicates. So I guess the only way to fix this is to ensure that the compare method never returns 0. So I wrote this code:
#Override
public int compare(
Entry<List<AuctionItem>, Double> o1,
Entry<List<AuctionItem>, Double> o2) {
if (o1.getKey().size() <= o2.getKey().size()) {
return -1;
} else {
return 1;
}
}
You are using a TreeSet with Comparator, and as per your implementation of the compare(), it returns 0 if the size of the list is same. As TreeSet cannot contain duplicates, it adds only one of those lists whose size is equal. You do not need to create a TreeSetin order to sort your Map, because SETS should be used when the elements resemble mathematical sets (NO duplicate elements). You could possibly do :
List<Map.Entry<List<Item>, Double>> list =
new LinkedList<Map.Entry<List<Item>, Double>>( map.entrySet() );
Collections.sort( list, new Comparator<Map.Entry<List<Item>, Double>>()
{
public int compare( Map.Entry<List<Item>, Double> o1, Map.Entry<List<Item>, Double> o2 )
{
return (o1.getKey().size().compareTo( o2.getKey.size() );
}
} );
That is, put the entries of map in a List and then sort the list.
If you attempt to put an item into a TreeSet for which your custom Comparator returns 0, the item will not be placed into the Set.
You have to use a Comparator which doesn't return 0. For Lists whose sizes are equal, you have to define a consistent, arbitrary order.
Here is an easy and convinient way to do so:
new Comparator<Map.Entry<List<Item>, Double>>() {
#Override
public int compare(Entry<List<Item>, Double> o1,
Entry<List<Item>, Double> o2) {
int diff = o2.getKey().size() - o1.getKey().size();
return diff != 0 ? diff :
System.identityHashCode(o2.getKey()) -
System.identityHashCode(o1.getKey());
}
};
Basically what I do is this: if the 2 lists have different size, size2 - size1 will do. If they have the same size, I return the difference of their identity hashcodes which will always differ from 0 because the identity hashcode is the memory address which is different for distinct objects. And it is always the same for an object, so the comparator will always return the same order for 2 lists having the same size.
Using this comparator you will get a sorted set which allows lists having the same size, and it will be sorted by list size.
It seems odd that you are using a list of values (items) as the key to your hashmap; however, I'll give it a go.
At the core any Map is not an ordered collection. In short, if "Pete" has a height of 67 inches and "Bob" has a height of 72 inches, then without some extra bit of information, it is not possible to determine if Bob should come before or after Pete.
Ordered by "key" or in this case, "name" one might impose alphabetical ordering, in which case "Bob" comes before "Pete".
Ordered by "value" or in this case, "height" one might impose smallest to largest ordering, in which case "Pete" comes before "Bob".
I'm sure that you know what you want to order by (the size of the list), but this example means to illustrate that a Map alone is a poor data structure for ordering. Even ordered maps in Java only sort by insertion order.
My suggestion is to keep two collections in the same wrapping class. One that contains an ordered list of the keys, and another that contains the Map. Walk the ordered list of keys and return the map values in that order if you want an ordered set of the values by their key characteristics. It will be easier to understand, and much more readable.
Also realize that the Map is not listening to the key values, as such, if someone who has a key decides to add an entry to the List that the key maintains, the Map will not know of the alteration and will not recompute the bucket for the key's former value. As such, to use a Map properly, you need to approach it in one of two ways.
Make the map keys immutable, copying the values upon input and returning Collections.unmodifiableList(...) wrappers on output.
Accept Map keys that can be listened to, and make the map a subscriber to the key updates that might occur. When the map detects a key change, the values are removed from the old key location and re-added back to the map with the new key.
If you don't need constant sorting over time, you can do that:
Map<List<Item>, Double> items = new HashMap<>();
List<Map.Entry<List<Item>, Double>> list = new ArrayList<>(items.entrySet());
Collections.sort(list, new Comparator<Map.Entry<List<Item>, Double>>() {
#Override
public int compare(
Map.Entry<List<Item>, Double> o1,
Map.Entry<List<Item>, Double> o2) {
return Integer.compare(o2.getKey().size(), o1.getKey().size());
}
}
});
If you need a TreeSet or a TreeMap, then the use of a Comparator is fine but you have to define what happens when the lists size are equals: you need to use another criteria to determine ordering (like comparing each items a, b of both list, until a.compareTo(b) != 0).
Ok so here is my issue. I have to HashSet's, I use the removeAll method to delete values that exist in one set from the other.
Prior to calling the method, I obviously add the values to the Sets. I call .toUpperCase() on each String before adding because the values are of different cases in both lists. There is no rhyme or reason to the case.
Once I call removeAll, I need to have the original cases back for the values that are left in the Set. Is there an efficient way of doing this without running through the original list and using CompareToIgnoreCase?
Example:
List1:
"BOB"
"Joe"
"john"
"MARK"
"dave"
"Bill"
List2:
"JOE"
"MARK"
"DAVE"
After this, create a separate HashSet for each List using toUpperCase() on Strings. Then call removeAll.
Set1.removeAll(set2);
Set1:
"BOB"
"JOHN"
"BILL"
I need to get the list to look like this again:
"BOB"
"john"
"Bill"
Any ideas would be much appreciated. I know it is poor, there should be a standard for the original list but that is not for me to decide.
In my original answer, I unthinkingly suggested using a Comparator, but this causes the TreeSet to violate the equals contract and is a bug waiting to happen:
// Don't do this:
Set<String> setA = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
setA.add("hello");
setA.add("Hello");
System.out.println(setA);
Set<String> setB = new HashSet<String>();
setB.add("HELLO");
// Bad code; violates symmetry requirement
System.out.println(setB.equals(setA) == setA.equals(setB));
It is better to use a dedicated type:
public final class CaselessString {
private final String string;
private final String normalized;
private CaselessString(String string, Locale locale) {
this.string = string;
normalized = string.toUpperCase(locale);
}
#Override public String toString() { return string; }
#Override public int hashCode() { return normalized.hashCode(); }
#Override public boolean equals(Object obj) {
if (obj instanceof CaselessString) {
return ((CaselessString) obj).normalized.equals(normalized);
}
return false;
}
public static CaselessString as(String s, Locale locale) {
return new CaselessString(s, locale);
}
public static CaselessString as(String s) {
return as(s, Locale.ENGLISH);
}
// TODO: probably best to implement CharSequence for convenience
}
This code is less likely to cause bugs:
Set<CaselessString> set1 = new HashSet<CaselessString>();
set1.add(CaselessString.as("Hello"));
set1.add(CaselessString.as("HELLO"));
Set<CaselessString> set2 = new HashSet<CaselessString>();
set2.add(CaselessString.as("hello"));
System.out.println("1: " + set1);
System.out.println("2: " + set2);
System.out.println("equals: " + set1.equals(set2));
This is, unfortunately, more verbose.
It could be done by:
Moving the content of your lists into case-insensitive TreeSets,
then removing all common Strings case-insensitively thanks TreeSet#removeAll(Collection<?> c)
and finally relying on the fact that ArrayList#retainAll(Collection<?> c) will iterate over the elements of the list and for each element it will call contains(Object o) on the provided collection to know whether the value should be kept or not and here as the collection is case-insensitive, we will keep only the Strings that match case-insensitively with what we have in the provided TreeSet instance.
The corresponding code:
List<String> list1 = new ArrayList<>(
Arrays.asList("BOB", "Joe", "john", "MARK", "dave", "Bill")
);
List<String> list2 = Arrays.asList("JOE", "MARK", "DAVE");
// Add all values of list1 in a case insensitive collection
Set<String> set1 = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
set1.addAll(list1);
// Add all values of list2 in a case insensitive collection
Set<String> set2 = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
set2.addAll(list2);
// Remove all common Strings ignoring case
set1.removeAll(set2);
// Keep in list1 only the remaining Strings ignoring case
list1.retainAll(set1);
for (String s : list1) {
System.out.println(s);
}
Output:
BOB
john
Bill
NB 1: It is important to have the content of the second list into a TreeSet especially if we don't know the size of it because the behavior of TreeSet#removeAll(Collection<?> c) depends on the size of both collections, if the size of the current collection is strictly bigger than the size of the provided collection, then it will call directly remove(Object o) on the current collection to remove each element, in this case the provided collection could be a list. But if it is the opposite, it will call contains(Object o) on the provided collection to know whether a given element should be removed or not so if it is not an case-insensitive collection, we won't get the expected result.
NB 2: The behavior of the method ArrayList#retainAll(Collection<?> c) described above is the same as the behavior of the default implementation of the method retainAll(Collection<?> c) that we can find in AbstractCollection such that this approach will actually work with any collections whose implementation of retainAll(Collection<?> c) has the same behavior.
You can use a hashmap and use the capital set as keys that map to the mixed case set.
Keys of hashmaps are unique and you can get a set of them using HashMap.keyset();
to retrieve the original case, it's as simple as HashMap.get("UPPERCASENAME").
And according to the documentation:
Returns a set view of the keys
contained in this map. The set is
backed by the map, so changes to the
map are reflected in the set, and
vice-versa. The set supports element
removal, which removes the
corresponding mapping from this map,
via the Iterator.remove, Set.remove,
removeAll, retainAll, and clear
operations. It does not support the
add or addAll operations.
So HashMap.keyset().removeAll will effect the hashmap :)
EDIT: use McDowell's solution. I overlooked the fact that you didn't actually need the letters to be upper case :P
This would be an interesting one to solve using google-collections. You could have a constant Predicate like so:
private static final Function<String, String> TO_UPPER = new Function<String, String>() {
public String apply(String input) {
return input.toUpperCase();
}
and then what you're after could be done someting like this:
Collection<String> toRemove = Collections2.transform(list2, TO_UPPER);
Set<String> kept = Sets.filter(list1, new Predicate<String>() {
public boolean apply(String input) {
return !toRemove.contains(input.toUpperCase());
}
}
That is:
Build an upper-case-only version of the 'to discard' list
Apply a filter to the original list, retaining only those items whose uppercased value is not in the upper-case-only list.
Note that the output of Collections2.transform isn't an efficient Set implementation, so if you're dealing with a lot of data and the cost of probing that list will hurt you, you can instead use
Set<String> toRemove = Sets.newHashSet(Collections2.transform(list2, TO_UPPER));
which will restore an efficient lookup, returning the filtering to O(n) instead of O(n^2).
as far as i know, hashset's use the object's hashCode-method to distinct them from each other.
you should therefore override this method in your object in order to distinct cases.
if you're really using string, you cannot override this method as you cannot extend the String-class.
therefore you need to create your own class containing a string as attribute which you fill with your content. you might want to have a getValue() and setValue(String) method in order to modify the string.
then you can add your own class to the hashmap.
this should solve your problem.
regards