Hi guys i've never written a comparator b4 and im having a real problem. I've created a hashtable.
Hashtable <String, Objects> ht;
Could someone show how you'd write a comparator for a Hashtable? the examples i've seen overide equals and everything but i simply dont have a clue. The code below is not mine but an example i found, the key thing in hashtables means i cant do it like this i guess.
public class Comparator implements Comparable<Name> {
private final String firstName, lastName;
public void Name(String firstName, String lastName) {
if (firstName == null || lastName == null)
throw new NullPointerException();
this.firstName = firstName;
this.lastName = lastName;
}
public String firstName() { return firstName; }
public String lastName() { return lastName; }
public boolean equals(Object o) {
if (!(o instanceof Name))
return false;
Name n = (Name)o;
return n.firstName.equals(firstName) &&
n.lastName.equals(lastName);
}
public int hashCode() {
return 31*firstName.hashCode() + lastName.hashCode();
}
public String toString() {
return firstName + " " + lastName;
}
public int compareTo(Name n) {
int lastCmp = lastName.compareTo(n.lastName);
return (lastCmp != 0 ? lastCmp :
firstName.compareTo(n.firstName));
}
}
A Comparator will tell you which of two items is larger. If this has meaning for your HashTable, only you can say what the meaning is. It would be very unusual to want to compare two HashTables in this way.
That's not a Comparator class. That's a Name class that implements Comparable.
Hashtable and Hashmap don't use either Comparator or Comparable. If you want sorted keys use a TreeMap.
Comparators are used to sort a list. A Hashtable (note the case) is not ordered by its elements. You can order a table by iterating over its keys (in the case you'd want to order on its keys, I presume) and put them in a List. The next thing to do is to sort the List and iterate over the List, and use a get out of the Hashtable to get its associated value.
Here is an example (using HashMap, since it's more integrated with the rest of the Java Collections. A HashMap is essentially the same as Hashtable.):
public static void main(String... arg) {
HashMap<String, Object> x = new HashMap<String, Object>();
x.put("second", " ordered!");
x.put("first", "Correctly");
LinkedList<String> keys = new LinkedList<String>();
for(final String f : x.keySet()) {
keys.add(f);
}
Collections.sort(keys, new Comparator<String>() {
public int compare(String first, String second) {
// return -1 is "first < second"
// return 1 is "first > second"
// return 0 is "first == second"
return first.compareTo(second);
}
});
for(final String f : keys) {
System.out.print(x.get(f));
}
System.out.println();
}
The order of the list keys is sorted by the anonymous Comparator class. It will sort alphabetically, as is the default for Strings. You can use your own key object, like you mentioned. If you don't implement Comparator in this key object, then you can supply, as in the above example. Else you can use the default Comparator by calling:
Collections.sort(keys);
Which will use the classes implementation of Comparator. If it does not implement Comparator, then it will throw an exception (since it will cast to a Comparator)
Related
I wan to create TreeSet() that will sort my elements with my predefined comparator. But the problem is when I give the comparator as a parameter to the constructor of the TreeSet(MyComparator), the TreeSet is not avoiding duplicates. Can I achieve sorting of the elements and avoiding duplicates?
The comparator looks like:
public static Comparator<Participant> byNameAndAge = (L, R) -> {
//check if they have the same code
if (L.code.equalsIgnoreCase(R.code))
return 0;
int res = L.name.compareToIgnoreCase(R.name);
if (res == 0)
res = Integer.compare(L.age, R.age);
return res;
};
You've misunderstood a few things. TreeSet does eliminate duplicates, with 'a duplicate' defined as 'any two elements for which your compare method returns 0'. No 2 such elements can both exist in a treeset. I'm sure your code doesn't work if you say so, but the code you pasted isn't the problem, nor is TreeSet's code.
A trivial example:
Comparator<String> byLength = (a, b) -> a.length() - b.length();
Set<String> set = new TreeSet<String>(byLength);
set.add("Hello");
set.add("World");
set.add("X");
set.add("VeryLong");
System.out.println(set);
> [X, Hello, VeryLong]
Note how 'World' disappeared, because the comparator says it is equal to Hello (they are both 5 length, a.length() - b.length() is returning 0, and 0 is 'equal, thus, eliminate the duplicate' according to treeset). In other words, your code as pasted would eliminate duplicates, the problem lies elsewhere.
This code is almost the same as yours.
Comparators chaining
public static void main(String[] args) {
// custom comparator
Comparator<Participant> byNameAndAge = Comparator
// first sorting by name ignoring case
.comparing(Participant::getName, String::compareToIgnoreCase)
// second sorting by age
.thenComparingInt(Participant::getAge);
// constructor with a comparator as a parameter
TreeSet<Participant> treeSet = new TreeSet<>(byNameAndAge);
treeSet.addAll(Set.of( // test data
new Participant("John", 25),
new Participant("Junior", 2),
new Participant("james", 31),
new Participant("john", 22)));
// output
treeSet.forEach(System.out::println);
//name=james, age=31
//name=john, age=22
//name=John, age=25
//name=Junior, age=2
}
static class Participant {
String name;
int age;
public Participant(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
#Override
public String toString() {
return "name=" + name + ", age=" + age;
}
}
I really don't understand from the below code how the get() methods, set() methods and toString() are called. Could someone explain me ?
The whole point here is after seeing the output I don't understand how the tostring method has been called. I don't see anything explicitly being called.
public class MyDuplicateKeyEx {
public static void main(String a[]){
HashMap<Price, String> hm = new HashMap<Price, String>();
hm.put(new Price("Banana", 20), "Banana");
hm.put(new Price("Orange", 30), "Orange");
printMap(hm);
Price key = new Price("Banana", 20);
System.out.println("Adding duplicate key...");
hm.put(key, "Grape");
System.out.println("After adding dulicate key:");
printMap(hm);
}
public static void printMap(HashMap<Price, String> map){
Set<Price> keys = map.keySet();
for(Price p:keys){
System.out.println(p+"==>"+map.get(p));
}
}
}
class Price{
private String item;
private int price;
public Price(String itm, int pr){
this.item = itm;
this.price = pr;
}
public int hashCode(){
int hashcode = 0;
hashcode = price*20;
hashcode += item.hashCode();
return hashcode;
}
public boolean equals(Object obj){
if (obj instanceof Price) {
Price pp = (Price) obj;
return (pp.item.equals(this.item) && pp.price == this.price);
} else {
return false;
}
}
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String toString(){
return "item: "+item+" price: "+price;
}
}
Output:
item: Apple price: 40==>Apple
item: Orange price: 30==>Orange
item: Banana price: 20==>Banana
Adding duplicate key...
After adding dulicate key:
item: Apple price: 40==>Apple
item: Orange price: 30==>Orange
item: Banana price: 20==>Grape
Thanks !!
You are right, there is no explicit call to toString. But under the hood, that is what Java is doing. When seeing p+"==>"+map.get(p), Java is doing p.toString()+"==>"+map.get(p).toString(). That is why you can concatenate strings and objects without problems.
Additionally, a better way of iterating through the key/values of a Map is:
public static void printMap(HashMap<Price, String> map) {
for (Map.Entry<Price, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + "==>" + entry.getValue())
}
}
When using a HashMap with user-defined objects as keys, you must be very careful that you do never modify the fields used to compute the hashCode if they are present in the map. This is why you'll often see that final fields should be used to compute it. With a large program, this avoids lots of unnecessary mistakes.
Of course there is no explicit toString! Java does this implicitly for you! You should thank him, ya know.
The magic lies behind this code:
p+"==>"+map.get(p)
Here, p is a Price and map.get(p) is a String. So the above is basically adding a price to a string, then add another string to the result.
When Java wants to add any object to a string, it calls that object's toString to convert that object to String first. Otherwise, how can a price be added to a string, right?
Tip:
Never use objects which hash code can change as keys of a hash map. I have actually seen a person who was very extreme and he implemented the hashCode method with Math.random()! Here's the post: Could not understand the output And asked why can a hash map store things with the same hash code.
So to avoid that confusion, please don't use mutable objects as keys. Just remove those setters and you'll be fine.
This question already has answers here:
Are mutable hashmap keys a dangerous practice?
(10 answers)
Closed 7 years ago.
Let's say I have a person class and equality is based on id attribute. Below is the implementation of Person class -
class Person {
private int id;
private String firstName;
public Person(int id, String firstName) {
super();
this.id = id;
this.firstName = firstName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public int hashCode() {
return this.id;
}
public boolean equals(Object obj) {
return ((Person) obj).getId() == this.id;
}
}
I am using Person class as as key of a HashMap. Now see below code -
import java.util.HashMap;
public class TestReport {
public static void main(String[] args) {
Person person1 = new Person(1, "Person 1");
Person person2 = new Person(2, "Person 2");
HashMap<Person, String> testMap = new HashMap<Person, String>();
testMap.put(person1, "Person 1");
testMap.put(person2, "Person 2");
person1.setId(2);
System.out.println(testMap.get(person1));
System.out.println(testMap.get(person2));
}
}
Notice, though we have added two different person object as key to the HashMap, later we have changed the id of person1 object to 2 to make both the person object equal.
Now, I am getting output as -
Person 2
Person 2
I can see there are two key-value pairs in the HashMap with data: "person1/Person 1" and "person2/Person 2", still I will always get "Person 2" as output and I can never access value "Person 1". Also notice, we have duplicate key in HashMap.
I can understand the behavior after looking at the source code, but doesn't it seem to be problem? Can we take some precaution to prevent it?
It all depends on how hashCode() value is used by HashMap.
While it is required that two equal objects of same hash code, reverse is not necessarily true. Two unequal objects can have same hash code (as int has only finite set of possible values).
Everytime you put an object in HashMap, it stores the object in a bucket identified by key's hashCode(). So, based on how hashCode() is implemented, you should have a fair distribution of entries in various buckets.
Now, when you try to retrieve a value, the HashMap will identify the bucket in which given key falls, and then will iterate through all keys in that bucket to pick the entry for given key - in this stage it will use equals() method to identify the entry.
In your case, person1 is sitting in bucket 1 and person2 is sitting in bucket 2.
However, when you changed the hashCode() value of person1 by updating its id, the HashMap is unaware of this change. Later, when you look up an entry using person1 as key, the HashMap thinks that it should be present in bucket 2 (as person1.hashCode() is 2 now), and after that when it iterates bucket 2 using equals method, it finds an entry of person2 and thinks that it is the object that you are interested in as equals in your case too is based on id attribute.
Above explanation is evident when one looks at implementation of HashMap#get method as shown below:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
PS Sometimes when you know an answer to question, you forget to lookup for duplicate questions, and jump right into answering the question before anyone can reply - that's what happened in this case. Should be careful next time :-)
This is happening because your equals() method uses the hashcode only. You should be comparing all your person fields like firstName.
I'm developing a Java Application that reads a lot of strings data likes this:
1 cat (first read)
2 dog
3 fish
4 dog
5 fish
6 dog
7 dog
8 cat
9 horse
...(last read)
I need a way to keep all couple [string, occurrences] in order from last read to first read.
string occurrences
horse 1 (first print)
cat 2
dog 4
fish 2 (last print)
Actually i use two list:
1) List<string> input; where i add all data
In my example:
input.add("cat");
input.add("dog");
input.add("fish");
...
2)List<string> possibilities; where I insert the strings once in this way:
if(possibilities.contains("cat")){
possibilities.remove("cat");
}
possibilities.add("cat");
In this way I've got a sorted list where all possibilities.
I use it like that:
int occurrence;
for(String possible:possibilities){
occurrence = Collections.frequency(input, possible);
System.out.println(possible + " " + occurrence);
}
That trick works good but it's too slow(i've got millions of input)... any help?
(English isn’t my first language, so please excuse any mistakes.)
Use a Map<String, Integer>, as #radoslaw pointed, to keep the insertion sorting use LinkedHashMap and not a TreeMap as described here:
LinkedHashMap keeps the keys in the order they were inserted, while a TreeMap is kept sorted via a Comparator or the natural Comparable ordering of the elements.
Imagine you have all the strings in some array, call it listOfAllStrings, iterate over this array and use the string as key in your map, if it does not exists, put in the map, if it exists, sum 1 to actual result...
Map<String, Integer> results = new LinkedHashMap<String, Integer>();
for (String s : listOfAllStrings) {
if (results.get(s) != null) {
results.put(s, results.get(s) + 1);
} else {
results.put(s, 1);
}
}
Make use of a TreeMap, which will keep ordering on the keys as specified by the compare of your MyStringComparator class handling MyString class which wraps String adding insertion indexes, like this:
// this better be immutable
class MyString {
private MyString() {}
public static MyString valueOf(String s, Long l) { ... }
private String string;
private Long index;
public hashcode(){ return string.hashcode(); }
public boolean equals() { // return rely on string.equals() }
}
class MyStringComparator implements Comparator<MyString> {
public int compare(MyString s1, MyString s2) {
return -s1.getIndex().compareTo(s2.gtIndex());
}
}
Pass the comparator while constructing the map:
Map<MyString,Integer> map = new TreeMap<>(new MyStringComparator());
Then, while parsing your input, do
Long counter = 0;
while (...) {
MyString item = MyString.valueOf(readString, counter++);
if (map.contains(item)) {
map.put(map.get(item)+1);
} else {
map.put(item,1);
}
}
There will be a lot of instantiation because of the immutable class, and the comparator will not be consistent with equals, but it should work.
Disclaimer: this is untested code just to show what I'd do, I'll come back and recheck it when I get my hands on a compiler.
Here is the complete solution for your problem,
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DataDto implements Comparable<DataDto>{
public int count = 0;
public String string;
public long lastSeenTime;
public DataDto(String string) {
this.string = string;
this.lastSeenTime = System.currentTimeMillis();
}
public boolean equals(Object object) {
if(object != null && object instanceof DataDto) {
DataDto temp = (DataDto) object;
if(temp.string != null && temp.string.equals(this.string)) {
return true;
}
}
return false;
}
public int hashcode() {
return string.hashCode();
}
public int compareTo(DataDto o) {
if(o != null) {
return o.lastSeenTime < this.lastSeenTime ? -1 : 1;
}
return 0;
}
public String toString() {
return this.string + " : " + this.count;
}
public static final void main(String[] args) {
String[] listOfAllStrings = {"horse", "cat", "dog", "fish", "cat", "fish", "dog", "cat", "horse", "fish"};
Map<String, DataDto> results = new HashMap<String, DataDto>();
for (String s : listOfAllStrings) {
DataDto dataDto = results.get(s);
if(dataDto != null) {
dataDto.count = dataDto.count + 1;
dataDto.lastSeenTime = System.nanoTime();
} else {
dataDto = new DataDto(s);
results.put(s, dataDto);
}
}
List<DataDto> finalResults = new ArrayList<DataDto>(results.values());
System.out.println(finalResults);
Collections.sort(finalResults);
System.out.println(finalResults);
}
}
Ans
[horse : 1, cat : 2, fish : 2, dog : 1]
[fish : 2, horse : 1, cat : 2, dog : 1]
I think this solution will be suitable for your requirement.
If you know that your data is not going to exceed your memory capacity when you read it all into memory, then the solution is simple - using a LinkedList or a and a LinkedHashMap.
For example, if you use a Linked list:
LinkedList<String> input = new LinkedList();
You then proceed to use input.add() as you did originally. But when the input list is full, you basically use Jordi Castilla's solution - but put the entries in the linked list in reverse order. To do that, you do:
Iterator<String> iter = list.descendingIterator();
LinkedHashMap<String,Integer> map = new LinkedHashMap<>();
while (iter.hasNext()) {
String s = iter.next();
if ( map.containsKey(s)) {
map.put( s, map.get(s) + 1);
} else {
map.put(s, 1);
}
}
Now, the only real difference between his solution and mine is that I'm using list.descendingIterator() which is a method in LinkedList that gives you the entries in backwards order, from "horse" to "cat".
The LinkedHashMap will keep the proper order - whatever was entered first will be printed first, and because we entered things in reverse order, then whatever was read last will be printed first. So if you print your map the result will be:
{horse=1, cat=2, dog=4, fish=2}
If you have a very long file, and you can't load the entire list of strings into memory, you had better keep just the map of frequencies. In this case, in order to keep the order of entry, we'll use an object such as this:
private static class Entry implements Comparable<Entry> {
private static long nextOrder = Long.MIN_VALUE;
private String str;
private int frequency = 1;
private long order = nextOrder++;
public Entry(String str) {
this.str = str;
}
public String getString() {
return str;
}
public int getFrequency() {
return frequency;
}
public void updateEntry() {
frequency++;
order = nextOrder++;
}
#Override
public int compareTo(Entry e) {
if ( order > e.order )
return -1;
if ( order < e.order )
return 1;
return 0;
}
#Override
public String toString() {
return String.format( "%s: %d", str, frequency );
}
}
The trick here is that every time you update the entry (add one to the frequency), it also updates the order. But the compareTo() method orders Entry objects from high order (updated/inserted later) to low order (updated/inserted earlier).
Now you can use a simple HashMap<String,Entry> to store the information as you read it (I'm assuming you are reading from some sort of scanner):
Map<String,Entry> m = new HashMap<>();
while ( scanner.hasNextLine() ) {
String str = scanner.nextLine();
Entry entry = m.get(str);
if ( entry == null ) {
entry = new Entry(str);
m.put(str, entry);
} else {
entry.updateEntry();
}
}
Scanner.close();
Now you can sort the values of the entries:
List<Entry> orderedList = new ArrayList<Entry>(m.values());
m = null;
Collections.sort(orderedList);
Running System.out.println(orderedList) will give you:
[horse: 1, cat: 2, dog: 4, fish: 2]
In principle, you could use a TreeMap whose keys contained the "order" stuff, rather than a plain HashMap like this followed by sorting, but I prefer not having either mutable keys in a map, nor changing the keys constantly. Here we are only changing the values as we fill the map, and each key is inserted into the map only once.
What you could do:
Reverse the order of the list using
Collections.reverse(input). This runs in linear time - O(n);
Create a Set from the input list. A Set garantees uniqueness.
To preserve insertion order, you'll need a LinkedHashSet;
Iterate over this set, just as you did above.
Code:
/* I don't know what logic you use to create the input list,
* so I'm using your input example. */
List<String> input = Arrays.asList("cat", "dog", "fish", "dog",
"fish", "dog", "dog", "cat", "horse");
/* by the way, this changes the input list!
* Copy it in case you need to preserve the original input. */
Collections.reverse(input);
Set<String> possibilities = new LinkedHashSet<String>(strings);
for (String s : possibilities) {
System.out.println(s + " " + Collections.frequency(strings, s));
}
Output:
horse 1
cat 2
dog 4
fish 2
is there a way to sort this numbers stored in a string variable?
TreeMap<String,List<QBFElement>> qbfElementMap = new TreeMap<String, List<QBFElement>>();
this is the map where the key is :
27525-1813,
27525-3989,
27525-4083,
27525-4670,
27525-4911,
27526-558,
27526-1303,
27526-3641,
27526-4102,
27527-683,
27527-2411,
27527-4342
this is the list of keys and the value for each of the key is a list.
now, how can i sort this key in ascending order by number.
ex. if i want to sort : 1,2,11,20,31,3,10
i want to have as output is : 1,2,3,10,11,20,31
but when i use the autosort of treemap the output goes : 1,10,11,2,20,3,31
how can i sort it in ascending order by numeric?
and the language is java :) thank you:)
The keys in your map are not Integer but String values. That's why the key's are sorted like observed.
Either change the Map to
TreeMap<Long,List<QBFElement>> qbfElementMap
or create it with a specialized Comparatorthat will provide the expected numerical order for the String type keys.
A mapping from your String values to Longs could be done like this:
private Long convertToLongTypeKey(String key) {
String[] parts = key.split("-");
// next lines assumes, that the second part is in range 0...9999
return Long.parseLong(parts[0]) * 10000 + Long.parseLong(parts[1]);
}
An implementation of Comparator<String> could use the same mapping to create a numerical comparision of two String based keys:
new TreeMap<String,List<QBFElement>>(new Comparator<String>(){
#Override
public int compare(String key1, String key2) {
String[] parts1 = key1.split("-");
Long long1 = Long.parseLong(parts1[0]) * 10000 + Long.parseLong(parts1[1]);
String[] parts2 = key2.split("-");
Long long2 = Long.parseLong(parts2[0]) * 10000 + Long.parseLong(parts2[1]);
return long1.compareTo(long2);
}
});
You can change the way that the TreeMap sorts its keys by providing a custom comparator to the constructor. If you want, you can define a new Comparator that compares strings by breaking them up into numeric components.
It seems like a better idea, though, would be to not use Strings as your keys. The data you're using as keys is clearly not textual - it's numeric - and you might want to define a custom type to represent it. For example:
public class KeyType implements Comparable<KeyType> {
private final int first;
private final int second;
public KeyType(int first, int second) {
this.first = first;
this.second = second;
}
#Override
public boolean equals(Object other) {
if (!(other instanceof KeyType)) return false;
KeyType realOther = (KeyType) other;
return realOther.first == first && realOther.second == second;
}
#Override
public int hashCode() {
return first + 31 * second;
}
public int compareTo(KeyType other) {
if (first != other.first)
return first - other.first;
return second - other.second;
}
}
This approach is the most expressive and robust. It gives you better access to the individual fields of the keys you're using, and also prevents you from adding nonsensical keys into the map like the string "Lalalalala". I'd strongly suggest using this approach, or at least one like it. The type system is your friend.
A TreeMap can take a custom comparator for custom sorting. Write a comparator that sorts the keys the way you want and use it when you create the treemap
TreeMap<String,List<QBFElement>> qbfElementMap = new TreeMap<String, List<QBFElement>>(myComparator);