GobiasKoffi
GobiasKoffi

Reputation: 4084

Guava iterators, and iterating over a list within a list object

I have the following example code which consists of 3-nested for-loops.

for(Continent continent : continentList) 
{
    for(Country country : continent.getCountries())
    {
        for(City city : country.getCities())
        {
            //Do stuff with city objects
        }
    }
}

Is there any way to mimic this nested for-loop using Guava and iterators? I've been trying to find a suitable example without much luck, and I was wondering if someone could help me out? A coworker of mine mentioned using filters.

EDIT: Fixed tiny bug in example code

Upvotes: 14

Views: 14424

Answers (5)

RikH
RikH

Reputation: 151

You can define static functions for:
• getCountries() in Continent, Continents or Functions
• getCities() in Country, Countries or Functions

Now you can do something like...

FluentIterable.from(continentList)
    .transformAndConcat(Continent.getCountriesFunction())
    .transformAndConcat(Country.getCitiesFunction())
    . //filter //tranform //find //toList() //etc.

If:
• You use Guava like this (more) often.
• And have certain rules/thoughts over where you define your Functions and Predicates.
• And have varying (complex) things to filter or search for.
Then it can be a great boon and can make many situations quite a bit easier. I know I'm glad I did.

If you use it sparsely, then I'll have to agree with @Louis Wasserman. Then it's not worth the hassle. Also, defining Functions and Predicates as an anonymous inner class like the other examples... is really ugly.

Upvotes: 8

Sean Patrick Floyd
Sean Patrick Floyd

Reputation: 299218

I agree with the others in that the nested loops are the most efficient way to go. However: I'd extract each loop level to a separate method to both maintain readability and make sure that each method does exactly one thing:

public void doStuffWithWorld(World world){
    for (Continent continent : world.getContinents()) {
        doStuffWithContinent(continent);
    }
}

private void doStuffWithContinent(Continent continent) {
    for (Country country : continent.getCountries()) {
        doStuffWithCountry(country);
    }
}

private void doStuffWithCountry(Country country) {
    for(City city : country.getCities()){
        doStuffWithCity(city);
    }
}

private void doStuffWithCity(City city) {
    // do stuff here
}

And if you need to carry some state through the different levels, you have several options: put them in member fields of the containing class, pass a second parameter to all the methods which can either be a map or a custom object.

Upvotes: 1

ig0774
ig0774

Reputation: 41287

As Peter Lawrey commented, this is almost certainly going to be simpler as nested loops. More over, the Guava documentation gives this warning:

Imperative code should be your default, your first choice as of Java 7. You should not use functional idioms unless you are absolutely sure of one of the following:

  • Use of functional idioms will result in net savings of lines of code for your entire project. Moving the definition of a function to another file, or a constant, does not help.
  • For efficiency, you need a lazily computed view of the transformed collection and cannot settle for an explicitly computed collection. Additionally, you have read and reread Effective Java, item 55, and besides following those instructions, you have actually done benchmarking to prove that this version is faster, and can cite numbers to prove it.

Please be sure, when using Guava's functional utilities, that the traditional imperative way of doing things isn't more readable. Try writing it out. Was that so bad? Was that more readable than the preposterously awkward functional approach you were about to try?

However, if you're insistent on ignoring advice, you could use something like this monstrosity (note I haven't actually tried to compile or run this):

FluentIterable.from(continentList)
    .transform(new Function<Continent, Void>() {
        public Void apply(Continent continent) {
            return FluentIterable.from(continent.getCountries())
                .transform(new Function<Country, Void>() {
                    public Void apply(Country country) {
                        return FluentIterable.from(country.getCities())
                            .transform(new Function<City, Void>() {
                                public Void apply(City city) {
                                    // do stuff with city object
                                    return null;
                                }
                            });
                    }
                });
        }
    });

Now ask yourself: Which would you want to maintain? Which is going to be the most efficient?

There are valid use-cases for Guava's functional idiom. Replacing Java for loops, even nested for loops, is not one of them.

Upvotes: 12

Olivier Gr&#233;goire
Olivier Gr&#233;goire

Reputation: 35477

Another monstrosity, using AbstractIterator:

    class CityIterable implements Iterable<City> {
        List<Continent> continents;

        CityIterable(List<Continent> continents) {
            this.continents = continents;
        }

        @Override
        public Iterator<City> iterator() {
            return new AbstractIterator<City>() {
                Iterator<Continent> continentIterator = continents.iterator();
                Iterator<Country> countryIterator;
                Iterator<City> cityIterator;

                @Override
                protected City computeNext() {
                    if (cityIterator != null && cityIterator.hasNext()) {
                        return cityIterator.next();
                    }
                    if (countryIterator != null && countryIterator.hasNext()) {
                        cityIterator = countryIterator.next().getCities().iterator();
                        return computeNext();
                    }
                    if (continentIterator.hasNext()) {
                        countryIterator = continentIterator.next().getCountries().iterator();
                        return computeNext();
                    }
                    return endOfData();
                }
            };
        }
    }

Then calling it:

    for (City city: new CityIterable(continentList)) {
        System.out.println(city.name);
    }

Given how this monstruosity, follow ig0774's advice and keep the nested loops.

P.S. No need for filters.

Upvotes: 3

artbristol
artbristol

Reputation: 32437

No, there isn't an easy way. Also, it would be more verbose than the for-each loop in your question.

See http://code.google.com/p/guava-libraries/issues/detail?id=218#c5 and the caveats in http://code.google.com/p/guava-libraries/wiki/FunctionalExplained

Upvotes: 2

Related Questions