You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mahout.apache.org by sr...@apache.org on 2008/05/09 23:35:17 UTC

svn commit: r654943 [7/9] - in /lucene/mahout/trunk/core: ./ lib/ src/main/examples/org/ src/main/examples/org/apache/ src/main/examples/org/apache/mahout/ src/main/examples/org/apache/mahout/cf/ src/main/examples/org/apache/mahout/cf/taste/ src/main/e...

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/DataModel.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/DataModel.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/DataModel.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/DataModel.java Fri May  9 14:35:12 2008
@@ -0,0 +1,106 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.model;
+
+import org.apache.mahout.cf.taste.common.Refreshable;
+import org.apache.mahout.cf.taste.common.TasteException;
+
+import java.util.List;
+
+/**
+ * <p>Implementations represent a repository of information about {@link User}s and their
+ * associated {@link Preference}s for {@link Item}s.</p>
+ */
+public interface DataModel extends Refreshable {
+
+  /**
+   * @return a {@link List} of all {@link User}s in the model, ordered by {@link User}
+   * @throws TasteException if an error occurs while accessing the data
+   */
+  Iterable<? extends User> getUsers() throws TasteException;
+
+  /**
+   * @param id user ID
+   * @return {@link User} who has that ID
+   * @throws TasteException if an error occurs while accessing the data
+   * @throws java.util.NoSuchElementException if there is no such {@link User}
+   */
+  User getUser(Object id) throws TasteException;
+
+  /**
+   * @return a {@link List} of all {@link Item}s in the model, order by {@link Item}
+   * @throws TasteException if an error occurs while accessing the data
+   */
+  Iterable<? extends Item> getItems() throws TasteException;
+
+  /**
+   * @param id item ID
+   * @return {@link Item} that has that ID
+   * @throws TasteException if an error occurs while accessing the data
+   * @throws java.util.NoSuchElementException if there is no such {@link Item}
+   */
+  Item getItem(Object id) throws TasteException;
+
+  /**
+   * @param itemID item ID
+   * @return all existing {@link Preference}s expressed for that item, ordered by {@link User}
+   * @throws TasteException if an error occurs while accessing the data
+   */
+  Iterable<? extends Preference> getPreferencesForItem(Object itemID) throws TasteException;
+
+  /**
+   * @param itemID item ID
+   * @return all existing {@link Preference}s expressed for that item, ordered by {@link User},
+   *         as an array
+   * @throws TasteException if an error occurs while accessing the data
+   */
+  Preference[] getPreferencesForItemAsArray(Object itemID) throws TasteException;
+
+  /**
+   * @return total number of {@link Item}s known to the model. This is generally the union
+   *         of all {@link Item}s preferred by at least one {@link User} but could include more.
+   * @throws TasteException if an error occurs while accessing the data
+   */
+  int getNumItems() throws TasteException;
+
+  /**
+   * @return total number of {@link User}s known to the model.
+   * @throws TasteException if an error occurs while accessing the data
+   */
+  int getNumUsers() throws TasteException;
+
+  /**
+   * <p>Sets a particular preference (item plus rating) for a user.</p>
+   *
+   * @param userID user to set preference for
+   * @param itemID item to set preference for
+   * @param value preference value
+   * @throws TasteException if an error occurs while accessing the data
+   */
+  void setPreference(Object userID, Object itemID, double value) throws TasteException;
+
+  /**
+   * <p>Removes a particular preference for a user.</p>
+   *
+   * @param userID user from which to remove preference
+   * @param itemID item to remove preference for
+   * @throws TasteException if an error occurs while accessing the data
+   */
+  void removePreference(Object userID, Object itemID) throws TasteException;
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/Item.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/Item.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/Item.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/Item.java Fri May  9 14:35:12 2008
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.model;
+
+/**
+ * <p>Implementations of this interface represent items that {@link User}s have
+ * preferences for, and which can be recommended to them. {@link Item}s must have
+ * a unique ID of some kind, and must be {@link Comparable}.</p>
+ */
+public interface Item extends Comparable<Item> {
+
+  /**
+   * @return unique ID for this item
+   */
+  Object getID();
+
+  /**
+   * @return true if and only if this {@link Item} can be recommended to a user;
+   *         for example, this could be false for an {@link Item} that is no longer
+   *         available but which remains valuable for recommendation
+   */
+  boolean isRecommendable();
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/JDBCDataModel.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/JDBCDataModel.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/JDBCDataModel.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/JDBCDataModel.java Fri May  9 14:35:12 2008
@@ -0,0 +1,35 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.model;
+
+import org.apache.mahout.cf.taste.common.TasteException;
+
+import javax.sql.DataSource;
+
+public interface JDBCDataModel extends DataModel {
+
+  DataSource getDataSource();
+
+  /**
+   * @param assumeExists assume the item exists; don't consult the underlying database. This is a necessary
+   * performance enhancement shortcut needed by slope one recommenders
+   * @see #getItem(Object)
+   */
+  Item getItem(Object id, boolean assumeExists) throws TasteException;
+
+}
\ No newline at end of file

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/Preference.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/Preference.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/Preference.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/Preference.java Fri May  9 14:35:12 2008
@@ -0,0 +1,50 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.model;
+
+/**
+ * <p>A {@link Preference} encapsulates an {@link Item} and a preference value, which
+ * indicates the strength of the preference for it. {@link Preference}s are associated
+ * to {@link User}s.</p>
+ */
+public interface Preference {
+
+  /**
+   * @return {@link User} who prefers the {@link Item}
+   */
+  User getUser();
+
+  /**
+   * @return {@link Item} that is preferred
+   */
+  Item getItem();
+
+  /**
+   * @return strength of the preference for that item. Zero should indicate "no preference either way";
+   *         positive values indicate preference and negative values indicate dislike
+   */
+  double getValue();
+
+  /**
+   * Sets the strength of the preference for this item
+   *
+   * @param value new preference
+   */
+  void setValue(double value);
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/User.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/User.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/User.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/model/User.java Fri May  9 14:35:12 2008
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.model;
+
+/**
+ * <p>Implementations represent a user, who has preferences for {@link Item}s.</p>
+ */
+public interface User extends Comparable<User> {
+
+  /**
+   * @return unique user ID
+   */
+  Object getID();
+
+  /**
+   * @param itemID ID of item to get the user's preference for
+   * @return user's {@link Preference} for that {@link Item}, or <code>null</code> if the user expresses
+   *         no such preference
+   */
+  Preference getPreferenceFor(Object itemID);
+
+  /**
+   * <p>Returns a sequence of {@link Preference}s for this {@link User} which can be iterated over.
+   * Note that the sequence <em>must</em> be "in order": ordered by {@link Item}.</p>
+   *
+   * @return a sequence of {@link Preference}s
+   */
+  Iterable<Preference> getPreferences();
+
+  /**
+   * <p>Returns an array view of {@link Preference}s for this {@link User}.
+   * Note that the sequence <em>must</em> be "in order": ordered by {@link Item}.</p>
+   *
+   * @return an array of {@link Preference}s
+   */
+  Preference[] getPreferencesAsArray();
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/neighborhood/UserNeighborhood.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/neighborhood/UserNeighborhood.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/neighborhood/UserNeighborhood.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/neighborhood/UserNeighborhood.java Fri May  9 14:35:12 2008
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.neighborhood;
+
+import org.apache.mahout.cf.taste.common.Refreshable;
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.model.User;
+
+import java.util.Collection;
+
+/**
+ * <p>Implementations of this interface compute a "neighborhood" of {@link User}s like a
+ * given {@link User}. This neighborhood can be used to compute recommendations then.</p>
+ */
+public interface UserNeighborhood extends Refreshable {
+
+  /**
+   * @param userID ID of user for which a neighborhood will be computed
+   * @return {@link Collection} of {@link User}s in the neighborhood
+   * @throws org.apache.mahout.cf.taste.common.TasteException if an error occurs while accessing data
+   */
+  Collection<User> getUserNeighborhood(Object userID) throws TasteException;
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/ClusteringRecommender.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/ClusteringRecommender.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/ClusteringRecommender.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/ClusteringRecommender.java Fri May  9 14:35:12 2008
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.recommender;
+
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.model.User;
+
+import java.util.Collection;
+
+/**
+ * <p>Interface implemented by "clustering" recommenders.</p>
+ */
+public interface ClusteringRecommender extends Recommender {
+
+  /**
+   * <p>Returns the cluster of users to which the given {@link User}, denoted by user ID,
+   * belongs.</p>
+   *
+   * @param userID user ID for which to find a cluster
+   * @return {@link Collection} of {@link User}s in the requested user's cluster
+   * @throws TasteException if an error occurs while accessing the {@link org.apache.mahout.cf.taste.model.DataModel}
+   */
+  Collection<User> getCluster(Object userID) throws TasteException;
+
+  /**
+   * <p>Returns all clusters of users.</p>
+   *
+   * @return {@link Collection} of {@link Collection}s of {@link User}s
+   * @throws TasteException if an error occurs while accessing the {@link org.apache.mahout.cf.taste.model.DataModel}
+   */
+  Collection<Collection<User>> getClusters() throws TasteException;
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/ItemBasedRecommender.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/ItemBasedRecommender.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/ItemBasedRecommender.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/ItemBasedRecommender.java Fri May  9 14:35:12 2008
@@ -0,0 +1,92 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.recommender;
+
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.impl.common.Pair;
+import org.apache.mahout.cf.taste.model.Item;
+
+import java.util.List;
+
+/**
+ * <p>Interface implemented by "item-based" recommenders.</p>
+ */
+public interface ItemBasedRecommender extends Recommender {
+
+  /**
+   * @param itemID ID of {@link Item} for which to find most similar other {@link Item}s
+   * @param howMany desired number of most similar {@link Item}s to find
+   * @return {@link Item}s most similar to the given item, ordered from most similar to least
+   * @throws TasteException if an error occurs while accessing the {@link org.apache.mahout.cf.taste.model.DataModel}
+   */
+  List<RecommendedItem> mostSimilarItems(Object itemID, int howMany) throws TasteException;
+
+  /**
+   * @param itemID ID of {@link Item} for which to find most similar other {@link Item}s
+   * @param howMany desired number of most similar {@link Item}s to find
+   * @param rescorer {@link Rescorer} which can adjust item-item correlation
+   * estimates used to determine most similar items
+   * @return {@link Item}s most similar to the given item, ordered from most similar to least
+   * @throws TasteException if an error occurs while accessing the {@link org.apache.mahout.cf.taste.model.DataModel}
+   */
+  List<RecommendedItem> mostSimilarItems(Object itemID,
+                                         int howMany,
+                                         Rescorer<Pair<Item, Item>> rescorer) throws TasteException;
+
+  /**
+   * @param itemIDs IDs of {@link Item} for which to find most similar other {@link Item}s
+   * @param howMany desired number of most similar {@link Item}s to find
+   * estimates used to determine most similar items
+   * @return {@link Item}s most similar to the given items, ordered from most similar to least
+   * @throws TasteException if an error occurs while accessing the {@link org.apache.mahout.cf.taste.model.DataModel}
+   */
+  List<RecommendedItem> mostSimilarItems(List<Object> itemIDs, int howMany) throws TasteException;
+
+  /**
+   * @param itemIDs IDs of {@link Item} for which to find most similar other {@link Item}s
+   * @param howMany desired number of most similar {@link Item}s to find
+   * @param rescorer {@link Rescorer} which can adjust item-item correlation
+   * estimates used to determine most similar items
+   * @return {@link Item}s most similar to the given items, ordered from most similar to least
+   * @throws TasteException if an error occurs while accessing the {@link org.apache.mahout.cf.taste.model.DataModel}
+   */
+  List<RecommendedItem> mostSimilarItems(List<Object> itemIDs,
+                                         int howMany,
+                                         Rescorer<Pair<Item, Item>> rescorer) throws TasteException;
+
+  /**
+   * <p>Lists the {@link Item}s that were most influential in recommending a given item to a given user.
+   * Exactly how this is determined is left to the implementation, but, generally this will return items
+   * that the user prefers and that are similar to the given item.</p>
+   *
+   * <p>This returns a {@link List} of {@link RecommendedItem} which is a little misleading since it's
+   * returning recommend<strong>ing</strong> items, but, I thought it more natural to just reuse this
+   * class since it encapsulates an {@link Item} and value. The value here does not necessarily have
+   * a consistent interpretation or expected range; it will be higher the more influential the {@link Item}
+   * was in the recommendation.</p>
+   *
+   * @param userID ID of {@link org.apache.mahout.cf.taste.model.User} who was recommended the {@link Item}
+   * @param itemID ID of {@link Item} that was recommended
+   * @param howMany maximum number of {@link Item}s to return
+   * @return {@link List} of {@link RecommendedItem}, ordered from most influential in recommended the given
+   *         {@link Item} to least
+   * @throws TasteException if an error occurs while accessing the {@link org.apache.mahout.cf.taste.model.DataModel}
+   */
+  List<RecommendedItem> recommendedBecause(Object userID, Object itemID, int howMany) throws TasteException;
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/RecommendedItem.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/RecommendedItem.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/RecommendedItem.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/RecommendedItem.java Fri May  9 14:35:12 2008
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.recommender;
+
+import org.apache.mahout.cf.taste.model.Item;
+
+/**
+ * <p>Implementations encapsulate items that are recommended, and include
+ * the {@link org.apache.mahout.cf.taste.model.Item} recommended and a value expressing
+ * the strength of the preference.</p>
+ */
+public interface RecommendedItem extends Comparable<RecommendedItem> {
+
+  /**
+   * @return the recommended {@link Item}
+   */
+  Item getItem();
+
+  /**
+   * <p>A value expressing the strength of the preference for the recommended
+   * {@link Item}. The range of the values depends on the implementation.
+   * Implementations must use larger values to express stronger preference.</p>
+   *
+   * @return strength of the preference
+   */
+  double getValue();
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/Recommender.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/Recommender.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/Recommender.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/Recommender.java Fri May  9 14:35:12 2008
@@ -0,0 +1,83 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.recommender;
+
+import org.apache.mahout.cf.taste.common.Refreshable;
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.Item;
+
+import java.util.List;
+
+/**
+ * <p>Implementations of this interface can recommend {@link Item}s for a
+ * {@link org.apache.mahout.cf.taste.model.User}. Implementations will likely take advantage of several
+ * classes in other packages here to compute this.</p>
+ */
+public interface Recommender extends Refreshable {
+
+  /**
+   * @param userID user for which recommendations are to be computed
+   * @param howMany desired number of recommendations
+   * @return {@link List} of recommended {@link RecommendedItem}s, ordered from most strongly
+   *         recommend to least
+   * @throws TasteException if an error occurs while accessing the {@link org.apache.mahout.cf.taste.model.DataModel}
+   */
+  List<RecommendedItem> recommend(Object userID, int howMany) throws TasteException;
+
+  /**
+   * @param userID user for which recommendations are to be computed
+   * @param howMany desired number of recommendations
+   * @param rescorer rescoring function to apply before final list of recommendations is determined
+   * @return {@link List} of recommended {@link RecommendedItem}s, ordered from most strongly
+   *         recommend to least
+   * @throws TasteException if an error occurs while accessing the {@link org.apache.mahout.cf.taste.model.DataModel}
+   */
+  List<RecommendedItem> recommend(Object userID, int howMany, Rescorer<Item> rescorer) throws TasteException;
+
+  /**
+   * @param userID user ID whose preference is to be estimated
+   * @param itemID item ID to estimate preference for
+   * @return an estimated preference if the user has not expressed a preference for the item, or else
+   *         the user's actual preference for the item. If a preference cannot be estimated, returns
+   *         {@link Double#NaN}
+   * @throws TasteException if an error occurs while accessing the {@link org.apache.mahout.cf.taste.model.DataModel}
+   */
+  double estimatePreference(Object userID, Object itemID) throws TasteException;
+
+  /**
+   * @param userID user to set preference for
+   * @param itemID item to set preference for
+   * @param value preference value
+   * @throws TasteException if an error occurs while accessing the {@link org.apache.mahout.cf.taste.model.DataModel}
+   */
+  void setPreference(Object userID, Object itemID, double value) throws TasteException;
+
+  /**
+   * @param userID user from which to remove preference
+   * @param itemID item for which to remove preference
+   * @throws TasteException if an error occurs while accessing the {@link org.apache.mahout.cf.taste.model.DataModel}
+   */
+  void removePreference(Object userID, Object itemID) throws TasteException;
+
+  /**
+   * @return {@link DataModel} used by this {@link Recommender}
+   */
+  DataModel getDataModel();
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/Rescorer.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/Rescorer.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/Rescorer.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/Rescorer.java Fri May  9 14:35:12 2008
@@ -0,0 +1,42 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.recommender;
+
+/**
+ * <p>A {@link Rescorer} simply assigns a new "score" to a thing like an
+ * {@link org.apache.mahout.cf.taste.model.Item} or {@link org.apache.mahout.cf.taste.model.User} which a {@link Recommender}
+ * is considering returning as a top recommendation. It may be used to arbitrarily re-rank the results
+ * according to application-specific logic before returning recommendations. For example, an application
+ * may want to boost the score of items in a certain category just for one request.</p>
+ *
+ * <p>A {@link Rescorer} can also exclude a thing from consideration entirely by returning <code>true</code>
+ * from {@link #isFiltered(Object)}.</p>
+ */
+public interface Rescorer<T> {
+
+  /**
+   * @param thing thing ({@link org.apache.mahout.cf.taste.model.Item} or
+   * {@link org.apache.mahout.cf.taste.model.User} really) to rescore
+   * @param originalScore original score
+   * @return modified score, or {@link Double#NaN} to indicate that this should be excluded entirely
+   */
+  double rescore(T thing, double originalScore);
+
+  boolean isFiltered(T thing);
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/UserBasedRecommender.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/UserBasedRecommender.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/UserBasedRecommender.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/UserBasedRecommender.java Fri May  9 14:35:12 2008
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.mahout.cf.taste.recommender;
+
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.impl.common.Pair;
+import org.apache.mahout.cf.taste.model.User;
+
+import java.util.List;
+
+/**
+ * <p>Interface implemented by "user-based" recommenders.</p>
+ */
+public interface UserBasedRecommender extends Recommender {
+
+  /**
+   * @param userID ID of {@link User} for which to find most similar other {@link User}s
+   * @param howMany desired number of most similar {@link User}s to find
+   * @return {@link User}s most similar to the given user
+   * @throws TasteException if an error occurs while accessing the {@link org.apache.mahout.cf.taste.model.DataModel}
+   */
+  List<User> mostSimilarUsers(Object userID, int howMany) throws TasteException;
+
+  /**
+   * @param userID ID of {@link User} for which to find most similar other {@link User}s
+   * @param howMany desired number of most similar {@link User}s to find
+   * @param rescorer {@link Rescorer} which can adjust user-user correlation
+   * estimates used to determine most similar users
+   * @return {@link User}s most similar to the given user
+   * @throws TasteException if an error occurs while accessing the {@link org.apache.mahout.cf.taste.model.DataModel}
+   */
+  List<User> mostSimilarUsers(Object userID, int howMany, Rescorer<Pair<User, User>> rescorer) throws TasteException;
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/slopeone/DiffStorage.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/slopeone/DiffStorage.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/slopeone/DiffStorage.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/recommender/slopeone/DiffStorage.java Fri May  9 14:35:12 2008
@@ -0,0 +1,84 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.recommender.slopeone;
+
+import org.apache.mahout.cf.taste.common.Refreshable;
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.impl.common.RunningAverage;
+import org.apache.mahout.cf.taste.model.Item;
+import org.apache.mahout.cf.taste.model.Preference;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>Implementations store item-item preference diffs for a
+ * {@link org.apache.mahout.cf.taste.impl.recommender.slopeone.SlopeOneRecommender}.
+ * It actually does a bit more for this implementation, like listing all items that may be
+ * considered for recommedation, in order to maximize what implementations can do
+ * to optimize the slope-one algorithm.</p>
+ *
+ * @see org.apache.mahout.cf.taste.impl.recommender.slopeone.SlopeOneRecommender
+ */
+public interface DiffStorage extends Refreshable {
+
+  /**
+   * @param itemID1
+   * @param itemID2
+   * @return {@link RunningAverage} encapsulating the average difference in preferences
+   *         between items corresponding to <code>itemID1</code> and <code>itemID2</code>, in that direction; that is,
+   *         it's the average of item 2's preferences minus item 1's preferences
+   * @throws TasteException
+   */
+  RunningAverage getDiff(Object itemID1, Object itemID2) throws TasteException;
+
+  /**
+   * @param userID user ID to get diffs for
+   * @param itemID itemID to assess
+   * @param prefs user's preferendces
+   * @return {@link List} of {@link RunningAverage} for that user's item-item diffs
+   * @throws TasteException
+   */
+  RunningAverage[] getDiffs(Object userID, Object itemID, Preference[] prefs) throws TasteException;
+
+  /**
+   * @param itemID
+   * @return {@link RunningAverage} encapsulating the average preference for the given item
+   * @throws TasteException
+   */
+  RunningAverage getAverageItemPref(Object itemID) throws TasteException;
+
+  /**
+   * <p>Updates internal data structures to reflect an update in a preference value for an item.</p>
+   *
+   * @param itemID item to update preference value for
+   * @param prefDelta amount by which preference value changed (or its old value, if being removed
+   * @param remove if <code>true</code>, operation reflects a removal rather than change of preference
+   * @throws TasteException
+   */
+  void updateItemPref(Object itemID, double prefDelta, boolean remove) throws TasteException;
+
+  /**
+   * @param userID
+   * @return {@link Item}s that may possibly be recommended to the given user, which may not be all
+   *         {@link Item}s since the item-item diff matrix may be sparses
+   * @throws TasteException
+   */
+  Set<Item> getRecommendableItems(Object userID) throws TasteException;
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/transforms/CorrelationTransform.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/transforms/CorrelationTransform.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/transforms/CorrelationTransform.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/transforms/CorrelationTransform.java Fri May  9 14:35:12 2008
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.transforms;
+
+import org.apache.mahout.cf.taste.common.Refreshable;
+import org.apache.mahout.cf.taste.common.TasteException;
+
+/**
+ * <p>Implementations encapsulate some transformation on correlation values between two
+ * things, where things might be {@link org.apache.mahout.cf.taste.model.User}s or {@link org.apache.mahout.cf.taste.model.Item}s or
+ * something else.</p>
+ */
+public interface CorrelationTransform<T> extends Refreshable {
+
+  /**
+   * @param thing1
+   * @param thing2
+   * @param value original correlation between thing1 and thing2
+   * (should be in [-1,1])
+   * @return transformed correlation (should be in [-1,1])
+   */
+  double transformCorrelation(T thing1, T thing2, double value) throws TasteException;
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/transforms/PreferenceTransform2.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/transforms/PreferenceTransform2.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/transforms/PreferenceTransform2.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/transforms/PreferenceTransform2.java Fri May  9 14:35:12 2008
@@ -0,0 +1,38 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.transforms;
+
+import org.apache.mahout.cf.taste.common.Refreshable;
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.model.Preference;
+
+/**
+ * <p>Implementations encapsulate a transform on a {@link Preference}'s value. These transformations are
+ * typically applied to values before they are used to compute a correlation value. They are typically not
+ * applied elsewhere; in particular {@link org.apache.mahout.cf.taste.model.DataModel}s no longer use a transform
+ * like this to transform all of their preference values at the source.</p>
+ *
+ * <p>This class sort of replaces the <code>PreferenceTransform</code> interface. It operates similarly, but
+ * is applied a bit differently within the framework. As such I wanted to make this an entirely new interface,
+ * but couldn't pick a name that seemed as applicable. Hence the simplistic name.</p>
+ */
+public interface PreferenceTransform2 extends Refreshable {
+
+  double getTransformedValue(Preference pref) throws TasteException;
+
+}
\ No newline at end of file

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/RecommenderService.jws
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/RecommenderService.jws?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/RecommenderService.jws (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/RecommenderService.jws Fri May  9 14:35:12 2008
@@ -0,0 +1,74 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Yes, no package statement. Since this JWS file is deployed at the server
+// root, it needs to not be in a package in order for Axis to find its generated
+// class file properly
+
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+import org.apache.mahout.cf.taste.web.RecommenderSingleton;
+
+import java.util.List;
+
+/**
+ * <p>A JWS file which exposes a {@link Recommender} as a web service via Axis.
+ * This service needs to be deployed alongside <code>RecommenderServlet</code>.</p>
+ *
+ * <p>This class exposes a subset of the {@link Recommender} API. In particular it
+ * does not support {@link Recommender#getDataModel()}
+ * or {@link Recommender#recommend(Object, int, org.apache.mahout.cf.taste.recommender.Rescorer)}
+ * since these involve data types which can't be transmitted in a web service.</p>
+ */
+public final class RecommenderService {
+
+	private final Recommender recommender;
+
+	public RecommenderService() {
+		// Assuming that this has already been initialized by RecommenderServlet
+		recommender = RecommenderSingleton.getInstance().getRecommender();
+	}
+
+	public String[][] recommend(final String userID, final int howMany) throws Exception {
+		final List<RecommendedItem> recommendations = recommender.recommend(userID, howMany);
+		final String[][] itemIDsAndPrefs = new String[recommendations.size()][2];
+		int i = 0;
+		for (final RecommendedItem item : recommendations) {
+			itemIDsAndPrefs[i][0] = String.valueOf(item.getValue());
+			itemIDsAndPrefs[i][1] = item.getItem().getID().toString();
+			i++;
+		}
+		return itemIDsAndPrefs;
+	}
+
+	public double estimatePreference(final String userID, final String itemID) throws Exception {
+		return recommender.estimatePreference(userID, itemID);
+	}
+
+	public void setPreference(final String userID, final String itemID, final double value) throws Exception {
+		recommender.setPreference(userID, itemID, value);
+	}
+
+	public void removePreference(final String userID, final String itemID) throws Exception {
+		recommender.removePreference(userID, itemID);
+	}
+
+	public void refresh() throws Exception {
+	    recommender.refresh();
+	}
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/RecommenderServlet.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/RecommenderServlet.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/RecommenderServlet.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/RecommenderServlet.java Fri May  9 14:35:12 2008
@@ -0,0 +1,161 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.web;
+
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.impl.model.ByValuePreferenceComparator;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.Preference;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * <p>A servlet which returns recommendations, as its name implies. The servlet accepts GET and POST
+ * HTTP requests, and looks for two parameters:</p>
+ *
+ * <ul>
+ * <li><em>userID</em>: the user ID for which to produce recommendations</li>
+ * <li><em>howMany</em>: the number of recommendations to produce</li>
+ * <li><em>debug</em>: (optional) output a lot of information that is useful in debugging.
+ * Defaults to false, of course.</li>
+ * </ul>
+ *
+ * <p>The response is text, and contains a list of the IDs of recommended items, in descending
+ * order of relevance, one per line.</p>
+ *
+ * <p>For example, you can get 10 recommendations for user 123 from the following URL (assuming
+ * you are running taste in a web application running locally on port 8080):<br/>
+ * <code>http://localhost:8080/taste/RecommenderServlet?userID=123&amp;howMany=1</code></p>
+ *
+ * <p>This servlet requires one <code>init-param</code> in <code>web.xml</code>: it must find
+ * a parameter named "recommender-class" which is the name of a class that implements
+ * {@link Recommender} and has a no-arg constructor. The servlet will instantiate and use
+ * this {@link Recommender} to produce recommendations.</p>
+ */
+public final class RecommenderServlet extends HttpServlet {
+
+  private static final int NUM_TOP_PREFERENCES = 20;
+  private static final int DEFAULT_HOW_MANY = 20;
+
+  private Recommender recommender;
+
+  @Override
+  public void init(ServletConfig config) throws ServletException {
+    super.init(config);
+    String recommenderClassName = config.getInitParameter("recommender-class");
+    if (recommenderClassName == null) {
+      throw new ServletException("Servlet init-param \"recommender-class\" is not defined");
+    }
+    try {
+      RecommenderSingleton.initializeIfNeeded(recommenderClassName);
+    } catch (TasteException te) {
+      throw new ServletException(te);
+    }
+    recommender = RecommenderSingleton.getInstance().getRecommender();
+  }
+
+  @Override
+  public void doGet(HttpServletRequest request,
+                    HttpServletResponse response) throws ServletException {
+
+    String userID = request.getParameter("userID");
+    if (userID == null) {
+      throw new ServletException("userID was not specified");
+    }
+    String howManyString = request.getParameter("howMany");
+    int howMany = howManyString == null ? DEFAULT_HOW_MANY : Integer.parseInt(howManyString);
+    boolean debug = Boolean.valueOf(request.getParameter("debug"));
+
+    try {
+
+      List<RecommendedItem> items = recommender.recommend(userID, howMany);
+
+      response.setContentType("text/plain");
+      response.setCharacterEncoding("UTF-8");
+      response.setHeader("Cache-Control", "no-cache");
+
+      PrintWriter writer = response.getWriter();
+      if (debug) {
+        DataModel dataModel = recommender.getDataModel();
+        writer.print("User:");
+        writer.println(dataModel.getUser(userID));
+        writer.print("Recommender: ");
+        writer.println(recommender);
+        writer.println();
+        writer.print("Top ");
+        writer.print(NUM_TOP_PREFERENCES);
+        writer.println(" Preferences:");
+        Preference[] rawPrefs = dataModel.getUser(userID).getPreferencesAsArray();
+        int length = rawPrefs.length;
+        Preference[] sortedPrefs = new Preference[length];
+        System.arraycopy(rawPrefs, 0, sortedPrefs, 0, length);
+        Arrays.sort(sortedPrefs, Collections.reverseOrder(ByValuePreferenceComparator.getInstance()));
+        // Cap this at 20 just to be brief
+        int max = Math.min(NUM_TOP_PREFERENCES, length);
+        for (int i = 0; i < max; i++) {
+          Preference pref = sortedPrefs[i];
+          writer.print(pref.getValue());
+          writer.print('\t');
+          writer.println(pref.getItem());
+        }
+        writer.println();
+        writer.println("Recommendations:");
+        for (RecommendedItem recommendedItem : items) {
+          writer.print(recommendedItem.getValue());
+          writer.print('\t');
+          writer.println(recommendedItem.getItem());
+        }
+      } else {
+        for (RecommendedItem recommendedItem : items) {
+          writer.print(recommendedItem.getValue());
+          writer.print('\t');
+          writer.println(recommendedItem.getItem().getID());
+        }
+      }
+
+    } catch (TasteException te) {
+      throw new ServletException(te);
+    } catch (IOException ioe) {
+      throw new ServletException(ioe);
+    }
+
+  }
+
+  @Override
+  public void doPost(HttpServletRequest request,
+                     HttpServletResponse response) throws ServletException {
+    doGet(request, response);
+  }
+
+  @Override
+  public String toString() {
+    return "RecommenderServlet[recommender:" + recommender + ']';
+  }
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/RecommenderSingleton.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/RecommenderSingleton.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/RecommenderSingleton.java (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/RecommenderSingleton.java Fri May  9 14:35:12 2008
@@ -0,0 +1,65 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.web;
+
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+
+/**
+ * <p>A singleton which holds an instance of a {@link Recommender}. This is used to share
+ * a {@link Recommender} between {@link RecommenderServlet} and <code>RecommenderService.jws</code>.</p>
+ */
+public final class RecommenderSingleton {
+
+  private final Recommender recommender;
+
+  private static RecommenderSingleton instance;
+
+  public static synchronized RecommenderSingleton getInstance() {
+    if (instance == null) {
+      throw new IllegalStateException("Not initialized");
+    }
+    return instance;
+  }
+
+  public static synchronized void initializeIfNeeded(String recommenderClassName) throws TasteException {
+    if (instance == null) {
+      instance = new RecommenderSingleton(recommenderClassName);
+    }
+  }
+
+  private RecommenderSingleton(String recommenderClassName) throws TasteException {
+    if (recommenderClassName == null) {
+      throw new IllegalArgumentException("Recommender class name is null");
+    }
+    try {
+      recommender = Class.forName(recommenderClassName).asSubclass(Recommender.class).newInstance();
+    } catch (ClassNotFoundException cnfe) {
+      throw new TasteException(cnfe);
+    } catch (InstantiationException ie) {
+      throw new TasteException(ie);
+    } catch (IllegalAccessException iae) {
+      throw new TasteException(iae);
+    }
+  }
+
+  public Recommender getRecommender() {
+    return recommender;
+  }
+
+}

Added: lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/web.xml
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/web.xml?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/web.xml (added)
+++ lucene/mahout/trunk/core/src/main/java/org/apache/mahout/cf/taste/web/web.xml Fri May  9 14:35:12 2008
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+        "http://java.sun.com/dtd/web-app_2_3.dtd">
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<web-app>
+
+  <servlet>
+    <servlet-name>taste-recommender</servlet-name>
+    <display-name>Taste Recommender</display-name>
+    <description>Taste recommender servlet</description>
+    <servlet-class>org.apache.mahout.cf.taste.web.RecommenderServlet</servlet-class>
+    <init-param>
+      <param-name>recommender-class</param-name>
+      <param-value>@RECOMMENDER_CLASS@</param-value>
+    </init-param>
+  </servlet>
+
+  <servlet>
+    <servlet-name>axis</servlet-name>
+    <display-name>Apache-Axis Servlet</display-name>
+    <servlet-class>org.apache.axis.transport.http.AxisServlet</servlet-class>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>taste-recommender</servlet-name>
+    <url-pattern>/RecommenderServlet</url-pattern>
+  </servlet-mapping>
+  <servlet-mapping>
+    <servlet-name>axis</servlet-name>
+    <url-pattern>*.jws</url-pattern>
+  </servlet-mapping>
+
+  <!-- The rest of the config is adapted from Axis's default web app -->
+
+  <session-config>
+    <session-timeout>5</session-timeout>
+  </session-config>
+
+  <!-- currently the W3C havent settled on a media type for WSDL;
+     http://www.w3.org/TR/2003/WD-wsdl12-20030303/#ietf-draft
+     for now we go with the basic 'it's XML' response -->
+  <mime-mapping>
+    <extension>wsdl</extension>
+    <mime-type>text/xml</mime-type>
+  </mime-mapping>
+  <mime-mapping>
+    <extension>xsd</extension>
+    <mime-type>text/xml</mime-type>
+  </mime-mapping>
+
+</web-app>
\ No newline at end of file

Added: lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/common/CommonTest.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/common/CommonTest.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/common/CommonTest.java (added)
+++ lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/common/CommonTest.java Fri May  9 14:35:12 2008
@@ -0,0 +1,42 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.common;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * <p>Tests common classes.</p>
+ */
+public final class CommonTest extends TasteTestCase {
+
+  public void testException() {
+    // Just make sure this all doesn't, ah, throw an exception
+    TasteException te1 = new TasteException();
+    TasteException te2 = new TasteException(te1);
+    TasteException te3 = new TasteException(te2.toString(), te2);
+    TasteException te4 = new TasteException(te3.toString());
+    te4.printStackTrace(new PrintStream(new ByteArrayOutputStream()));
+    te4.printStackTrace(new PrintWriter(new OutputStreamWriter(new ByteArrayOutputStream())));
+  }
+
+}

Added: lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/LoadTest.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/LoadTest.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/LoadTest.java (added)
+++ lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/LoadTest.java Fri May  9 14:35:12 2008
@@ -0,0 +1,161 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl;
+
+import junit.textui.TestRunner;
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.correlation.ItemCorrelation;
+import org.apache.mahout.cf.taste.correlation.UserCorrelation;
+import org.apache.mahout.cf.taste.impl.common.RandomUtils;
+import org.apache.mahout.cf.taste.impl.correlation.PearsonCorrelation;
+import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
+import org.apache.mahout.cf.taste.impl.model.GenericItem;
+import org.apache.mahout.cf.taste.impl.model.GenericPreference;
+import org.apache.mahout.cf.taste.impl.model.GenericUser;
+import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
+import org.apache.mahout.cf.taste.impl.recommender.CachingRecommender;
+import org.apache.mahout.cf.taste.impl.recommender.GenericItemBasedRecommender;
+import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
+import org.apache.mahout.cf.taste.impl.recommender.slopeone.SlopeOneRecommender;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.Item;
+import org.apache.mahout.cf.taste.model.Preference;
+import org.apache.mahout.cf.taste.model.User;
+import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <p>Generates load on the whole implementation, for profiling purposes mostly.</p>
+ */
+public final class LoadTest extends TasteTestCase {
+
+  private static final Logger log = Logger.getLogger(LoadTest.class.getName());
+
+  private static final int NUM_USERS = 1600;
+  private static final int NUM_ITEMS = 800;
+  private static final int NUM_PREFS = 20;
+  private static final int NUM_THREADS = 4;
+
+  private final Random random = RandomUtils.getRandom();
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    setLogLevel(Level.INFO);
+  }
+
+  public void testSlopeOneLoad() throws Exception {
+    DataModel model = createModel();
+    Recommender recommender = new CachingRecommender(new SlopeOneRecommender(model));
+    doTestLoad(recommender, 30);
+  }
+
+  public void testItemLoad() throws Exception {
+    DataModel model = createModel();
+    ItemCorrelation itemCorrelation = new PearsonCorrelation(model);
+    Recommender recommender = new CachingRecommender(new GenericItemBasedRecommender(model, itemCorrelation));
+    doTestLoad(recommender, 120);
+  }
+
+  public void testUserLoad() throws Exception {
+    DataModel model = createModel();
+    UserCorrelation userCorrelation = new PearsonCorrelation(model);
+    UserNeighborhood neighborhood = new NearestNUserNeighborhood(10, userCorrelation, model);
+    Recommender recommender =
+            new CachingRecommender(new GenericUserBasedRecommender(model, neighborhood, userCorrelation));
+    doTestLoad(recommender, 20);
+  }
+
+  private DataModel createModel() {
+
+    List<Item> items = new ArrayList<Item>(NUM_ITEMS);
+    for (int i = 0; i < NUM_ITEMS; i++) {
+      items.add(new GenericItem<String>(String.valueOf(i)));
+    }
+
+    List<User> users = new ArrayList<User>(NUM_USERS);
+    for (int i = 0; i < NUM_USERS; i++) {
+      int numPrefs = random.nextInt(NUM_PREFS) + 1;
+      List<Preference> prefs = new ArrayList<Preference>(numPrefs);
+      for (int j = 0; j < numPrefs; j++) {
+        prefs.add(new GenericPreference(null, items.get(random.nextInt(NUM_ITEMS)), random.nextDouble()));
+      }
+      GenericUser<String> user = new GenericUser<String>(String.valueOf(i), prefs);
+      users.add(user);
+    }
+
+    return new GenericDataModel(users);
+  }
+
+  private void doTestLoad(Recommender recommender, int allowedTimeSec)
+          throws InterruptedException, ExecutionException {
+
+    ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
+    Collection<Future<?>> futures = new ArrayList<Future<?>>(NUM_THREADS);
+    Callable<?> loadTask = new LoadWorker(recommender);
+
+    long start = System.currentTimeMillis();
+    for (int i = 0; i < NUM_THREADS; i++) {
+      futures.add(executor.submit(loadTask));
+    }
+    for (Future<?> future : futures) {
+      future.get();
+    }
+    long end = System.currentTimeMillis();
+    long timeMS = end - start;
+    log.info("Load test completed in " + timeMS + "ms");
+    assertTrue(timeMS < 1000L * (long) allowedTimeSec);
+  }
+
+  private final class LoadWorker implements Callable<Object> {
+
+    private final Recommender recommender;
+
+    private LoadWorker(Recommender recommender) {
+      this.recommender = recommender;
+    }
+
+    public Object call() throws TasteException {
+      for (int i = 0; i < NUM_USERS / 2; i++) {
+        recommender.recommend(String.valueOf(random.nextInt(NUM_USERS)), 10);
+      }
+      recommender.refresh();
+      for (int i = NUM_USERS / 2; i < NUM_USERS; i++) {
+        recommender.recommend(String.valueOf(random.nextInt(NUM_USERS)), 10);
+      }
+      return null;
+    }
+  }
+
+  public static void main(String... args) {
+    TestRunner.run(LoadTest.class);
+  }
+
+}

Added: lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/TasteTestCase.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/TasteTestCase.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/TasteTestCase.java (added)
+++ lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/TasteTestCase.java Fri May  9 14:35:12 2008
@@ -0,0 +1,87 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl;
+
+import junit.framework.TestCase;
+import org.apache.mahout.cf.taste.impl.common.RandomUtils;
+import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
+import org.apache.mahout.cf.taste.impl.model.GenericItem;
+import org.apache.mahout.cf.taste.impl.model.GenericPreference;
+import org.apache.mahout.cf.taste.impl.model.GenericUser;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.Preference;
+import org.apache.mahout.cf.taste.model.User;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public abstract class TasteTestCase extends TestCase {
+
+  /**
+   * "Close enough" value for floating-point comparisons.
+   */
+  public static final double EPSILON = 0.00001;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    // make sure we always show all log output during tests
+    setLogLevel(Level.FINEST);
+    RandomUtils.useTestSeed();
+  }
+
+  protected static void setLogLevel(Level level) {
+    Logger log = Logger.getLogger("org.apache.mahout.cf.taste.impl");
+    log.setLevel(level);
+    for (Handler handler : log.getHandlers()) {
+      handler.setLevel(level);
+    }
+  }
+
+  public static User getUser(String userID, double... values) {
+    List<Preference> prefs = new ArrayList<Preference>(values.length);
+    int i = 0;
+    for (double value : values) {
+      prefs.add(new GenericPreference(null, new GenericItem<String>(String.valueOf(i)), value));
+      i++;
+    }
+    return new GenericUser<String>(userID, prefs);
+  }
+
+  public static DataModel getDataModel(User... users) {
+    return new GenericDataModel(Arrays.asList(users));
+  }
+
+  public static DataModel getDataModel() {
+    return new GenericDataModel(getMockUsers());
+  }
+
+  public static List<User> getMockUsers() {
+    List<User> users = new ArrayList<User>(4);
+    users.add(getUser("test1", 0.1, 0.3));
+    users.add(getUser("test2", 0.2, 0.3, 0.3));
+    users.add(getUser("test3", 0.4, 0.3, 0.5));
+    users.add(getUser("test4", 0.7, 0.3, 0.8));
+    return users;
+  }
+
+}

Added: lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/EmptyIteratorTest.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/EmptyIteratorTest.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/EmptyIteratorTest.java (added)
+++ lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/EmptyIteratorTest.java Fri May  9 14:35:12 2008
@@ -0,0 +1,49 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.common;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public final class EmptyIteratorTest extends TasteTestCase {
+
+  public void testIterator() {
+    Iterator<Object> mock = new EmptyIterator<Object>();
+    assertFalse(mock.hasNext());
+    try {
+      mock.next();
+      fail("Should have thrown NoSuchElementException");
+    } catch (NoSuchElementException nsee) {
+      // good
+    }
+    try {
+      mock.remove();
+      fail("Should have thrown UnsupportedOperationException");
+    } catch (UnsupportedOperationException uoe) {
+      // good
+    }
+  }
+
+  public void testIterable() {
+    Iterable<Object> mock = new EmptyIterable<Object>();
+    assertNotNull(mock.iterator());
+  }
+
+}

Added: lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/FastMapTest.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/FastMapTest.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/FastMapTest.java (added)
+++ lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/FastMapTest.java Fri May  9 14:35:12 2008
@@ -0,0 +1,199 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.common;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * <p>Tests {@link FastMap}.</p>
+ */
+public final class FastMapTest extends TasteTestCase {
+
+  public void testPutAndGet() {
+    FastMap<String, String> map = new FastMap<String, String>();
+    assertNull(map.get("foo"));
+    map.put("foo", "bar");
+    assertEquals("bar", map.get("foo"));
+  }
+
+  public void testRemove() {
+    Map<String, String> map = new FastMap<String, String>();
+    map.put("foo", "bar");
+    map.remove("foo");
+    assertEquals(0, map.size());
+    assertTrue(map.isEmpty());
+    assertNull(map.get("foo"));
+  }
+
+  public void testClear() {
+    Map<String, String> map = new FastMap<String, String>();
+    map.put("foo", "bar");
+    map.clear();
+    assertEquals(0, map.size());
+    assertTrue(map.isEmpty());
+    assertNull(map.get("foo"));
+  }
+
+  public void testSizeEmpty() {
+    Map<String, String> map = new FastMap<String, String>();
+    assertEquals(0, map.size());
+    assertTrue(map.isEmpty());
+    map.put("foo", "bar");
+    assertEquals(1, map.size());
+    assertFalse(map.isEmpty());
+    map.remove("foo");
+    assertEquals(0, map.size());
+    assertTrue(map.isEmpty());
+  }
+
+  public void testContains() {
+    FastMap<String, String> map = buildTestFastMap();
+    assertTrue(map.containsKey("foo"));
+    assertTrue(map.containsKey("baz"));
+    assertTrue(map.containsKey("alpha"));
+    assertTrue(map.containsValue("bar"));
+    assertTrue(map.containsValue("bang"));
+    assertTrue(map.containsValue("beta"));
+    assertFalse(map.containsKey("something"));
+    assertFalse(map.containsValue("something"));
+  }
+
+  public void testNull() {
+    Map<String, String> map = new FastMap<String, String>();
+    try {
+      map.put(null, "bar");
+      fail("Should have thrown NullPointerException");
+    } catch (NullPointerException npe) {
+      // good
+    }
+    try {
+      map.put("foo", null);
+      fail("Should have thrown NullPointerException");
+    } catch (NullPointerException npe) {
+      // good
+    }
+    assertNull(map.get(null));
+  }
+
+  public void testRehash() {
+    FastMap<String, String> map = buildTestFastMap();
+    map.remove("foo");
+    map.rehash();
+    assertNull(map.get("foo"));
+    assertEquals("bang", map.get("baz"));
+  }
+
+  public void testGrow() {
+    FastMap<String, String> map = new FastMap<String, String>(1, FastMap.NO_MAX_SIZE);
+    map.put("foo", "bar");
+    map.put("baz", "bang");
+    assertEquals("bar", map.get("foo"));
+    assertEquals("bang", map.get("baz"));
+  }
+
+  public void testKeySet() {
+    FastMap<String, String> map = buildTestFastMap();
+    Collection<String> expected = new HashSet<String>(3);
+    expected.add("foo");
+    expected.add("baz");
+    expected.add("alpha");
+    Set<String> actual = map.keySet();
+    assertTrue(expected.containsAll(actual));
+    assertTrue(actual.containsAll(expected));
+  }
+
+  public void testValues() {
+    FastMap<String, String> map = buildTestFastMap();
+    Collection<String> expected = new HashSet<String>(3);
+    expected.add("bar");
+    expected.add("bang");
+    expected.add("beta");
+    Collection<String> actual = map.values();
+    assertTrue(expected.containsAll(actual));
+    assertTrue(actual.containsAll(expected));
+  }
+
+  public void testEntrySet() {
+    FastMap<String, String> map = buildTestFastMap();
+    Set<Map.Entry<String, String>> actual = map.entrySet();
+    Collection<String> expectedKeys = new HashSet<String>(3);
+    expectedKeys.add("foo");
+    expectedKeys.add("baz");
+    expectedKeys.add("alpha");
+    Collection<String> expectedValues = new HashSet<String>(3);
+    expectedValues.add("bar");
+    expectedValues.add("bang");
+    expectedValues.add("beta");
+    assertEquals(3, actual.size());
+    for (Map.Entry<String, String> entry : actual) {
+      expectedKeys.remove(entry.getKey());
+      expectedValues.remove(entry.getValue());
+    }
+    assertEquals(0, expectedKeys.size());
+    assertEquals(0, expectedValues.size());
+  }
+
+  public void testVersusHashMap() {
+    Map<Integer, String> actual = new FastMap<Integer, String>(1, 1000000);
+    Map<Integer, String> expected = new HashMap<Integer, String>(1000000);
+    Random r = RandomUtils.getRandom();
+    for (int i = 0; i < 1000000; i++) {
+      double d = r.nextDouble();
+      Integer key = r.nextInt(100);
+      if (d < 0.4) {
+        assertEquals(expected.get(key), actual.get(key));
+      } else {
+        if (d < 0.7) {
+          assertEquals(expected.put(key, "foo"), actual.put(key, "foo"));
+        } else {
+          assertEquals(expected.remove(key), actual.remove(key));
+        }
+        assertEquals(expected.size(), actual.size());
+        assertEquals(expected.isEmpty(), actual.isEmpty());
+      }
+    }
+  }
+
+  public void testMaxSize() {
+    Map<String, String> map = new FastMap<String, String>(1, 1);
+    map.put("foo", "bar");
+    assertEquals(1, map.size());
+    map.put("baz", "bang");
+    assertEquals(1, map.size());
+    assertNull(map.get("foo"));
+    map.put("baz", "buzz");
+    assertEquals(1, map.size());
+    assertEquals("buzz", map.get("baz"));
+  }
+
+  private static FastMap<String, String> buildTestFastMap() {
+    FastMap<String, String> map = new FastMap<String, String>();
+    map.put("foo", "bar");
+    map.put("baz", "bang");
+    map.put("alpha", "beta");
+    return map;
+  }
+
+}

Added: lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/IteratorUtilsTest.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/IteratorUtilsTest.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/IteratorUtilsTest.java (added)
+++ lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/IteratorUtilsTest.java Fri May  9 14:35:12 2008
@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.common;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.TreeSet;
+
+public final class IteratorUtilsTest extends TestCase {
+
+  private static final List<String> TEST_DATA;
+
+  static {
+    List<String> temp = new ArrayList<String>(3);
+    temp.add("bar");
+    temp.add("baz");
+    temp.add("foo");
+    TEST_DATA = Collections.unmodifiableList(temp);
+  }
+
+  public void testArray() {
+    String[] data = TEST_DATA.toArray(new String[3]);
+    assertEquals(TEST_DATA, IteratorUtils.iterableToList(new ArrayIterator<String>(data)));
+  }
+
+  public void testList() {
+    assertEquals(TEST_DATA, IteratorUtils.iterableToList(TEST_DATA));
+  }
+
+  public void testCollection() {
+    Collection<String> data = new TreeSet<String>();
+    data.add("foo");
+    data.add("bar");
+    data.add("baz");
+    assertEquals(TEST_DATA, IteratorUtils.iterableToList(data));
+  }
+
+  public void testComparator() {
+    Collection<String> data = new ArrayList<String>(3);
+    data.add("baz");
+    data.add("bar");
+    data.add("foo");
+    assertEquals(TEST_DATA, IteratorUtils.iterableToList(data, String.CASE_INSENSITIVE_ORDER));
+  }
+
+  public void testEmpty() {
+    assertEquals(0, IteratorUtils.iterableToList(new ArrayList<Object>(0)).size());
+  }
+
+}

Added: lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/RunningAverageAndStdDevTest.java
URL: http://svn.apache.org/viewvc/lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/RunningAverageAndStdDevTest.java?rev=654943&view=auto
==============================================================================
--- lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/RunningAverageAndStdDevTest.java (added)
+++ lucene/mahout/trunk/core/src/test/java/org/apache/mahout/cf/taste/impl/common/RunningAverageAndStdDevTest.java Fri May  9 14:35:12 2008
@@ -0,0 +1,65 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.cf.taste.impl.common;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+
+public final class RunningAverageAndStdDevTest extends TasteTestCase {
+
+  public void testFull() {
+    doTestAverageAndStdDev(new FullRunningAverageAndStdDev());
+  }
+
+  public void testCompact() {
+    doTestAverageAndStdDev(new CompactRunningAverageAndStdDev());
+  }
+
+  private static void doTestAverageAndStdDev(RunningAverageAndStdDev average) {
+
+    assertEquals(0, average.getCount());
+    assertTrue(Double.isNaN(average.getAverage()));
+    assertTrue(Double.isNaN(average.getStandardDeviation()));
+
+    average.addDatum(6.0);
+    assertEquals(1, average.getCount());
+    assertEquals(6.0, average.getAverage(), EPSILON);
+    assertTrue(Double.isNaN(average.getStandardDeviation()));
+
+    average.addDatum(6.0);
+    assertEquals(2, average.getCount());
+    assertEquals(6.0, average.getAverage(), EPSILON);
+    assertEquals(0.0, average.getStandardDeviation(), EPSILON);
+
+    average.removeDatum(6.0);
+    assertEquals(1, average.getCount());
+    assertEquals(6.0, average.getAverage(), EPSILON);
+    assertTrue(Double.isNaN(average.getStandardDeviation()));
+
+    average.addDatum(-4.0);
+    assertEquals(2, average.getCount());
+    assertEquals(1.0, average.getAverage(), EPSILON);
+    assertEquals(5.0 * 1.4142135623730951, average.getStandardDeviation(), EPSILON);
+
+    average.removeDatum(4.0);
+    assertEquals(1, average.getCount());
+    assertEquals(-2.0, average.getAverage(), EPSILON);
+    assertTrue(Double.isNaN(average.getStandardDeviation()));
+
+  }
+
+}