/*
* Copyright (C) 2015 Information Retrieval Group at Universidad Autónoma
* de Madrid, http://ir.ii.uam.es
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package es.uam.eps.ir.ranksys.fast.preference;
import es.uam.eps.ir.ranksys.core.preference.IdPref;
import es.uam.eps.ir.ranksys.fast.index.FastItemIndex;
import es.uam.eps.ir.ranksys.fast.index.FastUserIndex;
import java.io.Serializable;
import java.util.ArrayList;
import static java.util.Comparator.comparingInt;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.jooq.lambda.function.Function4;
import org.jooq.lambda.tuple.Tuple3;
import org.jooq.lambda.tuple.Tuple4;
import org.ranksys.fast.preference.FastPointWisePreferenceData;
import org.ranksys.fast.preference.StreamsAbstractFastPreferenceData;
/**
* Simple implementation of FastPreferenceData backed by nested lists.
*
* @param <U> type of the users
* @param <I> type of the items
* @author Saúl Vargas (saul.vargas@uam.es)
*/
public class SimpleFastPreferenceData<U, I> extends StreamsAbstractFastPreferenceData<U, I> implements FastPointWisePreferenceData<U, I>, Serializable {
private final int numPreferences;
private final List<List<IdxPref>> uidxList;
private final List<List<IdxPref>> iidxList;
/**
* Constructor with default IdxPref to IdPref converter.
*
* @param numPreferences number of total preferences
* @param uidxList list of lists of preferences by user index
* @param iidxList list of lists of preferences by item index
* @param uIndex user index
* @param iIndex item index
*/
protected SimpleFastPreferenceData(int numPreferences, List<List<IdxPref>> uidxList, List<List<IdxPref>> iidxList,
FastUserIndex<U> uIndex, FastItemIndex<I> iIndex) {
this(numPreferences, uidxList, iidxList, uIndex, iIndex,
(Function<IdxPref, IdPref<I>> & Serializable) p -> new IdPref<>(iIndex.iidx2item(p)),
(Function<IdxPref, IdPref<U>> & Serializable) p -> new IdPref<>(uIndex.uidx2user(p)));
}
/**
* Constructor with custom IdxPref to IdPref converter.
*
* @param numPreferences number of total preferences
* @param uidxList list of lists of preferences by user index
* @param iidxList list of lists of preferences by item index
* @param uIndex user index
* @param iIndex item index
* @param uPrefFun user IdxPref to IdPref converter
* @param iPrefFun item IdxPref to IdPref converter
*/
protected SimpleFastPreferenceData(int numPreferences, List<List<IdxPref>> uidxList, List<List<IdxPref>> iidxList,
FastUserIndex<U> uIndex, FastItemIndex<I> iIndex,
Function<IdxPref, IdPref<I>> uPrefFun, Function<IdxPref, IdPref<U>> iPrefFun) {
super(uIndex, iIndex, uPrefFun, iPrefFun);
this.numPreferences = numPreferences;
this.uidxList = uidxList;
this.iidxList = iidxList;
uidxList.parallelStream()
.filter(Objects::nonNull)
.forEach(l -> l.sort(comparingInt(IdxPref::v1)));
iidxList.parallelStream()
.filter(Objects::nonNull)
.forEach(l -> l.sort(comparingInt(IdxPref::v1)));
}
@Override
public int numUsers(int iidx) {
if (iidxList.get(iidx) == null) {
return 0;
}
return iidxList.get(iidx).size();
}
@Override
public int numItems(int uidx) {
if (uidxList.get(uidx) == null) {
return 0;
}
return uidxList.get(uidx).size();
}
@Override
public Stream<IdxPref> getUidxPreferences(int uidx) {
if (uidxList.get(uidx) == null) {
return Stream.empty();
} else {
return uidxList.get(uidx).stream();
}
}
@Override
public Stream<IdxPref> getIidxPreferences(int iidx) {
if (iidxList.get(iidx) == null) {
return Stream.empty();
} else {
return iidxList.get(iidx).stream();
}
}
@Override
public int numPreferences() {
return numPreferences;
}
@Override
public IntStream getUidxWithPreferences() {
return IntStream.range(0, numUsers())
.filter(uidx -> uidxList.get(uidx) != null);
}
@Override
public IntStream getIidxWithPreferences() {
return IntStream.range(0, numItems())
.filter(iidx -> iidxList.get(iidx) != null);
}
@Override
public int numUsersWithPreferences() {
return (int) uidxList.stream()
.filter(Objects::nonNull).count();
}
@Override
public int numItemsWithPreferences() {
return (int) iidxList.stream()
.filter(Objects::nonNull).count();
}
@Override
public Optional<IdxPref> getPreference(int uidx, int iidx) {
List<IdxPref> uList = uidxList.get(uidx);
int low = 0;
int high = uList.size() - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
IdxPref p = uList.get(mid);
int cmp = Integer.compare(p.v1, iidx);
if (cmp < 0) {
low = mid + 1;
} else if (cmp > 0) {
high = mid - 1;
} else {
return Optional.of(p);
}
}
return Optional.empty();
}
@Override
public Optional<? extends IdPref<I>> getPreference(U u, I i) {
Optional<? extends IdxPref> pref = getPreference(user2uidx(u), item2iidx(i));
if (!pref.isPresent()) {
return Optional.empty();
} else {
return Optional.of(uPrefFun.apply(pref.get()));
}
}
/**
* Loads a SimpleFastPreferenceData from a stream of user-item-value triples.
*
* @param <U> user type
* @param <I> item type
* @param tuples stream of user-item-value triples
* @param uIndex user index
* @param iIndex item index
* @return an instance of SimpleFastPreferenceData containing the data from the input stream
*/
public static <U, I> SimpleFastPreferenceData<U, I> load(Stream<Tuple3<U, I, Double>> tuples, FastUserIndex<U> uIndex, FastItemIndex<I> iIndex) {
return load(tuples.map(t -> t.concat((Void) null)),
(uidx, iidx, v, o) -> new IdxPref(iidx, v),
(uidx, iidx, v, o) -> new IdxPref(uidx, v),
uIndex, iIndex,
(Function<IdxPref, IdPref<I>> & Serializable) p -> new IdPref<>(iIndex.iidx2item(p)),
(Function<IdxPref, IdPref<U>> & Serializable) p -> new IdPref<>(uIndex.uidx2user(p)));
}
/**
* Loads a SimpleFastPreferenceData from a stream of user-item-value-other tuples. It can accomodate other information, thus you need to provide sub-classes of IdxPref IdPref accomodating for this new information.
*
* @param <U> user type
* @param <I> item type
* @param <O> additional information type
* @param tuples stream of user-item-value-other tuples
* @param uIdxPrefFun converts a tuple to a user IdxPref
* @param iIdxPrefFun converts a tuple to a item IdxPref
* @param uIndex user index
* @param iIndex item index
* @param uIdPrefFun user IdxPref to IdPref converter
* @param iIdPrefFun item IdxPref to IdPref converter
* @return an instance of SimpleFastPreferenceData containing the data from the input stream
*/
public static <U, I, O> SimpleFastPreferenceData<U, I> load(Stream<Tuple4<U, I, Double, O>> tuples,
Function4<Integer, Integer, Double, O, ? extends IdxPref> uIdxPrefFun,
Function4<Integer, Integer, Double, O, ? extends IdxPref> iIdxPrefFun,
FastUserIndex<U> uIndex, FastItemIndex<I> iIndex,
Function<IdxPref, IdPref<I>> uIdPrefFun,
Function<IdxPref, IdPref<U>> iIdPrefFun) {
AtomicInteger numPreferences = new AtomicInteger();
List<List<IdxPref>> uidxList = new ArrayList<>();
for (int uidx = 0; uidx < uIndex.numUsers(); uidx++) {
uidxList.add(null);
}
List<List<IdxPref>> iidxList = new ArrayList<>();
for (int iidx = 0; iidx < iIndex.numItems(); iidx++) {
iidxList.add(null);
}
tuples.forEach(t -> {
int uidx = uIndex.user2uidx(t.v1);
int iidx = iIndex.item2iidx(t.v2);
numPreferences.incrementAndGet();
List<IdxPref> uList = uidxList.get(uidx);
if (uList == null) {
uList = new ArrayList<>();
uidxList.set(uidx, uList);
}
uList.add(uIdxPrefFun.apply(uidx, iidx, t.v3, t.v4));
List<IdxPref> iList = iidxList.get(iidx);
if (iList == null) {
iList = new ArrayList<>();
iidxList.set(iidx, iList);
}
iList.add(iIdxPrefFun.apply(uidx, iidx, t.v3, t.v4));
});
return new SimpleFastPreferenceData<>(numPreferences.intValue(), uidxList, iidxList, uIndex, iIndex, uIdPrefFun, iIdPrefFun);
}
}