Java TreeSet Comparator - java

I have a Vehicle class with fields: baseVehicleId, subModelId, notes and partNo
If is the same baseVehicleId, subModelId and partNo , i want the object with the longest notes to remain( and delete the others)
I have this:
Comparator<Vehicle> comparator = new Comparator<Vehicle>() {
#Override
public int compare(Vehicle a, Vehicle b) {
if(a.baseVehicleId().equals(b.baseVehicleId()) && String.valueOf(a.subModelId ()).equals(String.valueOf(b.subModelId ()))
&& String.valueOf(a.partNo ()).equals(String.valueOf(b.partNo ()))
))
return a.getOurnotes().compareTo(b.getOurnotes());
// not good I know
return 1;
}
};
TreeSet<Vehicle> treeSet = new TreeSet<Vehicle>(comparator);
How can i modify this code :/?
So for example if i have all the fields equal and the length of notes are greater then delete the other objects.

The comparator's job is simply to say which of two objects comes first - there's no mechanism to manipulate the underlying collection during it's comparisons. I think you would want to think about this in a slightly different way: perform your filtering logic prior to adding an object to your collection.
I'd recommend just using a standard list, say ArrayList for sake of simplicity. Also you'd need to override the equals() method in your Vehicle class which returns true if two Vehicles share the same IDs and part numbers.
When it comes to adding new vehicles you can do something like this:
int vehicleIndex = myvehicles.indexOf(vehicle) // -1 if missing, otherwise 0 or greater to represent the index position
if (vehicleIndex == -1) {
// No item in the list shares the ids/model/part numbers so can add
myvehicles.add(vehicle)
} else {
// There's another similar vehicle, better compare their notes:
Vehicle existingVehicle = myvehicles.get(vehicleIndex); // retrieve the object from list so that we can compare it
// check if the new vehicle has longer notes
if (vehicle.getOurNotes().length > existingVehicle.getOurNotes().length) {
// if it does, remove the old one from the list
myvehicles.remove(vehicleIndex);
// and add the new one. Effectively we've done a replacement
myvehicles.add(vehicle)
}
}
Obviously it's not automatically sorted but you can do that after adding a batch of items by running the Collections.sort() method which also accepts a Comparator, but this time your comparator can focus on the specific task of detailing the ordering now that you know the list is pre-filtered so you are not concerned with trying to filter items out.

I think you should use the method
maximum = Collections.max(collection, comparator);
for searching the maximum of the elements and then just delete the others with
collection.remove();
Inside the comparator you can't delete from the collection.

Just loop through the collection and remove everything that doesn't meet your requirements using it.remove().
You may also want to look at the Java 8 collections functionality that will allow you to do things like filter a collection as that will also do what you are looking for.

Related

What is the best way to merge similar objects in a java List?

Here is my problem (simplified):
Suppose we have a class:
public class MyClass{
String name;
Double amount;
String otherAttribute;
}
And a List<MyClass> myList
Suppose we have 2 elements from myList. Let's say object1 and object2
What I would like to do is:
if (object1.name.equals(object2.name){
//add amount of object2 to object1
//remove object 2 from the list
}
Considering I have a large list (maybe 100 elements) and I would like to find the best and less consuming way to do what I want.
What would you suggest ?
EDIT:
Yes 100 items is not large, but I would call this method (of merging similar objects) many times for many different sized lists. So that's way I would like to find the best practice for this.
I can't override equals or hashCode methods of MyClass, unfortunately (client requirement)
I'd add the objects to a HashMap where the name is the key and MyClass is the value being stored. Loop through each object in your list to add them to the map. If the name isn't in the map, just add the name, object pair. If it is already in the map, add the amount to the object already stored. When the loop completes, extract the objects from the map.
100 elements is a tiny size for a list, considering you're not going to repeat the operation some hundreds of thousands times. If it's the case, I'd consider creating a data structure indexing the list items by the search property (Map for instance), or ordering it if suitable and using an efficient search algorithm.
One approach (as suggested by Bill) would be to traverse the List adding every element to a Map, with the name property as key. You can take advantage of put's return to know if a name has been previously put into the map, and add the previosuly accumulated amounts in the current element. Finally, you could use values() to get the List without duplicates.
For instance:
List<MyClass> l;
Map<String, Myclass> m = new HashMap<MyClass>();
for (MyClass elem : l) {
MyClass oldElem = m.put(elem.getName(), elem);
if (oldElem != null) {
elem.setAmount(elem.getAmount() + oldElem.getAmount());
}
}
l = new ArrayList<MyClass>(m.values());
If you need to preserve order in the list, consider using a LinkedHashMap.
This is an O(n^2) problem unfortunately. You need to compare n elements to n-1 other elements. There is no way to do this but to brute force it.
If you used a HashMap however, you could check the map for an element before adding it to the Map which is an O(1) operation. It would look something like this:
HashMap<String, MyClass> map = new HashMap<String, MyClass>();
when you add an element:
if (map.get(obj1.name) != null) {
var obj2 = map.get(obj1.name);
obj2.amount = obj2.amount + obj1.amount;
map.put(obj1.name, obj2);
}
'Large' is relative, 100 items is definitely not large, imagine if you had to process a stream of 1.000.000 items/second. Then you would redefine large :D
In your example, what I think would be good to avoid would be to create a Set of your items' names. Searching a java HashSet takes O(1), so if an objects' name exists in the hash set, then update it on the list. An even better solution would be to create a HashMap, on which you could say e.g.
if(mymap.contains(thename)){
mymap.put(thename, newSum);
}
this being an example of how you could use it. Here's a link to get you started: http://java67.blogspot.gr/2013/02/10-examples-of-hashmap-in-java-programming-tutorial.html
I suggest to optimize (if possible) by not even doing the .add() to the list if an element with the same name exists. Using one of the hash based collections in combination with a proper equals() & hashCode() implementation based on MyClass.name should also give you somewhat good performance.
First, since you cannot override equals or hashCode, then you need to have the function that will do this functionality in the same package as your MyClass class, since no accessor methods are defined in MyClass
Second, try to have your items in a LinkedList, so that you can remove repeating elements from that list really quick without having to move around the other items.
Use a map to keep track of the amount that corresponds to a given name, while iterating the list, and removing repeating elements at the same time. In this way you don't have to create a new list.
List<MyClass> myClass_l;
Map<String, MyClass> nameMyClass_m = new HashMap<String, MyClass>();
for (Iterator<MyClass> iterator = myClass_l.iterator(); iterator.hasNext(){
MyClass m = iterator.next();
if (nameAmount_m.contains(m.name)){
MyClass firstClass = m.get(m.name);
firstClass.amount += m.amount;
iterator.remove();
}
else{
nameMyClass_m.put(m.name, m);
}
}
By the time you have finished the loop, you will have the items you want in your original list.

Merging and comparing two arrays

How do I compare each element in two different sets of data, and then merge certain elements of them into a new set of data?
Some info about my methods below. Note:
Not using java.lang.Collections
In one class I have these calls initializers:
BookCollection collection1 = new BookCollection(100);
BookCollection collection2 = new BookCollection(50);
BookCollection collection3 = new BookCollection(150);
And this call to my method merge(which I am trying to complete)
collection3.merge(collection1, collection2);
Anndd in another class my method merge is trying to compare the two sets of data(collection 1 and 2) and if it finds an elements that are equal, only add 1 of those elements to the new collection. Otherwise, add all of the non-equal elements to the new collection.
Here is what I made, but I know that it isn't working. There is a call to one of my other methods findBook that I will post.
public BookCollection merge(BookCollection c1,BookCollection c2){
//use this. operator to grab other vars
BookCollection cNew = new BookCollection(cNew);
for(String s1: c1)
if (s1.equals(c2)) {
cNew = c1;
}
cNew = c1 + s1;
}
Annddd
private int findBook(String isbn){
// iterate all the Book elements in the collection array
for(int i = 0; i <= collection.length; i++){
// check if the current book isbn matches the one provided argument
if (collection[i].getIsbn().equals(isbn))
return i;
}
return -1;
}
The solution should be far shorter than that.
I'll give you some tips because the idea is not give plain solutions and this question I'm pretty sure is homework.
If the question is homework add 'homework' tag
Tips:
Your main method doesn't have any loop. How are gonna iterate over the collections?
Books as objects they are should have an equals method. If that method is correctly implemented (you should define what is a method begin equals to another), then you just can compare b1.equals(b2)
Collections have contains method to check if it contains (by using equals) other book
Math.min method calculates the minimum between two integers or doubles (there are two versions)
Also, to be complete, if your collections are of a certain type your books should implement hashCode in a compatible way with equals (but my guess is you don't know what hashing is yet, and you'll not use that kind of collections)
So first of all: devise an algorithm. The simpler the better. In plain English.
Just then try to implement that. You'll see the code is much simpler and tidy if you try to program a previous well thought concept.
Edit:
Ensure to know what a Collection is in Java world (tip: is a general interface implemented by several distinct more specific classes).
And don't use the word array unless you are using real arrays (and given your exercise proposal it doesn't seem you need it).

How to use equals() to create ArrayList of unique elements

I have to add only unique elements to an ArrayList. How can I override the equals method for that purpose? I want something like:
public boolean equals (Object in) {
if(in == null) {
return false;
}
else if(in instanceof UniqueFruits) {
// ?? check whether the newly added element exists or not
return true;
}
else {
return false;
}
}
How to check whether the newly added element exists or not? I have to check on the Fruit Names.
It sounds like you want to use a Set implementation. If order doesn't matter, just use HashSet. If you want to keep insertion-order, use LinkedHashSet. Or, to maintain natural ordering, use TreeSet.
In any case, make sure to override your element object's equals method. What you have is a good start. After checking in instanceof UniqueFruits, cast in to UniqueFruits:
UniqueFruits uf = (UniqueFruits)in;
You can then check each relevant field using equals in turn (make sure to check for null first if a field is nullable). Any modern IDE will help you generate equals automatically. It may be educational to try it yourself first, then compare with the generated version.
Make sure to override hashCode also (IDEs will similarly help you with this, and there is plenty of reading online about the matter - just search).
If you use TreeSet (or some other SortedSet implementation), your element object should either implement Comparable or else you should provide the SortedSet with a Comparator.
I would suggest using Set instead of ArrayList for storing unique elements. It is easier and cleaner.
As per the javadoc for Set, here's what it says:
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
Here's an SO article that talks about how to remove repeated elements from ArrayList. However, you would notice that most of the solutions provided uses an implementation of Set to achieve what you want to do. In any case, over-riding equals() as you've done will not help achieve your objective. If it's not too much of a trouble (I don't understand why you need to stick with an ArrayList given your actual requirement, I would strongly suggest moving to a Set implementation.
If your fruitname class has 2 attributes, like fruitname and fruitclass then
your check code should be
UniqueFruits current = (UniqueFruits) in;
if(this.fruitname.equals(current.fruitname) && this.fruitclass.equals(current.fruitclass)) {
return true;
}else{
return false;
}
And coming to hashcode ask your IDE to generate the hasdcode for UniqueFruits Class.
And use Set instead of ArrayList
On a lighter note Your IDE can even generate equals() for you.

Adding an element to an ArrayList in the correct postition

I have a custom ArrayList interface that extends the Comparable class and is in ascending order. The class I'm working on is implementing this interface.
My problem is I need to edit the add method so that it will add an element to the ArrayList, have the List stay ordered, and make sure there are no duplicates.
It would be easy to do all this in separate methods, but that's out the question. I need the one method to do it all, so that when the method is called, (as long as it isn't a duplicate) the element is added in the correct position.
On top of that, to check the position of the index to insert the method to, I must use the compareTo() method inherited from the Comparable class. Only problem is I have to implement my own compareTo() method in the class I'm working on. I've looked all over, and I'm confused on how to go about that for this certain class.
Here's my code so far:
public void add(E item) throws IndexOutOfBoundsException {
if (contains(item)) {
throw new IllegalArgumentException("This is a duplicate!");
}
//here is where I need the implementation to add the item to the array, in order
}
Then here is my compareTo() method:
public int compareTo(E item) {
if () {
return -1;
}
else if () {
return 1;
}
else {
return 0;
}
}
One way to do this is to first check if
myArrayList.contains(item)
and then if not, just insert and re-sort your array:
myArrayList.add(item);
Collections.sort(myArrayList);
Note that in general, if you want to maintain a sorted set without duplicates, there are better data structures than ArrayList.
What about a TreeSet? It seems to have the behaviour you're looking for.
You don't give that much information. If what you truly are implementing is an ArrayList like data structure, then you first need to see if the array is large enough to add a new item. If not, you need to create a new array. In the first case, you need to find the location to enter the new element, shift everything from that position on down one, and then add the element. For the second case, you can "merge" the old list with the new element (that is, keep adding from the old list until the location where the new element should go comes up, add the new element, and continue). Another question I have: where is the compareTo(Object o) being put? If you are putting in the ArrayList class, that's rather pointless since you don't really want to compare arrays. If it's in the class being stored in the ArrayList, you want to return -1 if the this object comes before the object passed in, a -1 if the this object comes after, and 0 if they are equal. If you can choose your data structure, you might want to consider a linked list: they are very easy to add to and remove from.
If you are extending the ArrayList class, then this is super (pun intended) easy. In your add method, you have to determine the location to add the element, then call the super.add(int loc) method
Adding an element in the right position is the same as failing to do binary search and recording the last position you compared.
Check out the documentation for Arrays.binarySearch. Hopefully this will give enough information to implement it. Your implementation of comparable should just be the same as you would use for sorting. Here's the relevant excerpt from the documentation:
index of the search key, if it is contained in the array; otherwise, (-(insertion point) - 1). The insertion point is defined as the point at which the key would be inserted into the array: the index of the first element greater than the key, or a.length if all elements in the array are less than the specified key. Note that this guarantees that the return value will be >= 0 if and only if the key is found.

Java: Iterating HashMap - Algorithm needed

I have a list of names, say 8 names: Joe, Bob, Andrew, Bill, Charlie, Sarah, Ann, Victor
The count of names might differ**.
1) What should I use as name list? Hashmap, Vector, Hashtable, List, ArrayList?
2) I need to match them up like this: Joe-Bob, Andrew-Bill, Charlie-Sarah, Ann-Victor. Could you please show me an example how to make a loop which would do so?
Thank you!
Hashmaps have no order. If you want to insert a list of names like you want, you'd do something like the following (with an array):
for(int i = 0; i < myArray.length - 1; i += 2) {
hashMap.add(myArray[i], myArray[i+1]);
}
1) What should I use as name list? Hashmap, Vector, Hashtable, List, ArrayList?
Well, it depends on your needs :) But, because of the question and because you are mixing collection interfaces (e.g. List) and concrete implementations (e.g. ArrayList or Vector), I think that you should start with the basics. An awesome resource for this is the Trail: Collections from The Java(tm) Tutorials, a really highly recommended reading.
First, you need to understand the various collection interfaces and their purpose. Then you'll choose a concrete implementations. The Interfaces section of the tutorial that I'm quoting below will help you for the first step:
The following list describes the core
collection interfaces:
Collection — the root of the collection hierarchy. A collection
represents a group of objects known as
its elements. The Collection interface
is the least common denominator that
all collections implement and is used
to pass collections around and to
manipulate them when maximum
generality is desired. Some types of
collections allow duplicate elements,
and others do not. Some are ordered
and others are unordered. The Java
platform doesn't provide any direct
implementations of this interface but
provides implementations of more
specific subinterfaces, such as Set
and List. Also see The Collection
Interface section.
Set — a collection that cannot contain duplicate elements. This
interface models the mathematical set
abstraction and is used to represent
sets, such as the cards comprising a
poker hand, the courses making up a
student's schedule, or the processes
running on a machine. See also The Set
Interface section.
List — an ordered collection (sometimes called a sequence). Lists
can contain duplicate elements. The
user of a List generally has precise
control over where in the list each
element is inserted and can access
elements by their integer index
(position). If you've used Vector,
you're familiar with the general
flavor of List. Also see The List
Interface section.
Queue — a collection used to hold multiple elements prior to processing.
Besides basic Collection operations, a
Queue provides additional insertion,
extraction, and inspection operations.
Queues typically, but do not
necessarily, order elements in a FIFO
(first-in, first-out) manner. Among
the exceptions are priority queues,
which order elements according to a
supplied comparator or the elements'
natural ordering. Whatever the
ordering used, the head of the queue
is the element that would be removed
by a call to remove or poll. In a FIFO
queue, all new elements are inserted
at the tail of the queue. Other kinds
of queues may use different placement
rules. Every Queue implementation must
specify its ordering properties. Also
see The Queue Interface section.
Map — an object that maps keys to values. A Map cannot contain duplicate
keys; each key can map to at most one
value. If you've used Hashtable,
you're already familiar with the
basics of Map. Also see The Map
Interface section.
In your case, I don't think that you want a Queue, I'm not sure you need a Map, I think you want to allow duplicate elements so you don't want a Set and this leaves us with a List.
Regarding the concrete implementation, if a thread safe implementation is not needed, ArrayList - or LinkedList, depending on the algorithm in 2) - might be a good choice). But really, have a look at the Implementations section of the tutorial to learn more.
2) I need to match them up like this: Joe-Bob, Andrew-Bill, Charlie-Sarah, Ann-Victor. Could you please show me an example how to make a loop which would do so?
If the initial list can contain duplicate elements, I wouldn't use a Map to store matched names (because a Map cannot contain duplicate keys). So, I'd create a Couple class to store associated names:
public class Couple {
private name1;
private name2;
...
}
and use a List<Couple> to store matched names. But, because the logic of the algorithm is still not clear (does the initial list always contain an odd number of elements? is one element always associated with the immediate next one?), I can't provide more guidance.
It's not exactly clear what you mean by "associating" the names and what you need to do with them. Also you don't say whether the names are unique.
You can pair names this way:
String input = "Joe, Bob, Andrew, Bill, Charlie, Sarah, Ann, Victor";
String names[] = input.split(",");
Map<String, String> output = new HashMap<String, String>();
for (int i=0; i<names.length; i+=2) {
output.put(names[i].trim(), names[i+1].trim());
}
and then do:
output.remove("Joe"); // Joe is paired with Bob
If you also need to associate Bob with Joe, you could do it this way:
String input = "Joe, Bob, Andrew, Bill, Charlie, Sarah, Ann, Victor";
String names[] = input.split(",");
Map<String, String> output = new HashMap<String, String>();
for (int i=0; i<names.length; i+=2) {
String first = names[i].trim();
String second = names[i+1].trim();
output.put(first, second);
output.put(second, first);
}
and then do:
String other = output.remove("Joe");
output.remove(other);
Your choice should depend on the purpose for which you want these names. Do you want efficient search over the list for a given last name? If yes, then you should follow Anon's proposal.
Otherwise, you could just create a class where you could keep information about each person (first name, last name, telephone, etc.) and use a Vector to keep the instances of this class. An example of this class could be:
class PersonDetails {
String firstName;
String lastName;
public PersonDetails(String fn, String ln) {
firstName = fn;
lastName = ln;
}
}
For the insertion of the names in the vector you could use something like the following:
for(int i = 0; i < nameArray.length; i += 2) {
vector.add(new PersonDetails(nameArray[i], nameArray[i+1]));
}
If you have an array to start with but want to remove elements as you process them, first convert it to a List of some kind. Also, removing items from the beginning of an ArrayList can be very expensive, so if you have a lot of names, you may want to use either a LinkedList (though there are very few reasons to actually use this between performance and memory utilization, it is better to have a circular List but this does not come standard with Java).
So, in your case, you know you will be processing the list sequentially so the most efficient I can think of is to create an ArrayList and reverse it then remove from the end, like this:
private Map matchNames(String[] names) {
List namesList = new ArrayList(Arrays.asList(names));
Collections.reverse(namesList);
Map result = new HashMap(namesList.size() / 2);
while (!namesList.isEmpty()) {
result.put(namesList.remove(namesList.size() - 1),
namesList.remove(namesList.size() - 1));
}
return result;
}

Categories

Resources