/*
 * LensKit, an open source recommender systems toolkit.
 * Copyright 2010-2011 Regents of the University of Minnesota
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This program 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
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.grouplens.lenskit.knn.item;

import it.unimi.dsi.fastutil.doubles.DoubleComparators;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongSortedSet;
import org.grouplens.lenskit.collections.ScoredLongArrayList;
import org.grouplens.lenskit.collections.ScoredLongList;
import org.grouplens.lenskit.collections.ScoredLongListIterator;
import org.grouplens.lenskit.knn.params.ModelSize;
import org.grouplens.lenskit.util.ScoredItemAccumulator;
import org.grouplens.lenskit.util.TopNScoredItemAccumulator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Accumulator for item similarities that go into the item-item CF model.
 *
 * @author Michael Ekstrand <ekstrand@cs.umn.edu>
 */
public class ItemItemModelAccumulator {
    private static final Logger logger = LoggerFactory.getLogger(ItemItemModelAccumulator.class);

    private Long2ObjectMap<ScoredItemAccumulator> columns;
    private final LongSortedSet itemUniverse;

    public ItemItemModelAccumulator(@ModelSize int size,
            LongSortedSet entities) {
        logger.debug("Using model size of {} for {} items",
                     size, entities.size());
        itemUniverse = entities;
        columns = new Long2ObjectOpenHashMap<ScoredItemAccumulator>(entities.size());
        LongIterator it = entities.iterator();
        while (it.hasNext()) {
            columns.put(it.nextLong(), new TopNScoredItemAccumulator(size));
        }
    }

    public ItemItemModel build() {
        Long2ObjectMap<ScoredLongList> data = new Long2ObjectOpenHashMap<ScoredLongList>(columns.size());
        for (Entry<ScoredItemAccumulator> colEntry: columns.long2ObjectEntrySet()) {
            final long j = colEntry.getLongKey();
            ScoredLongList column = colEntry.getValue().finish();
            colEntry.setValue(null);
            ScoredLongListIterator iter = column.iterator();
            while (iter.hasNext()) {
                final long i = iter.nextLong();
                final double s = iter.getScore();
                ScoredLongList row = data.get(i);
                if (row == null) {
                    row = new ScoredLongArrayList();
                    data.put(i, row);
                }
                row.add(j, s);
            }
        }
        for (ScoredLongList row: data.values()) {
            ScoredLongArrayList impl = (ScoredLongArrayList) row;
            impl.trim();
            impl.sort(DoubleComparators.OPPOSITE_COMPARATOR);
        }
        ItemItemModel model = new ItemItemModel(itemUniverse, data);
        columns = null;
        return model;
    }

    /**
     * Store an entry in the similarity matrix.
     * @param i The matrix row (an item ID).
     * @param j The matrix column (an item ID).
     * @param sim The similarity between items {@code j} and {@code i}. As documented in the
     *            {@link org.grouplens.lenskit.knn.item package docs}, this is \(s(j,i)\).
     */
    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
    public void put(long i, long j, double sim) {
        // We only accept nonnegative similarities
        if (sim <= 0.0) return;

        // concurrent read-only array access permitted
        ScoredItemAccumulator q = columns.get(j);
        // synchronize on this row to add item
        synchronized (q) {
            q.put(i, sim);
        }
    }
}

@@ -892,64 +892,8 @@
 m;%0A%0A
-import it.unimi.dsi.fastutil.doubles.DoubleComparators;%0A
 impo
@@ -1158,190 +1158,64 @@
 et;%0A
-import org.grouplens.lenskit.collections.ScoredLongArrayList;%0Aimport org.grouplens.lenskit.collections.ScoredLongList;%0Aimport org.grouplens.lenskit.collections.ScoredLongListIterator
+%0Aimport org.grouplens.lenskit.collections.ScoredLongList
 ;%0Aim
@@ -1763,22 +1763,19 @@
 ulator%3E 
-column
+row
 s;%0A    p
@@ -2053,30 +2053,27 @@
 es;%0A        
-column
+row
 s = new Long
@@ -2222,22 +2222,19 @@
         
-column
+row
 s.put(it
@@ -2425,22 +2425,19 @@
 ngList%3E(
-column
+row
 s.size()
@@ -2485,24 +2485,16 @@
 or%3E 
-colEntry: column
+row: row
 s.lo
@@ -2532,794 +2532,58 @@
     
-final long j = colEntry.getLongKey();%0A            ScoredLongList column = colEntry.getValue().finish();%0A            colEntry.setValue(null);%0A            ScoredLongListIterator iter = column.iterator();%0A            while (iter.hasNext()) %7B%0A                final long i = iter.nextLong();%0A                final double s = iter.getScore();%0A                ScoredLongList row = data.get(i);%0A                if (row == null) %7B%0A                    row = new ScoredLongArrayList();%0A                    data.put(i, row);%0A                %7D%0A                row.add(j, s);%0A            %7D%0A        %7D%0A        for (ScoredLongList row: data.v
+data.put(row.getLongKey(), row.getV
 alue
-s
 ()
-) %7B%0A            ScoredLongArrayList impl = (ScoredLongArrayList) row;%0A            impl.trim();%0A            impl.sort(DoubleComparators.OPPOSITE_COMPARATOR
+.finish()
 );%0A 
@@ -2668,22 +2668,19 @@
         
-column
+row
 s = null
@@ -3351,21 +3351,18 @@
 q = 
-column
+row
 s.get(
-j
+i
 );%0A 
@@ -3452,17 +3452,17 @@
   q.put(
-i
+j
 , sim);%0A

Truncate models along rows again