Searching a List - java

Lets say I have a list and I am trying to look up a Class element in it, from which I only know one (unique) attribute.
public static List<Achievement> listAchievements;
String idAchievement = "5764e35";
This is obviously not working
listAchievements.indexOf(idAchievement );
Neither is this
Achievement ach(idAchievement);
listAchievements.getIndexOf(ach);
and the workaround is not pretty
for (Achievement achievement : listAchievements) {
if (achievement.resourceID().equalsIgnoreCase(idAchievement)) {
// STUFF
break;
}
}

What you have isn't a workaround, it's the solution.
You could abstractify it with lambda-like behavior, and this and that... but in the end, if you're trying to search a list for an element that has a given attribute, there's nothing you can do but to iterate over the list until you find an element with that given attribute.
If you need to find Acchievments by ID more directly, a Map<String,Achievement> might be a better choice if the IDs are unique (which you say they are).

There is no other way than to loop over the elements until you find the one you're looking for. You could use Guava's support for predicates:
Achievement a = Iterables.find(list, new Predicate<Achievement>() {
#Override
public boolean apply(Achievement input) {
return input.resourceID().equalsIgnoreCase(idAchievement)
}
});
but the end result is the same.
Or you could maintain a separate Map<String, Achievement> in addition to your list, or use a LinkedHashMap<String, Achievement> instead of your list, which would achieve O(1) search instead of O(n).

1) you have to sort your list using static void sort(List,Comparator).
2) use static int binarySearch(List,Key,Comparator).
These two method are of java.util.Collections

If you only have a list there's not much more to do. But if this is something that is done often you may want to consider creating a HashMap or similar instead (from achievementId to achievement).

To make such efficient you'll either have to use Map of idAchievement to Achievement, or make sure your collection is sorted by idAchievement attribute and then use Collections.binarySearch().

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.

Efficiently removing an item from Java LinkedList

Here is the pseudo-code I am using to remove an item from the linked-list:
public static void removeByID(LinkedList<Fruit> fruits, int fruitID) {
for(Fruit f : fruits) {
if (f.ID == fruitID) {
fruits.remove(f);
return;
}
}
}
I am thinking this is not very efficient as fruits.remove() will once again iterate over the list. Wondering if there is a better way to achieve this.
For a java.util.LinkedList, use the Iterator.
Iterator<Fruit> it = fruits.iterator();
while(it.hasNext()) {
if(it.next().ID == fruitID) {
it.remove();
break;
}
}
This will result in only a single traversal. The Iterator returned has access to the underlying link structure and can perform a removal without iterating.
The Iterator is implicitly used anyway when you use the for-each loop form. You'd just be retaining the reference to it so you can make use of its functionality.
You may also use listIterator for O(n) insertions.
Nope, not in terms of asymptotic complexity. That's the price you pay for using a LinkedList: removals require a traversal over the list. If you want something more efficient, you need to use a different data structure.
You're in fact doing two traversals here if you've got a singly linked list: the .remove() call needs to find the parent of the given node, which it can't do without another traversal.
If you need to access elements from a collection that have a unique attribute, it is better to use a HashMap instead, with that attribute as key.
Map<Integer, Fruit> fruits = new HashMap<Integer, Fruit>();
// ...
Fruit f = fruits.remove(fruitID);
As stated in the above linked lists generally need traversal for any operation. Avoiding multiple traversal can probably be done with an iterator. Although, if you are able to relate fruit to fruit.ID ahead of time you may be able to speed up your operations because you can a void the slow iterative look up. This will still require a different data structure, namely a Map (Hashmap probably).
Regarding to your post, using a HashMap appears to a good solution.
In addition, if we suppose that you need also to search a fruit using the fruitID into your set, HashMap will make the search time barely constant.
Regarding complexity, you can find additional information on Simple Notions article depending the data structure that you use.

How to check whether a HashMap has all the elements of an ArrayList?

If I have a HashMap hashM, an ArrayList arrayL. If I would like to use an if statement to check whether hashM has all the elements in arrayL, how can I do that?
I cm currently using something like
if (hashM.values().containsAll(arrayL.getPreReqs()))
However it doesn't work properly.
Dear all thanks for the answers!
Actually containsAll works however the way I structure the my codes is wrong so that I got wrong outcomes. Now it has been fixed.
Cheers!
Given
Map<?,?> map = new HashMap<?,?>();
List<?> list = new ArrayList<?>();
The approach you tried (well, nearly, as pointed out by Marko Topolnik) is indeed correct:
if (map.values().containsAll(list)) { ... }
(Or map.keySet().containsAll(list) if you were interested in the map keys instead of values.)
For this to work as expected for custom types, you of course must have implemented equals() and hashcode() correctly for them. (See e.g. this question or better yet, read Item 9 in Effective Java.)
By the way, when working with Java Collections, it is good practice to define fields and variables using the interfaces (such as List, Set, Map), not implementation types (e.g. ArrayList, HashSet, HashMap). For example:
List<String> list = new ArrayList<String>();
Map<Integer, String> map = new HashMap<Integer, String>();
Similarly, a more "correct" or fluent title for your question would have been "How to check whether a Map has all the elements of a List?". Check out the Java Collections tutorial for more info.
Your code is correct except..
if (hashM.values().containsAll(arrayL)) {....}
[EDIT]
You can use HashMap.containsValue(Object value)
public boolean containsList(HashMap<K, V> map, List<V> list) {
for(V value : list) {
if(!map.containsValue(value)) {
return false;
}
}
return true;
}
Your code should work - but will not be particularly efficient. You need to compare every element in the list with every element in the map.
If (and only if) you can easily extract the key of the map from the elements then you would be better off looping through your List and for each element do map.containsKey(getKey(elem)), this will be much faster.
If you are doing this sort of comparison a lot and you cannot map from element to key then it may be worth keeping a HashSet of the values for this purpose.
I agree with JoniK. This can be done in a single line like this.
if(hashM.values().containsAll(arrayL)) {// put your code here that will be returned}

How better convert all Set elements in java?

I have set like:
Set<String>
I need on each element of Set make split by ; and create new Set that will contain only 2-nd element. Should I make it directly one by one or exists better way?
Thanks.
If you can relax your constraint of an output being a Set<String> to being a Collection<String> you could use Guava and defer the transformation of elements until enumeration of elements through the Collections2#transform() method. You would just have to write a custom function to perform the split on an individual element.
But if you cannot/should not relax this constraint, you are best left to doing the already proposed individual iterations (as it'd be much more legible).
Code would look something like:
Set<String> input; //Given
Collection<String> output = Collections2.transform(input, new Function<String,String>() {
#Override
public String apply (String element) {
// As JohnnyO says, add appropriate edge case checking...
return element.split(";")[1];
}
});
Set<String> suffixSet = new HashSet<String>();
for (String s : inputSet) {
suffixSet.add(s.split(";")[1])
}
I'd also add appropriate error checking and handling for the case when s does not have a ; present.
As you have not shown the code, we can only guess what you're trying to do. You need to iterate through the Set and split each String. You can use split method if you want.
It is hard to say with so little information. You could iterate over the set doing split and adding to another Set.
Or you could replace Set with a e.g. HashMap and when you create the map put as key the first part of the string and as value the second so that you can retrieve the second part when you need fast.
Or if you create the strings yourself place them in different sets directly
Or...(you don't say enough) to provide more options

Changing parameters in a LinkedList

I'm new to java so im having some "annoying" problems. I have a class Employee which contains an int idNumber and a int phone number. Then I have a LinkedList<Employee> sorted by idNumber. I want to change the phonenumber of a certain idnumber.
I've been working with Iterators but i don't know if i'm doing it right, which I doubt.
public void setNewPhoneNumber(int idnumber, int newphone){
Iterator<IndexC> it = listEmployee.iterator();
IndexC employeeTemp = null;
boolean found = false;
while(it.hasNext() && !found){
employeeTemp = it.next();
if(employee.getIdNumber()== idnumber){
employeeTemp.setNewPhoneNumber(newphone);
found = true;
}
}
}
Yeah, I know employee.setNewPhoneNumber is wrong, but I don't know which the correct way change the value on the linkedlist. (Sorry for the bad english, not a native speaker)
Iterators are a pain; the foreach construct is a lot nicer:
public void setNewPhoneNumber(int idnumber, int newphone) {
for (Employee employee : listEmployee)
if (employee.getIdNumber() == idnumber) {
employee.setNewPhoneNumber(newphone);
return;
}
}
I'm not clear on what IndexC is, and I don't often use LinkedList - there could be some subtlety here I'm missing - but I think you're better off avoiding the iterators.
You're not "changing parameters in a Linked List", you're trying to find an object in a list and change a property of that object
You should be using a Map (such as a
HashMap) instead of a List, then you
won't have to iterate.
If you iterate, use the for loop: for(IndexC employeeTemp: employeeTemp){}
Changing the phone numer would conventionally be done through a setPhoneNubmer() method, but it depends entirely on the IndexC class whether it has such a method. Look at that class's definition.
When asking a question, always include error messages! "It doesn't work" is a really useless piece of information.
my bad, IndexC is the Employee class, "bad copy past" sorry. I don't like LinkedList but i have to use it with +5000 entries (School Exercise). I don't think using for's with so many entries is recommended.
The class as set's, get's, clones..
class Manager{
private LinkedList<Employee> listE = new LinkedList<Emploee>;
public void setNewPhoneNumber(int idnumber, int newphone)
}
One reason for it not to work is that there´s no IndexC in the list that satisfies (employee.getIdNumber()== idnumber).
Maybe you should post some extra code, for example, where is that list created, have you filled it with anything?
Besides, what is it that doesn´t work? The setting of the new phone number, or retrieving the element from the list?
In both cases, i think that you should post both methods, that is
getIdNumber();
As Mike B. says, maybe using a Map implementation would be better. Since you are considering order, maybe a SortedMap (such as TreeMap) implementation could be better.
In any case, rember you have to override two methods in your IndexC (when using maps). Otherwise, things will get messy.
equals
hashCode
http://java.sun.com/j2se/1.4.2/docs/api/java/util/LinkedList.html
You want to use a for loop with an int incrementing till you find the object you want. Then you want to use listEmployee.get() to get the object you want and edit it.
However, if you need random access to items like that, then you should not be using Linkedlists. Stick it in an ArrayList instead. That has much better random access time.
As a side note, you don't even need the for loop if the id numbers are in order from 0-whatever. You can simply listEmployee.get(idNumber)

Categories

Resources