/* * Copyright (C) 2015 Information Retrieval Group at Universidad Autónoma * de Madrid, http://ir.ii.uam.es * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package es.uam.eps.ir.ranksys.novdiv.reranking; import es.uam.eps.ir.ranksys.core.Recommendation; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntSortedSet; import org.ranksys.core.util.tuples.Tuple2od; import java.util.List; import java.util.stream.IntStream; import static java.lang.Double.isNaN; import static java.lang.Math.min; /** * Greedy re-ranking. Greedily selects items from an input recommendation * according to a selection criterion that is updated after a selection. It * requires a nested {@link GreedyUserReranker}. * * @author Saúl Vargas (saul.vargas@uam.es) * @author Pablo Castells (pablo.castells@uam.es) * * @param <U> type of the users * @param <I> type of the items */ public abstract class GreedyReranker<U, I> extends PermutationReranker<U, I> { /** * Cut-off of the re-ranking. */ protected final int cutoff; /** * Constructor. * * @param cutoff how many items are re-ranked by the greedy selection. */ public GreedyReranker(int cutoff) { this.cutoff = cutoff; } /** * Returns the permutation that is applied to the input recommendation * to generate the re-ranked recommendation. * * @param recommendation input recommendation * @param maxLength maximum length of the permutation * @return permutation that encodes the re-ranking */ @Override public int[] rerankPermutation(Recommendation<U, I> recommendation, int maxLength) { return getUserReranker(recommendation, maxLength).rerankPermutation(); } /** * Returns an instance of {@link GreedyUserReranker} that does the greedy * selection. * * @param recommendation input recommendation to be re-ranked * @param maxLength maximum length of the resulting re-ranked recommendation * @return the {@link GreedyUserReranker} that does the re-ranking */ protected abstract GreedyUserReranker getUserReranker(Recommendation<U, I> recommendation, int maxLength); /** * Re-ranker of a single recommendation. */ protected abstract class GreedyUserReranker { /** * input recommendation */ protected final Recommendation<U, I> recommendation; /** * maximum length of the re-ranked recommendation */ protected final int maxLength; /** * Constructor * * @param recommendation input recommendation * @param maxLength maximum length of the re-ranked recommendation */ public GreedyUserReranker(Recommendation<U, I> recommendation, int maxLength) { this.recommendation = recommendation; this.maxLength = maxLength; } /** * Returns the permutation to obtain the re-ranking * * @return permutation to obtain the re-ranking */ public int[] rerankPermutation() { List<Tuple2od<I>> list = recommendation.getItems(); IntList perm = new IntArrayList(); IntLinkedOpenHashSet remainingI = new IntLinkedOpenHashSet(); IntStream.range(0, list.size()).forEach(remainingI::add); while (!remainingI.isEmpty() && perm.size() < min(maxLength, cutoff)) { int bestI = selectItem(remainingI, list); perm.add(bestI); remainingI.remove(bestI); update(list.get(bestI)); } while (perm.size() < min(maxLength, list.size())) { perm.add(remainingI.removeFirstInt()); } return perm.toIntArray(); } /** * Selects the next element of the permutation that maximizes the * objective function. * * @param remainingI positions of the original recommendation that have * not been selected yet. * @param list the list of item-score pairs of the input recommendation * @return the next element of the permutation that maximizes the * objective function. */ protected int selectItem(IntSortedSet remainingI, List<Tuple2od<I>> list) { double[] max = new double[]{Double.NEGATIVE_INFINITY}; int[] bestI = new int[]{remainingI.firstInt()}; remainingI.forEach(i -> { double value = value(list.get(i)); if (isNaN(value)) { return; } if (value > max[0] || (value == max[0] && i < bestI[0])) { max[0] = value; bestI[0] = i; } }); return bestI[0]; } /** * Objective function that drives the greedy selection. * * @param itemValue item-score pair of the input recommendation. * @return value of the function with the given item-score pair. */ protected abstract double value(Tuple2od<I> itemValue); /** * Updates the value of the objective function after a selection. * * @param bestItemValue item-score pair that has been selected to * be added to the re-ranking */ protected abstract void update(Tuple2od<I> bestItemValue); } }