1

I'm not sure what to call the property that's 2 layers deep.

But let's say we have List<FruitColor> where Fruit and Color are 2 entities. (These are all example entities) A fruit can have different colors, but colors can also have different fruits.

public class FruitColor {
    private String fruitColorId;
    private Fruit fruit;
    private Color color;
    private int ripe; //1 to 3 where 1 unripe 2 normal 3 ripe

    // more props, getters, etc.
}

public class Fruit {
    private String fruitId;
    private String fruitName;

    // getters, etc.
}

public class Color {
    private String colorId;
    private String colorName;

    // getters, etc.
}

I'm thinking it in the following steps:

  1. Filter all the duplicates by fruitName
  2. Pick one of the two 'duplicates'. I say 'duplicates' because they only have the same name, but not the same color. The rule to choose which one to keep is the season/time-transition indicator systemTransition, which is described by the double property systemTransition that ranges from 0 to a 100.
  3. Remove the other 'duplicate'.
  4. If there is a duplicate, makes the fruit less ripe by 0.5
  5. Set the factorialRipeness, which is ripe * systemTransition or ripe * (100 - systemTransition) depending on how far we are in the season, halfway is the threshold.
  6. Return suggested FruitColors with the right Color, sorted by FactorialRipeness The thing is, this is probably do-able with a lot of for loops, but I'm just wondering if there are any ways that are more efficient.

I did find this source on how to filter by property, but this solution doesn't let me filter on the property of a property(=fruitName). Java 8 Distinct by property

I've made a code example (again different entities) that works. Are there any parts that could be made more efficient? By more efficient I mean, if the code could be shorter and/or faster. I'll definitely refactor the method into smaller parts later. Sorry for the confusing bits, this problem is really hard to explain without using the exact same entities.

//logic in the method
{   
    List<FruitColor> fruitColors = getAllFCs();
    
    //Remove if FC is neither colors that were chosen
    fruitColors.removeIf(fc -> fc.getColor().getColorId() != presentColor.getColor().getColorId()
            && fc.getColor().getColorId() != futureColor.getColor().getColorId());
    List<FruitColor> suggestedFruits = new ArrayList<>();

    //Systemtransition is the season/time from 0 to 100, where 100 the fruit should be completely ripe with the corresponding color.
    //If the time is less than half, it should pick the present color, which is the less ripe color.
    boolean getPresentColorFruit = systemTransition < 50;

    // --->This whole for-if-if-else-elseif-if-else part I wanted to make more "efficient" <---
    for (FruitColor fruitColor : fruitColors) {
        //First check for duplicate FCs 
        List<FruitColor> sameNameFruits = fruitColors.stream()
                .filter(fc -> fc.getFruit().getName().equals(itemColor.getFruit().getName()))
                .collect(Collectors.toList());
                
        //If there is only one of the fruit, check if it's added already. If not, than add with proper ripeness
        //FactorialRipeness is the "true" ripeness.
        if (sameNameFruits.size() == 1 && !suggestedFruits.stream().anyMatch(fc -> fc.getFruit().getName().equals(sameNameFruits.get(0).getFruit().getName()))) {
            FruitColor onlyOne = sameNameFruits.get(0);
            if (onlyOne.getColor().getColorId() == presentColor.getColor().getColorId()) {
                onlyOne.setFactorialRipeness(onlyOne.getRipeness() * systemTransition);
            } else {
                onlyOne.setFactorialRipeness(onlyOne.getRipeness() * (100 - systemTransition));
            }
            suggestedFruits.add(onlyOne);
          // If there are multiple FCs, than the ripeness has to go down. Which prioritizes duplicate FCs more. (this part isn't logical with these entities, sorry)
        } else if(!suggestedFruits.stream().anyMatch(fc -> fc.getFruit().getName().equals(sameNameFruits.get(0).getFruit().getName()))){
            if (getPresentColorFruit) {
                FruitColor fcWithPresentColor = sameNameFruits.stream()
                        .filter(fc -> fc.getColor().getColorId() == presentColor.getColor().getColorId()).findFirst()
                        .get();
                fcWithPresentColor.setFactorialRipeness((fcWithPresentColor.getRipeness() - 0.5) * systemTransition);
                suggestedFruits.add(fcWithPresentColor);
            } else {
                FruitColor fcWithFutureColor = sameNameFruits.stream()
                        .filter(fc -> fc.getColor().getColorId() == futureColor.getColor().getColorId()).findFirst()
                        .get();
                //This part is also not logical, but if the presentColor is not chosen. Than we need to multiply by the opposite amount of the systemTransition
                fcWithFutureColor.setFactorialRipeness((fcWithFutureColor.getRipeness() - 0.5) * (100 - systemTransition)); 
                suggestedFruits.add(fcWithFutureColor);
            }
        }
    }
    //Sort by "true" ripeness value, from lowest to highest
    Collections.sort(suggestedFruits, new FruitColorRipenessComparator());
    return suggestedFruits;
}

/**
 * @source https://stackoverflow.com/questions/2839137/how-to-use-comparator-in-java-to-sort
 */
public class FruitColorRipenessComparator implements Comparator<FruitColor> {
    @Override
    public int compare(FruitColor a, FruitColor b){
        return a.getFactorialRipeness() < b.getFactorialRipeness() ? -1 : a.getFactorialRipeness() == b.getFactorialRipeness() ? 0 : 1;
    }
}
zhrgci
  • 584
  • 1
  • 6
  • 25
  • I see, okay I'll start making a version with loops and clarify it with that as example. Also the rule to choose which one to keep is how ripe the fruit is, from 0 to a 100, which was a property in `fruit` that I didn't include. @AlexanderIvanchenko – zhrgci Nov 21 '22 at 14:32

1 Answers1

1

As a possible option you can group FruitColor instances by the name property of their fruits by storing them into a Map. And then dump the values into a list.

It can be implemented with Stream API using Collector toMap(keyMapper,valueMapper,mergeFunction).

the rule to choose which one to keep is how ripe the fruit is, from 0 to a 100, which was a property in fruit

To implement the mergeFunction, which is meant to resolve values mapped to the same key we can use static method BinaryOpearator.maxBy(), which expects a Comparator.

Comparator that compares FruitColor instances based rape property of the fruit field might be expressed in the following way:

Comparator.comparingInt(fruitColor -> fruitColor.getFruit().getRipe())

or alternatively as:

Comparator.comparing(
    FruitColor::getFruit,
    Comparator.comparingInt(Fruit::getRipe)
)

That's the overall implementation might look like:

List<FruitColor> fruitColors = // initializing the list
        
List<FruitColor> fruitColorsDistinct = fruitColors.stream()
    .collect(Collectors.toMap(
        fruitColor -> fruitColor.getFruit().getFruitName(), // keyMapper
        Function.identity(),  // valueMapper
        BinaryOperator.maxBy( // mergeFunction - resolving duplicates
            Comparator.comparingInt(fruitColor -> fruitColor.getFruit().getRipe())
        )
    ))
    .values().stream()
    .toList();
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46