/**
* File ./src/main/java/de/lemo/dms/processing/questions/QFrequentPathsApriori.java
* Lemo-Data-Management-Server for learning analytics.
* Copyright (C) 2015
* Leonard Kappe, Andreas Pursian, Sebastian Schwarzrock, Boris Wenzlaff
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* 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, see <http://www.gnu.org/licenses/>.
**/
/**
* File ./main/java/de/lemo/dms/processing/questions/QFrequentPathsApriori.java
* Date 2013-01-24
* Project Lemo Learning Analytics
*/
package de.lemo.dms.processing.questions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import de.lemo.dms.core.config.ServerConfiguration;
import de.lemo.dms.db.IDBHandler;
import de.lemo.dms.db.mapping.abstractions.ILearningObject;
import de.lemo.dms.db.mapping.abstractions.ILog;
import de.lemo.dms.processing.FrequentPath;
import de.lemo.dms.processing.MetaParam;
import de.lemo.dms.processing.Question;
import de.lemo.dms.processing.StudentHelper;
import de.lemo.dms.processing.resulttype.ResultListUserPathGraph;
import de.lemo.dms.processing.resulttype.UserPathLink;
import de.lemo.dms.processing.resulttype.UserPathNode;
import de.lemo.dms.processing.resulttype.UserPathObject;
/**
* Reads the path data from the database and using a algorithm to find frequent paths
*
* @author Sebastian Schwarzrock
*/
@Path("frequentPathsApriori")
public class QFrequentPathsApriori extends Question {
private Map<Integer, ILog> idToLogM = new HashMap<Integer, ILog>();
private Map<Integer, List<Long>> requests = new HashMap<Integer, List<Long>>();
private Map<Integer, Integer> idToInternalId = new HashMap<Integer, Integer>();
private Map<Integer, Integer> internalIdToId = new HashMap<Integer, Integer>();
private ArrayList<Integer> objInd = new ArrayList<Integer>();
private int userCount = 0;
@POST
public ResultListUserPathGraph compute(
@FormParam(MetaParam.COURSE_IDS) final List<Long> courses,
@FormParam(MetaParam.USER_IDS) final List<Long> users,
@FormParam(MetaParam.TYPES) final List<String> types,
@FormParam(MetaParam.MIN_LENGTH) final Long minLength,
@FormParam(MetaParam.MAX_LENGTH) final Long maxLength,
@FormParam(MetaParam.MIN_SUP) final Double minSup,
@FormParam(MetaParam.START_TIME) final Long startTime,
@FormParam(MetaParam.END_TIME) final Long endTime,
@FormParam(MetaParam.GENDER) final List<Long> gender,
@FormParam(MetaParam.LEARNING_OBJ_IDS) final List<Long> learningObjects) {
validateTimestamps(startTime, endTime);
final ArrayList<UserPathNode> nodes = Lists.newArrayList();
final ArrayList<UserPathLink> links = Lists.newArrayList();
logger.info(startTime + " " + endTime);
if (logger.isDebugEnabled()) {
if ((courses != null) && (courses.size() > 0))
{
StringBuffer buffer = new StringBuffer();
buffer.append("Parameter list: Courses: " + courses.get(0));
for (int i = 1; i < courses.size(); i++) {
buffer.append(", " + courses.get(i));
}
logger.debug(buffer.toString());
}
if ((users != null) && (users.size() > 0))
{
StringBuffer buffer = new StringBuffer();
buffer.append("Parameter list: Users: " + users.get(0));
for (int i = 1; i < users.size(); i++) {
buffer.append(", " + users.get(i));
}
logger.debug(buffer.toString());
}
if ((types != null) && (types.size() > 0))
{
StringBuffer buffer = new StringBuffer();
buffer.append("Parameter list: Types: : " + types.get(0));
for (int i = 1; i < types.size(); i++) {
buffer.append(", " + types.get(i));
}
logger.debug(buffer.toString());
}
if ((minLength != null) && (maxLength != null) && (minLength < maxLength))
{
logger.debug("Parameter list: Minimum path length: : " + minLength);
logger.debug("Parameter list: Maximum path length: : " + maxLength);
}
logger.debug("Parameter list: Minimum Support: : " + minSup);
logger.debug("Parameter list: Start time: : " + startTime);
logger.debug("Parameter list: End time: : " + endTime);
}
final IDBHandler dbHandler = ServerConfiguration.getInstance().getMiningDbHandler();
final Session session = dbHandler.getMiningSession();
List<List<Integer>> list = generateList(courses, users, types, minLength, maxLength, startTime, endTime,
session, gender, learningObjects);
List<List<List<Integer>>> patterns = FrequentPath.apriori(list, minSup.doubleValue());
final LinkedHashMap<String, UserPathObject> pathObjects = Maps.newLinkedHashMap();
Long pathId = 0L;
for(int i = patterns.size()-1; i >=0 ; i-- )
{
logger.info("Found " + patterns.get(i).size() + " paths of length " + patterns.get(i).get(0).size());
for(int j = 0; j < patterns.get(i).size(); j++)
{
String predecessor = null;
Long absSup = (long) (minSup * userCount) + 1;
pathId++;
for(int k = 0; k < patterns.get(i).get(j).size(); k++)
{
final String posId = String.valueOf(pathObjects.size());
int obj = patterns.get(i).get(j).get(k);
final ILog ilo = this.idToLogM.get(objInd.get(obj));
if (predecessor != null)
{
pathObjects.put(
posId,
new UserPathObject(posId, ilo.getLearning().getTitle(), absSup, ilo.getClass().getSimpleName(),
0d, ilo.getPrefix(), pathId,
Long.valueOf(this.requests.get(obj).size()), Long
.valueOf(new HashSet<Long>(this.requests.get(obj))
.size())));
// Increment or create predecessor edge
pathObjects.get(predecessor).addEdgeOrIncrement(posId);
}
else
{
pathObjects.put(
posId,
new UserPathObject(posId, ilo.getLearning().getTitle(), absSup,
ilo.getClass().getSimpleName(), 0d, ilo.getPrefix(), pathId, Long
.valueOf(this.requests.get(obj).size()), Long
.valueOf(new HashSet<Long>(this.requests.get(obj))
.size())));
}
predecessor = posId;
}
}
}
for (final UserPathObject pathEntry : pathObjects.values()) {
final UserPathObject path = pathEntry;
path.setWeight(path.getWeight());
path.setPathId(pathEntry.getPathId());
nodes.add(new UserPathNode(path, true));
final String sourcePos = path.getId();
for (final Entry<String, Integer> linkEntry : pathEntry.getEdges().entrySet()) {
final UserPathLink link = new UserPathLink();
link.setSource(sourcePos);
link.setPathId(path.getPathId());
link.setTarget(linkEntry.getKey());
link.setValue(String.valueOf(linkEntry.getValue()));
links.add(link);
}
}
this.requests.clear();
this.idToLogM.clear();
this.internalIdToId.clear();
this.idToInternalId.clear();
logger.info("Found " + pathId + " paths.");
session.close();
return new ResultListUserPathGraph(nodes, links);
}
/**
* Generates the necessary list containing the sequences (user paths) for the algorithm
*
* @param courses
* Course-Ids
* @param users
* User-Ids
* @param starttime
* Start time
* @param endtime
* End time
* @return The path to the generated file
*/
@SuppressWarnings("unchecked")
public List<List<Integer>> generateList(final List<Long> courses, List<Long> users,
final List<String> types, final Long minLength, final Long maxLength, final Long starttime,
final Long endtime, Session session, List<Long> gender, List<Long> learningObjects){
final List<List<Integer>> result = new ArrayList<List<Integer>>();
final boolean hasBorders = (minLength != null) && (maxLength != null) && (maxLength > 0)
&& (minLength < maxLength);
final boolean hasTypes = (types != null) && (types.size() > 0);
Map<Long, ILearningObject> objects = new HashMap<Long, ILearningObject>();
Criteria criteria;
if(users == null || users.size() == 0)
{
users = new ArrayList<Long>(StudentHelper.getCourseStudentsAliasKeys(courses, gender).values());
}
else
{
Map<Long, Long> userMap = StudentHelper.getCourseStudentsAliasKeys(courses, gender);
List<Long> tmp = new ArrayList<Long>();
for(int i = 0; i < users.size(); i++)
{
tmp.add(userMap.get(users.get(i)));
}
users = tmp;
}
criteria = session.createCriteria(ILog.class, "log");
if (courses.size() > 0) {
criteria.add(Restrictions.in("log.course.id", courses));
}
if (users.size() > 0) {
criteria.add(Restrictions.in("log.user.id", users));
}
if(!learningObjects.isEmpty())
{
criteria.add(Restrictions.in("log.learning.id", learningObjects));
}
criteria.add(Restrictions.between("log.timestamp", starttime, endtime));
criteria.addOrder(Property.forName("log.timestamp").asc());
final ArrayList<ILog> list = (ArrayList<ILog>) criteria.list();
Map<Long, List<Integer>> userPaths = new HashMap<Long, List<Integer>>();
logger.debug("Found " + list.size() + " logs.");
long id = -1;
List<Integer> path = new ArrayList<Integer>();
for(ILog log : list)
{
Integer pos = ((Long)log.getLearning().getId()).intValue();
if(!hasTypes || types.contains(log.getLearning().getLOType()))
{
if(!objInd.contains(pos))
{
objInd.add(pos);
}
if(idToLogM.get(pos) == null )
{
idToLogM.put(pos, log);
}
if (this.requests.get(objInd.indexOf(pos)) == null)
{
final ArrayList<Long> us = new ArrayList<Long>();
us.add(log.getUser().getId());
this.requests.put(objInd.indexOf(pos), us);
} else {
this.requests.get(objInd.indexOf(pos)).add(
log.getUser().getId());
}
if(userPaths.get(log.getUser().getId()) == null)
{
List<Integer> l = new ArrayList<Integer>();
l.add(objInd.indexOf(pos));
userPaths.put(log.getUser().getId(), l);
}
else
{
userPaths.get(log.getUser().getId()).add(objInd.indexOf(pos));
}
}
/*if(!hasTypes || types.contains(log.getLearning().getLOType()))
{
if(objects.get(log.getLearning()) == null)
objects.put(log.getLearning().getId(), log.getLearning());
if(id != log.getUser().getId())
{
id = log.getUser().getId();
if(!hasBorders || (path.size() < maxLength && path.size() > minLength))
{
List<Integer> userPath = new ArrayList<Integer>();
for(int i = 0; i < path.size(); i++)
{
userPath.add(path.get(i).intValue());
}
if(userPath.size() > 0)
result.add(userPath);
}
path = new ArrayList<Integer>();
}
if(!objInd.contains(pos))
{
objInd.add(pos);
}
if(idToLogM.get(pos) == null )
{
idToLogM.put(pos, log);
}
if (this.requests.get(objInd.indexOf(pos)) == null)
{
final ArrayList<Long> us = new ArrayList<Long>();
us.add(log.getUser().getId());
this.requests.put(objInd.indexOf(pos), us);
} else {
this.requests.get(objInd.indexOf(pos)).add(
log.getUser().getId());
}
path.add(objInd.indexOf(pos));
}
*/
}
for(List<Integer> uhis : userPaths.values())
{
result.add(uhis);
}
userCount = result.size();
logger.debug("Found " + result.size() + " user paths.");
return result;
}
}