Java - list iterator not working on IdentityHashMap - java

I am using an identity hash map to keep track of objects that I have seen before in a custom serializer. I have a while loop in which I attempt to iterate over entries in the map. During the method call addAllFields its possible that objects are added to the keyset of the map. After some debugging I have noticed that my iterator.hasNext() returns false even when I know for a fact that object have been added to the map since the last loop body execution. What am I doing wrong here?
public Document serialize(Object obj) throws Exception {
reset();
addToMap(obj);
Set<Object> keys = map.keySet();
Iterator<Object> iterator = keys.iterator();
while(iterator.hasNext()) {
Object key = iterator.next();
Element objectElement = createObjectElement(key.getClass().getName(), map.get(key));
addAllFields(objectElement, key);
document.getRootElement().addContent(objectElement);
}
return document;
}

You need to use ListIterator, and then if you want to iterate over added elements too you need to do the whole thing in reverse:
ListIterator<Object> iter = keys.listIterator(keys.size());
while (iter.hasPrevious()){
Object prev=iter.previous();
// do your Element creation here
// then use the iter.add() method
iter.add(/*new object*/);
}
NB you may need to wrap you keyset in a different collection type as not all support ListIterator, and will probably need to pass iter as an argument to #addAllFields()
You will likely also need to iterate over entry set rather than keySet

Related

Java Iterator infinite loop iterates only the 1st item in hashmap

I am executing a database query, and as result I get a HashMap. I want to iterate through all the results, but I infinitely add the first item from the result to the arraylist.
QueryResult result=engine.query(query,params);
while(result.iterator().hasNext()) {
HashMap res= (HashMap)result.iterator().next();
Node node=(Node)res.get("n");
results.add(new BusyProfile(node));
}
How to iterate through each object and why do I have infinite loop? Thanks!
Everytime you call result.iterator(), a new Iterator is created, pointing to the first item.
So create it before your loop:
Iterator<?> it = result.iterator();
while (it.hasNext()) {
HashMap res = (HashMap)it.next();
//...
}
You are overwriting your iterator! When result.iterator() is called, you create your iterator but on each iteration, it's creating a new one and it continues to point to the beginning - causing the infinite loop.
What you need to do in this case is save the iterator and then use it to move through the collection.
QueryResult result = engine.query(query,params);
//Save iterator
Iterator i = result.iterator();
while(i.hasNext()) {
HashMap res = (HashMap)i.next();
Node node = (Node)res.get("n");
results.add(new BusyProfile(node));
}
Before you can access a collection through an iterator, you must
obtain one. Each of the collection classes provides an iterator( )
method that returns an iterator to the start of the collection. By
using this iterator object, you can access each element in the
collection, one element at a time.
In general, to use an iterator to cycle through the contents of a
collection, follow these steps −
Obtain an iterator to the start of the collection by calling the
collection's iterator( ) method.
Set up a loop that makes a call to hasNext( ). Have the loop iterate
as long as hasNext( ) returns true.
Within the loop, obtain each element by calling next( ).
There is a quick tutorial here:
https://www.tutorialspoint.com/java/java_using_iterator.htm
All answers posted before mine explained that result.iterator() will instantiate a new iterator each time it is invoked.
And it makes not sense to create an iterator during each iteration for the same iterator that you are iterating with : while(result.iterator().hasNext()) {
.It is right.
Beyond this misuse of the Iterator, you should read the javadoc of the class you are using. It may often help you to create a more effective code.
According to the javadoc of org.neo4j.ogm.session.result.QueryResult, the
QueryResultclass implements the Iterable interface in this way Iterable<Map<String,Object>>.
So instead of doing thing more complicated than required, just use an enhanced for.
It would produce a shorter and more readable code.
Besides, using the more restricted scope for a variable is better as it prevents to use it unsuitably.
With the enhanced for, you don't need any longer to declare the iterator before the loop.
It will be used in the compiled class (as enhanced for uses under the hood an iterator) but it will be restricted to the scope of the loop.
So you should really consider this way :
QueryResult result = engine.query(query,params);
for (Map<String,Object> currentMap : result) {
Node node = (Node) currentMap.get("n");
results.add(new BusyProfile(node));
}
You should call the iterator() method once and then store (and use) the returned value.
Reuse the Iterator
Iterator i = result.iterator();
if(i.hasNext()) {
HashMap res= (HashMap)i.next();
Node node=(Node)res.get("n");
results.add(new BusyProfile(node));
}

method get(int) is undefined for the type Set

I have written a piece of logic, where, my goal is to select random keys from the map. The map has been assigned as a ConcurrentSkipListMap<Text,IntWritable> tuples. Also, I don't have any industrial level experience with Java, so struggling to get over this.
The piece of code is
Set<Text> keys = tuples.keySet();
String randomKeys = keys.get(random.netInt(keys.size()))
I am not able to figure out the error statement: method get(int) is undefined for the type Set<Text>
Also, I have searched for the similar problem, and a solution do exist using List, but my key/value pairs are stored as ConcurrentSkipListMap, so not able to figure out this.
Thanks.
First, Set doesn't have a get() method. You could use a List. Also, it's Random.nextInt()
List<Text> keys = new ArrayList<>(tuples.keySet());
Text randomKeys = keys.get(random.nextInt(keys.size()));
Also, depending on your use-case I believe it would likely be better to Collections.shuffle(List) once and then iterate it (perhaps with a for-each loop) like
List<Text> keys = new ArrayList<>(tuples.keySet());
Collections.shuffle(keys);
for (String randomKey : keys) {
// ...
}
A Set does not have an get() method, because its not array like.
you normaly can iterate through an Set with its iterator: set.iteraor().next()
The iterator interface implements the folowing methods:
public boolean hasNext()
public E next()
public void remove()
you can use it like here (in this case is an Set):
Iterator it = set.iterator();
while (it.hasNext())
{
String setText = (String) it.next();
System.out.println(setText);
}

How to iterate over a SortedSet to modify items within

lets say I have an List. There is no problem to modify list's item in for loop:
for (int i = 0; i < list.size(); i++) { list.get(i).setId(i); }
But I have a SortedSet instead of list. How can I do the same with it?
Thank you
First of all, Set assumes that its elements are immutable (actually, mutable elements are permitted, but they must adhere to a very specific contract, which I doubt your class does).
This means that generally you can't modify a set element in-place like you're doing with the list.
The two basic operations that a Set supports are the addition and removal of elements. A modification can be thought of as a removal of the old element followed by the addition of the new one:
You can take care of the removals while you're iterating, by using Iterator.remove();
You could accumulate the additions in a separate container and call Set.addAll() at the end.
You cannot modify set's key, because it causes the set rehasing/reordering. So, it will be undefined behaviour how the iteration will run further.
You could remove elements using iterator.remove(). But you cannot add elements, usually better solution is to accumulate them in a new collection and addAll it after the iteration.
Set mySet = ...;
ArrayList newElems = new ArrayList();
for(final Iterator it = mySet.iterator(); it.hasNext(); )
{
Object elem = it.next();
if(...)
newElems.add(...);
else if(...)
it.remove();
...
}
mySet.addAll(newElems);
Since Java 1.6, you're able to use a NavigableSet.
You should use an Iterator or better still the enhanced for-loop syntax (which depends on the class implementing the Iterable interface), irrespective of the Collection you're using. This abstracts away the mechanism used to traverse the collection and allows a new implementation to be substituted in without affecting the iteration routine.
For example:
Set<Foo> set = ...
// Enhanced for-loop syntax
for (Foo foo : set) {
// ...
}
// Iterator approach
Iterator it = set.iterator();
while (it.hasNext()) {
Foo foo = it.next();
}
EDIT
Kan makes a good point regarding modifying the item's key. Assuming that your class's equals() and hashCode() methods are based solely on the "id" attribute (which you're changing) the safest approach would be to explicitly remove these from the Set as you iterate and add them to an "output" Set; e.g.
SortedSet<Foo> input = ...
SortedSet<Foo> output = new TreeSet<Foo>();
Iterator<Foo> it = input.iterator();
while (it.hasNext()) {
Foo foo = it.next();
it.remove(); // Remove from input set before updating ID.
foo.setId(1);
output.add(foo); // Add to output set.
}
You cannot do that. But you may try, maybe you'll succeed, maybe you'll get ConcurrentModificationException. It's very important to remember, that modifying elements while iterating may have unexpected results. You should instead collect that elements in some collection. And after the iteration modify them one by one.
This will only work, if id is not used for equals, or the comperator you used for the sorted set:
int counter = 0;
for(ElementFoo e : set) {
e.setId(counter);
couter++;
}

How can I extract ArrayList from HashMap and loop through it in Java?

I have set up a HashMap like so:
Map<String, ArrayList<String>> theAccused = new HashMap<String, ArrayList<String>>();
... and I populate this by storing for every name (key), a list of names (value). So:
ArrayList<String> saAccused = new ArrayList<String>();
// populate 'saAccused' ArrayList
...
// done populating
theAccused.put(sAccuser, saAccused);
So now, I want to look through all of the entries in the HashMap and see if (for each 'sAccuser'), the list 'saAccused' contains a certain name. This is my failed attempt so far:
Set<String> setAccusers = theAccused.keySet();
Iterator<String> iterAccusers = setAccusers.iterator();
iterAccusers.next();
ArrayList<String> saTheAccused;
// check if 'sAccuser' has been accused by anyone before
for (int i = 0; i < theAccused.size(); i++) {
saTheAccused = theAccused.get(iterAccusers);
if (saTheAccused.contains(sAccuser)) {
}
iterAccusers.next();
}
... however I'm not sure how the Set and Iterator classes work :/ The problem is that I don't have the "values"... the names... the 'sAccuser's... for the HashMap available.
In a nutshell, I want to iterate through the HashMap and check if a specific name is stored in any of the lists. So how can I do this? Let me know if you need me to go into further detail or clear up any confusion.
Thanks.
In a nutshell, I want to iterate through the HashMap and check if a specific name is stored in any of the lists. So how can I do this?
There's two ways of iterating through the map that might be of interest here. Firstly, you can iterate through all of the mappings (i.e. pairs of key-value relations) using the entrySet() method, which will let you know what the key is for each arraylist. Alternatively, if you don't need the key, you can simply get all of the lists in turn via the values() method. Using the first option might look something like this:
for (Map.Entry<String, ArrayList<String>> entry : theAccused.entrySet())
{
String sListName = entry.getKey();
ArrayList<String> saAccused = entry.getValue();
if (saAccused.contains(sAccuser))
{
// Fire your logic for when you find a match, which can
// depend on the list's key (name) as well
}
}
To answer the broader questions - the Set interface simply represents an (unordered) collection of non-duplicated values. As you can see by the linked Javadoc, there are methods available that you might expect for such an unordered collection. An Iterator is an object that traverses some data structure presenting each element in turn. Typical usage of an iterator would look something like the following:
Iterator<?> it = ...; // get the iterator somehow; often by calling iterator() on a Collection
while (it.hasNext())
{
Object obj = it.next();
// Do something with the obj
}
that is, check whether the iterator is nonexhausted (has more elements) then call the next() method to get that element. However, since the above pattern is so common, it can be elided with Java 5's foreach loop, sparing you from dealing with the iterator itself, as I took advantage of in my first example.
Something like this?
for (List<String> list : theAccused.values()) {
if (list.contains("somename")) {
// found somename
}
}
This should make it work:
saTheAccused = theAccused.get(iterAccused.next());
However, to make your code more readable, you can have either:
for (List<String> values : theAccused.values()) {
if (value.contains(sAcuser)) {
..
}
}
or, if you need the key:
for (String key : theAccused.keySet()) {
List<String> accused = theAccused.get(key);
if (accused.contains(sAccuser)) {
}
}
You need to use the value from Iterator.next() to index into the Map.
String key = iterAccusers.next();
saTheAccused = theAccused.get(key);
Currently you're getting values from the Map based on the iterator, not the values returned by the iterator.
It sounds like you need to do two things: first, find out if a given name is "accused", and second, find out who the accuser is. For that, you need to iterate over the Entry objects within your Map.
for (Entry<String, List<String>> entry : theAccused.entrySet()) {
if (entry.getValue().contains(accused)) {
return entry.getKey();
}
}
return null; // Or throw NullPointerException, or whatever.
In this loop, the Entry object holds a single key-value mapping. So entry.getValue() contains the list of accused, and entry.getKey() contains their accuser.
Make a method that does it:
private String findListWithKeyword(Map<String, ArrayList<String>> map, String keyword) {
Iterator<String> iterAccusers = map.keySet().iterator();
while(iterAccusers.hasNext()) {
String key = iterAccusers.next();
ArrayList<String> list = theAccused.get(key);
if (list.contains(keyword)) {
return key;
}
}
}
And when you call the method:
String key = findListWithKeyword(map, "foobar");
ArrayList<String> theCorrectList = map.get(key);

Hashmap.keySet(), foreach, and remove

I know that it's typically a big no-no to remove from a list using java's "foreach" and that one should use iterator.remove(). But is it safe to remove() if I'm looping over a HashMap's keySet()? Like this:
for(String key : map.keySet()) {
Node n = map.get(key).optimize();
if(n == null) {
map.remove(key);
} else {
map.put(key, n);
}
}
EDIT:
I hadn't noticed that you weren't really adding to the map - you were just changing the value within the entry. In this case, pstanton's (pre-edit1) solution is nearly right, but you should call setValue on the entry returned by the iterator, rather than calling map.put. (It's possible that map.put will work, but I don't believe it's guaranteed - whereas the docs state that entry.setValue will work.)
for (Iterator<Map.Entry<String, Node>> it = map.entrySet().iterator();
it.hasNext();)
{
Map.Entry<String, Node> entry = it.next();
Node n = entry.getValue().optimize();
if(n == null)
{
it.remove();
}
else
{
entry.setValue(n);
}
}
(It's a shame that entry doesn't have a remove method, otherwise you could still use the enhanced for loop syntax, making it somewhat less clunky.)
Old answer
(I've left this here for the more general case where you just want to make arbitrary modifications.)
No - you should neither add to the map nor remove from it directly. The set returned by HashSet.keySet() is a view onto the keys, not a snapshot.
You can remove via the iterator, although that requires that you use the iterator explicitly instead of via an enhanced for loop.
One simple option is to create a new set from the original:
for (String key : new HashSet<String>(map.keySet())) {
...
}
At this point you're fine, because you're not making any changes to the set.
EDIT: Yes, you can definitely remove elements via the key set iterator. From the docs for HashMap.keySet():
The set supports element removal,
which removes the corresponding
mapping from the map, via the
Iterator.remove, Set.remove,
removeAll, retainAll, and clear
operations. It does not support the
add or addAll operations.
This is even specified within the Map interface itself.
1 I decided to edit my answer rather than just commenting on psanton's, as I figured the extra information I'd got for similar-but-distinct situations was sufficiently useful to merit this answer staying.
you should use the entry set:
for(Iterator<Map.Entry<String, Node>> it = map.entrySet().iterator(); it.hasNext();)
{
Map.Entry<String, Node> entry = it.next();
Node n = entry.getValue().optimize();
if(n == null)
it.remove();
else
entry.setValue(n);
}
EDIT fixed code

Categories

Resources