Home > Back-end >  Simulating nested for-loop filtering using Streams
Simulating nested for-loop filtering using Streams

Time:03-27

I'm working on a personal project to do with Pokemon. The fact that this program has to do with Pokemon isn't strictly important, but I think it will be easier to understand in Pokemon-related terms, so I'll explain it that way and anybody answering who doesn't know much about Pokemon can feel free to use more general terms if they'd want to:

I have a PokemonRecord record which has 2 parameters for 2 PokemonTypes. Any instance of PokemonRecord can have either 1 or 2 types. If the PokemonRecord has only 1 type, then type2 == null. The following method's purpose is to take an array of PokemonTypes and generate a list of all possible PokemonRecord type combinations that will resist all of those given types. If you don't know what "resisting a type" means, then you can just think of this as a separate method which tests a condition and returns a boolean.

This is a sample of the expected output:

Enter the name of a Pokemon type: water
Enter the name of a Pokemon type: ground
Enter the name of a Pokemon type: grass
Enter the name of a Pokemon type: done

The following type combinations resist all of [water, ground, grass]:
Grass
Normal & Grass
Grass & Ice
Grass & Fighting
Grass & Flying
Grass & Psychic
Grass & Bug
Grass & Ghost
Grass & Dragon
Grass & Dark
Grass & Fairy
Flying & Dragon
Bug & Dragon

Currently, my code works as intended; however, looking back, I'd like to write some things differently - in chess, when you find a good move, find a better one. I initially used a procedural for-loop approach in order to filter through the full list of PokemonTypes and test every single combination of them:

public static List<PokemonRecord> genMonResToAll(PokemonTypes... types) {
    List<PokemonTypes> allTypes = //List of possible PokemonTypes that this Pokemon can have (PokemonTypes that are not weak to any inputted PokemonTypes)
    List<PokemonRecord> outputList = new ArrayList<>();
    
    //Add any single-type Pokemon that resists all types
    for(PokemonTypes type : allTypes)
            if(new PokemonRecord(type).isResistantToAll(types))
                outputList.add(new PokemonRecord(type));

    //Add any multi-type Pokemon that resists all types
    for (int i = 0; i < allTypes.size() - 1; i  )
            for (int j = i   1; j < allTypes.size(); j  ) {
                PokemonRecord testMon = new PokemonRecord(allTypes.get(i), allTypes.get(j));
                if (testMon.isResistantToAll(types))
                    otuputList.add(testMon);
            }
    return outputList;
}

//The functionality of any specific `Pokemon` or `PokemonTypes` method used isn't relevant, they all work as intended.

I'm now trying to rewrite this code to be more declarative using the Stream API. I was able to work out how to convert the first loop, the loop that adds single-type PokemonRecord, to a Stream-based declarative statement. I'm having a much harder time wrapping my head around the second. My current code with the first loop refactored is:

public static List<PokemonRecord> genMonResToAll(PokemonTypes... types) {
    List<PokemonTypes> allTypes = //List of possible PokemonTypes that this Pokemon can have (PokemonTypes that are not weak to any inputted PokemonTypes)
    
    //Add any single-type Pokemon that resists all types
    List<PokemonRecord> outputList= allTypes.stream()
    .map(PokemonRecord::new)
    .filter(x -> x.isResistantToAll(types))
    .collect(Collectors.toList());

    //Add any multi-type Pokemon that resists all types
    for (int i = 0; i < allTypes.size() - 1; i  )
            for (int j = i   1; j < allTypes.size(); j  ) {
                PokemonRecord testMon = new PokemonRecord(allTypes.get(i), allTypes.get(j));
                if (testMon.isResistantToAll(types))
                    otuputList.add(testMon);
            }
    return outputList;
}

//The functionality of any specific `Pokemon` or `PokemonTypes` method used isn't relevant, they all work as intended.

Any help is appreciated, feel free to ask if you need any clarification!

CodePudding user response:

Since PokemonRecord's are twodimensional, I don't think you should use streams. This is a better approach without streams:

  1. If you havn't done this already, this is a clean implementation of PokemonRecord, which guarantees that PokemonRecord(type1, type2) == PokemonRecord(type2, type1). Preventing any illegally non-equal objects this way is generally a good idea:
public record PokemonRecord(PokemonType type1, PokemonType type2) {
    
    public PokemonRecord(PokemonType type1, PokemonType type2) {
        if (type1 == type2) throw new IllegalArgumentException("Illegal type combination");
        boolean order = type1 != null && (type2 == null || type1.compareTo(type2) < 0);
        this.type1 = order ? type1 : type2;
        this.type2 = order ? type2 : type1;
    }

    // your methods
}
  1. Now just add null to allTypes and staircase iterate over it (to get all possible combinations without duplicates):
public static List<PokemonRecord> genMonResToAll(PokemonType... types) {
    List<PokemonType> allTypes = new ArrayList<>();
    allTypes.add(null);
    List<PokemonRecord> result = new ArrayList<>();
    for (int s = allTypes.size(), i = 0; i < s; i  ) for (int j = s - 1; j > i; j--) {
        PokemonRecord record = new PokemonRecord(allTypes.get(i), allTypes.get(j));
        if (record.isResistantToAll(types)) result.add(record);
    }
    return result;
}
  • Related