/* * Copyright 2010 Research Studios Austria Forschungsgesellschaft mBH * * This file is part of easyrec. * * easyrec is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * easyrec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with easyrec. If not, see <http://www.gnu.org/licenses/>. */ /** * */ package org.easyrec.plugin.itemitem.impl; import org.easyrec.model.core.ItemAssocVO; import org.easyrec.model.core.ItemVO; import org.easyrec.model.core.RatingVO; import org.easyrec.plugin.itemitem.SimilarityCalculationStrategy; import org.easyrec.plugin.itemitem.store.dao.ActionDAO; import org.easyrec.plugin.itemitem.store.dao.ActionDAO.RatedTogether; import org.easyrec.plugin.support.ExecutablePluginSupport; import org.easyrec.service.core.ItemAssocService; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; /** * Common denominator for calculating similarities. Since each method for calculating similarities is of the form:<br/> * a = rating1 - x<br/> b = rating2 - y<br/> similarity = sum over all items( a * b ) / ( sqrt(sum over all items (a^2)) * * sqrt(sum over all items (b^2)) )<br/> Where x or y might be 0 or an average only methods for providing x and y need * to be overridden. <p/> <p/> <p> <b>Company: </b> SAT, Research Studios Austria </p> <p/> <p> * <b>Copyright: </b> (c) 2009 </p> <p/> <p> <b>last modified:</b><br/> $Author$<br/> $Date$<br/> $Revision$ </p> * * @author Patrick Marschik */ abstract class AbstractSimilarityCalculationStrategy implements SimilarityCalculationStrategy { // ------------------------------ FIELDS ------------------------------ private ItemAssocService itemAssocService; private ActionDAO actionDao; // --------------------------- CONSTRUCTORS --------------------------- protected AbstractSimilarityCalculationStrategy() { } protected AbstractSimilarityCalculationStrategy(final ActionDAO actionDao, final ItemAssocService itemAssocService) { this.actionDao = actionDao; this.itemAssocService = itemAssocService; } // --------------------- GETTER / SETTER METHODS --------------------- public void setItemAssocService(final ItemAssocService itemAssocService) { this.itemAssocService = itemAssocService; } // ------------------------ INTERFACE METHODS ------------------------ // --------------------- Interface SimilarityCalculationStrategy --------------------- public int calculateSimilarity(final Integer tenantId, final Integer actionTypeId, final Integer itemTypeId, final Integer assocTypeId, final Integer sourceTypeId, final Integer viewTypeId, final Date changeDate, final ExecutablePluginSupport.ExecutionControl control) { validateState(); final List<ItemVO<Integer, Integer>> allItems = actionDao .getAvailableItemsForTenant(tenantId, itemTypeId); final Map<Integer, RatingVO<Integer, Integer>> averageItemRatings = getAverageItemRatings( tenantId, itemTypeId); final int ITEM_ASSOC_BUFFER = 10000; int itemAssocsCreated = 0; List<ItemAssocVO<Integer,Integer>> itemAssocs = new ArrayList<ItemAssocVO<Integer,Integer>>( ITEM_ASSOC_BUFFER); final int TOTAL_STEPS = allItems.size(); for (int i = 0; i < TOTAL_STEPS; i++) { if (control != null) control.updateProgress( String.format("Calculating similarity %d/%d - %.2f%%", i, TOTAL_STEPS, ((double) i / (double) TOTAL_STEPS) * 100.0)); final ItemVO<Integer, Integer> item1 = allItems.get(i); for (int j = i + 1; j < allItems.size(); j++) { double numerator = 0.0; double denominator1 = 0.0; double denominator2 = 0.0; final ItemVO<Integer, Integer> item2 = allItems.get(j); final List<RatedTogether<Integer, Integer>> ratedTogether = actionDao .getItemsRatedTogether(tenantId, itemTypeId, item1.getItem(), item2.getItem(), actionTypeId); for (final RatedTogether<Integer, Integer> rated : ratedTogether) { final double averageItem1 = getAverage1(rated, averageItemRatings); final double averageItem2 = getAverage2(rated, averageItemRatings); final RatingVO<Integer, Integer> rating1 = rated.getRating1(); final RatingVO<Integer, Integer> rating2 = rated.getRating2(); final double rating1diff = rating1.getRatingValue() - averageItem1; final double rating2diff = rating2.getRatingValue() - averageItem2; final double rating1diffSquared = Math.pow(rating1diff, 2); final double rating2diffSquared = Math.pow(rating2diff, 2); numerator += rating1diff * rating2diff; denominator1 += rating1diffSquared; denominator2 += rating2diffSquared; } denominator1 = Math.sqrt(denominator1); denominator2 = Math.sqrt(denominator2); if (denominator1 == 0.0 || denominator2 == 0.0) continue; final double similarityValue = numerator / (denominator1 * denominator2); if (Double.isNaN(similarityValue)) continue; final ItemAssocVO<Integer,Integer> itemAssoc1 = new ItemAssocVO<Integer,Integer>( tenantId, item1, assocTypeId, similarityValue, item2, sourceTypeId, getSourceInfo(), viewTypeId, null, changeDate); final ItemAssocVO<Integer,Integer> itemAssoc2 = new ItemAssocVO<Integer,Integer>( tenantId, item2, assocTypeId, similarityValue, item1, sourceTypeId, getSourceInfo(), viewTypeId, null, changeDate); itemAssocs.add(itemAssoc1); itemAssocs.add(itemAssoc2); itemAssocsCreated += 2; if (itemAssocs.size() >= ITEM_ASSOC_BUFFER) { itemAssocService.insertOrUpdateItemAssocs(itemAssocs); itemAssocs.clear(); } } } if (itemAssocs.size() > 0) itemAssocService.insertOrUpdateItemAssocs(itemAssocs); return itemAssocsCreated; } public void setActionDAO(final ActionDAO actionDao) { this.actionDao = actionDao; } // -------------------------- OTHER METHODS -------------------------- /** * Get x as defined in the class documentation. * * @param ratedTogether Two vector component of items to use for getting x. * @param averageRatings The map precalculated by {@link #getAverageItemRatings(Integer, Integer)}. * @return X. */ protected abstract double getAverage1(RatedTogether<Integer, Integer> ratedTogether, Map<Integer, RatingVO<Integer, Integer>> averageRatings); /** * Get y as defined in the class documentation. * * @param ratedTogether Two vector component of items to use for getting y. * @param averageRatings The map precalculated by {@link #getAverageItemRatings(Integer, Integer)}. * @return Y. */ protected abstract double getAverage2(RatedTogether<Integer, Integer> ratedTogether, Map<Integer, RatingVO<Integer, Integer>> averageRatings); /** * Allows precalulation of the averages. * * @param tenantId Tenant id. * @param itemTypeId Item type id. * @return A precalculated map mapping item id to average rating. */ protected abstract Map<Integer, RatingVO<Integer, Integer>> getAverageItemRatings( Integer tenantId, Integer itemTypeId); protected ActionDAO getLatestActionDao() { return actionDao; } private void validateState() { if (actionDao == null || itemAssocService == null) throw new IllegalStateException("DAOs not initialized"); } }