How can I access elements in a java collection beyond my iterator without "losing my place"?
I have created a class called Month that extends Calendar, which I am using to hold events for a given month. I have several years worth of Month objects stored in a TreeSet. Most of the events I want to record last for several months and are specified only by their start month (and a duration, in months). I want to be able to do this:
for ( Event e : events )
{
for ( Month aMonth : myMonths )
{
// check if this is a start month for e
// if it is, call aMonth.addEvent(e);
// and also add e to the next e.getDuration() months of myMonths
// and then start checking again from the month where we called addEvent(e)
// in case there is another occurrence of e starting
// before the first one has finished
}
}
It's the part in capitals I'm having trouble with. I tried using an iterator instead of a foreach loop, and having a separate loop when a start date was found which used the iterator to add e to the next x months, but I couldn't then get the iterator back to where it started. It looks like ListIterator has a previous() method, but I wanted to use a SortedSet rather than a List, to ensure duplicates are avoided (although perhaps this inclination is wrong?)
It feels like it would be much easier to do this with a plain old array, but the collection is useful for other parts of the program. Perhaps I can use multiple iterators and just "use them up" as needed for these forays into months just beyond my main "bookmark" iterator? Doesn't exactly seem开发者_StackOverflow elegant though. Or is there a hack for "peeking" beyond where my iterator is actually pointing?
I am quite new to all this, so I may just be making a design error. All advice welcomed!
What's do the Event and Month objects look like? I think what you had will work:
for(Event event : events) {
for(Month aMonth : myMonths) {
if(aMonth >= event.startMonth && aMonth <= event.startMonth+event.duration) {
aMonth.add(event);
}
}
}
Alternatively you could flip it around and go the other way, make your outer iterator the Months and your inner iterator the Events. That should work too, the if() condition would probably be the same.
Depending on how many events and months you're dealing with at a time, the naive approach may well be the best. I'd start by just letting your for
loops handle the iterators, and accepting the fact that you'll be doing (m*n) iterations. Then if you find that this spot is causing a significant slow-down you can try a few other techniques to speed things up without making your code overly complex.
Trying to seek ahead and back will make your code difficult to understand and more prone to bugs, without necessarily gaining you much in terms of performance. Usually you won't notice a significant difference in performance until you're talking about at least hundreds of items in both collections (in which case you could start with something simple like breaking your data up into years, for example, to reduce the overhead of the double-nested for
loops).
Edit
However, since I just can't help myself, here's a semi-elegant strategy that will take advantage of the fact that your events and months are both stored in ascending order (I'm assuming events are stored in order of their start date). It uses a LinkedList (which is very efficient at adding and removing elements from the front and back of the list) to keep track of which months the current event might span, and then breaks as soon as it finds a month that the event doesn't include:
LinkedList<Month> monthList = new LinkedList<Month>();
var i = monthList.getIterator();
for(Event ev : events)
{
shiftList(monthList, i, ev);
for(Month m : monthList)
{
if (!isInMonth(ev, m)) break;
m.addEvent(ev);
}
}
...
// Remove months that are not in scope from the front of the list.
// Add months that are in scope to the end of the list
public void shiftList(LinkedList<Month> monthList, Iterator<Month> i, Event ev)
{
while(!monthList.size() > 0 && !isInMonth(ev, monthList.getFirst()))
{
monthList.removeFirst();
}
while(i.hasNext() && isInMonth(ev, monthList.getLast()))
{
monthList.addLast(i.next());
}
}
Again, you can see how much more complicated this is: it's very likely I introduced a bug on this logic, and I wouldn't feel comfortable using this in production without thorough unit-testing. You're generally much better off just keeping it simple until you have a compelling reason to optimize.
Google's guava library provides a PeekingIterator
that allows single-element peekahead. Create one via Iterators.peekingIterator(Iterator)
.
精彩评论