org.cook_e.data.TimeLearner.java Source code

Java tutorial

Introduction

Here is the source code for org.cook_e.data.TimeLearner.java

Source

/*
 * Copyright 2016 the Cook-E development team
 *
 *  This file is part of Cook-E.
 *
 *  Cook-E is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Cook-E 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 Cook-E.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.cook_e.data;

import android.support.annotation.NonNull;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.joda.time.Duration;

/**
 * According to the step and actual time given
 * this class will perform operations to give more accurate estimate time for one user
 */
public class TimeLearner implements TimeLearnerInterface {
    // Maximum multiple of estimated time learner can change each time
    private static final double LEARNING_LIMIT = 2.0;

    // The rate that learn rate decays for each learn
    private static final double LEARN_RATE_DECAY_RATE = 0.75;

    // Sorted list of learning weights. List is sorted by hash code
    @NonNull
    private Map<Long, List<LearningWeight>> mWeights;

    private StorageAccessor mStorageAccessor;

    /**
     * The constructor for TimeLearner
     *
     * @param sA the StorageAccessor for this TimeLearner
     * @param b the Bunch users want to cook
     * @throws SQLException
     */
    public TimeLearner(StorageAccessor sA, Bunch b) throws SQLException {
        mStorageAccessor = sA;
        mWeights = mStorageAccessor.loadLearnerData(b);
    }

    /**
     * Learns the actual time of a step.
     *
     * @param r the recipe you want to record the time for
     * @param time the actual time user took to finish this step (in milliseconds)
     * @throws IllegalArgumentException when actual time is negative
     */
    @Override
    public void learnStep(@NonNull Recipe r, Step s, @NonNull Duration time)
            throws IllegalArgumentException, SQLException {
        Objects.requireNonNull(r, "recipe must not be null");
        Objects.requireNonNull(time, "time must not be null");
        long actualTime = time.getMillis();
        if (actualTime < 0)
            throw new IllegalArgumentException("time must not be negative");

        LearningWeight lw = accessOrCreateLearningWeight(r, s);

        // calculate new weight
        long oldEstimatedTime = (long) (s.getTime().getMillis() * lw.getTimeWeight());
        double weightChange;
        if (actualTime >= oldEstimatedTime * LEARNING_LIMIT)
            weightChange = LEARNING_LIMIT - 1;
        else if (actualTime * LEARNING_LIMIT <= oldEstimatedTime)
            weightChange = (1 / LEARNING_LIMIT) - 1;
        else {
            weightChange = (actualTime * 1.0 / oldEstimatedTime) - 1;
        }

        lw.setTimeWeight(lw.getTimeWeight() + lw.getTimeWeight() * weightChange * lw.getLearnRate());
        lw.setLearnRate(lw.getLearnRate() * LEARN_RATE_DECAY_RATE);
        mStorageAccessor.updateLearnerData(r, lw);
    }

    /**
     * Helper that finds a learning weight associated with the recipe and step or creates it if it does not exist
     *
     * @param r the recipe you want to record the time for
     * @param s the particular step you want to record the time for
     * @return the LearningWeight of this step
     */
    private LearningWeight accessOrCreateLearningWeight(Recipe r, Step s) {
        List<LearningWeight> weights = mWeights.get(r.getObjectId());

        // mWeights does not contain the learningWeight for this recipe does not exist, so create one
        if (weights == null || weights.size() == 0) {
            int stepCount = r.getSteps().size();
            weights = new ArrayList<>(stepCount);
            for (int i = 0; i < stepCount; i++) {
                weights.add(new LearningWeight(i));
            }
            mWeights.put(r.getObjectId(), weights);
        }

        LearningWeight lw = weights.get(s.getIndex());

        // The learningWeight for this step does not exits
        if (lw == null) {
            lw = new LearningWeight(s.getIndex());
            weights.add(s.getIndex(), lw);
        }
        return lw;
    }

    /**
     * Returns the estimated time for a step based on learning result.
     * If step is not learned before, returns the estimate time of that step.
     *
     * @param s the step you need to estimate the time for
     * @return the estimated time (in milliseconds) for that specific step
     */
    @Override
    @NonNull
    public Duration getEstimatedTime(@NonNull Recipe r, @NonNull Step s) {
        Objects.requireNonNull(r, "recipe must not be null");
        Objects.requireNonNull(s, "step must not be null");
        LearningWeight lw = accessOrCreateLearningWeight(r, s);
        long time = (long) (s.getTime().getMillis() * lw.getTimeWeight());
        return Duration.millis(time);
    }
}