简体   繁体   中英

Finding the most suitable objects by set of parameters in java

I have a set of objects. This objects calculate some numbers based on request parameters. Let's call them calculators. Each calculator has description where specified type of requests that this calculator the most suitable for. For example,

Calculator1 : with this parameters : price > 10, gender = male, geo_id = 1, 2 or 3.
Calculator2 : with this parameters : price < 5, gender = male,  geo_id = 1, 2. 

For request : price = 11, gender = male, geo_id = 2 I should get calculator1 like the most suitable and then calculator2.

For request : price = 4, gender = male, geo_id = 2 I should get calculator2 and then calculator1.

For request : price = 3, gender = female, geo_id = 5 I should get only the second one.

Now I'm doing it with Lucene, but it's not really fit for this task. Can you recommend me some library or approach?

My suggestion would be to use a comparator. See a sketch of the classes below.

import java.util.HashMap;
import java.util.Map;

public abstract class Calculator {
    public static Map<String, Integer> weights;
    static {
        weights = new HashMap<String, Integer>();
        weights.put("price", 10);
        weights.put("gender", 2);
        weights.put("geo", 5);
    }

    public abstract int calculate(Map<String, Integer> request);
    public abstract int fitnessFor(Map<String, Integer> request);
}

You can use the weights to adjust relative importance of the individual request parameters.

import java.util.Map;

public class Calculator1 extends Calculator {

    public int calculate(Map<String, Integer> request) {
        return -1;
    }

    @Override
    public int fitnessFor(Map<String, Integer> request) {
        int fitness = -1;
        Integer price = request.get("price");
        if (price == null)
            return fitness;

        if (price > 10)
            fitness += weights.get("price");

        return fitness;
    }

    public String toString() { return "Calculator1"; }
}

Calculator1 cares only about the pricey items.

import java.util.Map;

public class Calculator2 extends Calculator {

    public int calculate(Map<String, Integer> request) {
        return -1;
    }

    @Override
    public int fitnessFor(Map<String, Integer> request) {
        int fitness = -1;
        Integer price = request.get("price");
        if (price == null)
            return fitness;

        if (price < 5)
            fitness += weights.get("price");

        Integer gender = request.get("gender");
        if (gender == null)
            return fitness;

        if (gender == 1)
            fitness += weights.get("gender");

        return fitness;
    }

    public String toString() { return "Calculator2"; }  
}

Calculator2 cares about the less pricey items esp. if they are for gender 1.

The comparator just compares Calculators by their fitness relative to the request:

import java.util.Comparator;
import java.util.Map;

public class CalcComparator implements Comparator<Calculator> {
    private Map<String, Integer> request;

    public CalcComparator(Map<String, Integer> request) {
        this.request = request;
    }

    @Override
    public int compare(Calculator c1, Calculator c2) {
        int c1Fitness = c1.fitnessFor(request);
        int c2Fitness = c2.fitnessFor(request);

        if (c1Fitness == c2Fitness)
            return 0;

        if (c1Fitness < c2Fitness)
            return 1;

        return -1;
    }
}

Try it out with:

public class Main {

    public static void main(String[] args) {
        Map<String, Integer> request = new HashMap<String, Integer>();
        request.put("price", 5);
        request.put("gender", 1);

        List<Calculator> calculators = new ArrayList<Calculator>();
        calculators.add(new Calculator1());
        calculators.add(new Calculator2());

        Collections.sort(calculators, new CalcComparator(request));

        System.out.println("For request: "+request);
        for (Calculator c : calculators) {
            System.out.println("\t"+c.toString() + "( fitness " + c.fitnessFor(request) + ")");
        }
    }
}

This is just a sketch to illustrate the idea. You will probably want to introduce an enum for the request parameters, maybe introduce a Request class, most likely change completely how fitness is computed, make some of the fields private and encapsulate them, etc.

The advantage is that you easily get an ordering of all the Calculators based on their fitness for the request.

You could maybe try something like this:

public enum Calculator
{
    CALC1
    {
        @Override
        protected int matchCount( Map parameters )
        {
            // TODO count how many conditions match
            return 0;
        }

        @Override
        protected int calc( Map parameters )
        {
            // TODO
            return 0;
        }
    },

    CALC2
    {
        @Override
        protected int matchCount( Map parameters )
        {
            // TODO count how many conditions match
            return 0;
        }

        @Override
        protected int calc( Map parameters )
        {
            // TODO
            return 0;
        }
    };

    protected abstract int matchCount( Map parameters );
    protected abstract int calc( Map parameters );

    public int doCalc( Map parameters )
    {
        Calculator  mostSuited = null;
        int  maxCount = 0;

        for ( Calculator  calc : values() )
        {
            int  matchCount = calc.matchCount( parameters );

            if ( matchCount > maxCount )
            {
                mostSuited = calc;
            }
        }

        return mostSuited.calc( parameters );
    }
}

The way you would use the above is by invoking: int result = Calculator.doCalc( parameters )

Provided that I understood you correctly, I would suggest that you use the Specification design pattern which is used in cases like this. There's no need in such a fancy library like Lucene for such a simple task. The advantage of the Specification pattern is that it keeps all the filtering logic grouped and encapsulated. Your implementation may vary, but below is a simple example of what it could look like

public interface Specification<T> {
    boolean isSatisfiedBy(T candidate);
    Specification<T> and(Specification<T> specification);
    Specification<T> or(Specification<T> specification);
    Specification<T> not(Specification<T> specification);
}

public abstract class Calculator {
    // ...
}

public class Calculator1 extends Calculator implements Specification<Request> {
    public boolean isSatisfiedBy(Request request) {
        // check if the request fits this calculator
    }
}

public class Calculator2 extends Calculator implements Specification<Request> {
    public boolean isSatisfiedBy(Request request) {
        // check if the request fits this calculator
    }
}

You can then have a collection or a pool of calculators such that

public class Calculators {
    private final List<RequestSpecification> calculators;
    public Calculator getOneSuitedFor(Request request) {
        for (Calculator calculator : calculators) {
            if (calculator.isSatisfiedBy(request)) {
                return calculator;
            }
        }
        return null;
    }
}

And here how you would use it

Calculator calculator = Calculators.getOneSuitedFor(request);

Or, if needed, you can always go on and expand on it by making use of composition (see the reference link above) which allows for logic chaining and combining of different specifications depending on the context. This, however, would require a little bit different class design from that of above, but is more flexible

final Request request;

Specification<Calculator> price = new Specification<>() {
     public boolean isSatisfiedBy(Calculator calculator) {
         return calculator.supportsPrice(request.getPrice());
     }
};

Specification<Calculator> gender = new Specification<>() {
     public boolean isSatisfiedBy(Calculator calculator) {
         return calculator.supportsGender(request.getGender());
     }
};

Specification<Calculator> region = new Specification<>() {
     public boolean isSatisfiedBy(Calculator calculator) {
         return calculator.supportsRegion(request.getRegion());
     }
};

Specification calcSpec = price.and(gender).and(region);
boolean isSatisfied = calcSpec.isSatisfiedBy(calculator);

Another interesting example is to use named specifications

Specification<Calculator> teenager = new Specification<>() {
     public boolean isSatisfiedBy(Calculator calculator) {
         return calculator.getAge() >= 13 && calculator.getAge() <= 19;
     }
};

Specification<Calculator> male = new Specification<>() {
     public boolean isSatisfiedBy(Calculator calculator) {
         return calculator.getGender().equals("male");
     }
};

Specification<Calculator> fromEurope = new Specification<>() {
     public boolean isSatisfiedBy(Calculator calculator) {
         return calculator.getRegion().equals("Europe");
     }
};

Specification<Calculator> calcSpec = teenager.and(male).and(fromEurope);
boolean isSatisfied = calcSpec.isSatisfiedBy(calculator);

Create a Calculator base class :

public static abstract class Calculator {

    // This Contains the common score calculation methods.
    public int getScore(int price, String gender, int geo_id) {
        int score = 0;
        if (gender.equalsIgnoreCase("male"))
            score++;
        if (getGeoIds().contains(geo_id))
            score++;
        return score;
    }

    public ArrayList<Integer> getGeoIds() {
        // Fetching the common list of geo points to be compared.
        ArrayList<Integer> lst = new ArrayList<Integer>();
        lst.add(1);
        lst.add(2);
        return lst;
    }

    public abstract void doCalculation();

}

Then create your calculator classes by extending from this base.

public static class Calcualtor1 extends Calculator {

    @Override
    public int getScore(int price, String gender, int geo_id) {
        // fetching score from common score calculation.
        int score = super.getScore(price, gender, geo_id);
        // Adding its own score logic.
        if (price > 10)
            score++;
        return score;
    }

    @Override
    public void doCalculation() {
        // Do your actual work.
    }

    @Override
    public ArrayList<Integer> getGeoIds() {
        ArrayList<Integer> lst = super.getGeoIds();
        // Adding the geo id to compare for this calculator.
        lst.add(3);
        return lst;
    }

}

public static class Calcualtor2 extends Calculator {

    @Override
    public int getScore(int price, String gender, int geo_id) {
        // fetching score from common score calculation.
        int score = super.getScore(price, gender, geo_id);
        // Adding its own score logic.
        if (price < 5)
            score++;
        return score;
    }

    @Override
    public void doCalculation() {
        // Do your actual work.
    }
}

Initialise values :

//To store the list of available calculators.
private static ArrayList<Class<? extends Calculator>> calculators;

static {
    //Initializing the calculator list in static constructor.
    calculators = new ArrayList<Class<? extends Calculator>>();
    calculators.add(Calcualtor1.class);
    calculators.add(Calcualtor2.class);
}

Actual processing :

public static void main(String[] args) {
    int price = 10;
    String gender = "male";
    int geo_id = 2;

    Calculator calculator = null;
    int score = 0;

    for (Class<? extends Calculator> calClass : calculators) {

        Calculator cal = null;
        try {
            cal = calClass.newInstance();
        } catch (Exception e) {
            continue;
        }

        int calScore = cal.getScore(price, gender, geo_id);

        if (calScore > score) {
            calculator = cal;
            score = calScore;
        }
    }

    if (calculator != null) {
        calculator.doCalculation();
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM