com.hichinaschool.flashcards.libanki.Stats.java Source code

Java tutorial

Introduction

Here is the source code for com.hichinaschool.flashcards.libanki.Stats.java

Source

/****************************************************************************************
 * Copyright (c) 2011 Norbert Nagold <norbert.nagold@gmail.com>                         *
 *                                                                                      *
 * 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 (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, see <http://www.gnu.org/licenses/>.                           *
 ****************************************************************************************/

package com.hichinaschool.flashcards.libanki;

import java.util.ArrayList;

import org.json.JSONException;
import org.json.JSONObject;

import com.hichinaschool.flashcards.anki.AnkiDroidApp;
import com.hichinaschool.flashcards.anki.R;

import android.database.Cursor;

/**
 * Deck statistics.
 */
public class Stats {

    public static final int TYPE_MONTH = 0;
    public static final int TYPE_YEAR = 1;
    public static final int TYPE_LIFE = 2;

    public static final int TYPE_FORECAST = 0;
    public static final int TYPE_REVIEW_COUNT = 1;
    public static final int TYPE_REVIEW_TIME = 2;
    // public static final int TYPE_INTERVALS = 2;
    // public static final int TYPE_REVIEWS = 3;
    // public static final int TYPE_REVIEWING_TIME = 4;
    // public static final int TYPE_DECK_SUMMARY = 5;

    private static Stats sCurrentInstance;

    private Collection mCol;
    private boolean mWholeCollection;
    private double[][] mSeriesList;

    private int mType;
    private int mTitle;
    private boolean mBackwards;
    private int[] mValueLabels;
    private int[] mColors;
    private int[] mAxisTitles;

    public Stats(Collection col, boolean wholeCollection) {
        mCol = col;
        mWholeCollection = wholeCollection;
        sCurrentInstance = this;
    }

    public static Stats currentStats() {
        return sCurrentInstance;
    }

    public double[][] getSeriesList() {
        return mSeriesList;
    }

    public Object[] getMetaInfo() {
        String title;
        if (mWholeCollection) {
            title = AnkiDroidApp.getInstance().getResources().getString(R.string.card_browser_all_decks);
        } else {
            try {
                title = mCol.getDecks().current().getString("name");
            } catch (JSONException e) {
                throw new RuntimeException(e);
            }
        }
        return new Object[] { mType, mTitle, mBackwards, mValueLabels, mColors, mAxisTitles, title };
    }

    /**
     * Due and cumulative due
     * ***********************************************************************************************
     */
    public boolean calculateDue(int type) {
        mType = type;
        mBackwards = false;
        mTitle = R.string.stats_forecast;
        mValueLabels = new int[] { R.string.statistics_young, R.string.statistics_mature };
        mColors = new int[] { R.color.stats_young, R.color.stats_mature };
        mAxisTitles = new int[] { type, R.string.stats_cards };
        int end = 0;
        int chunk = 0;
        switch (type) {
        case TYPE_MONTH:
            end = 31;
            chunk = 1;
            break;
        case TYPE_YEAR:
            end = 52;
            chunk = 7;
            break;
        case TYPE_LIFE:
            end = -1;
            chunk = 30;
            break;
        }
        String lim = "";// AND due - " + mCol.getSched().getToday() + " >= " + start; // leave this out in order to show
                        // card too which were due the days before
        if (end != -1) {
            lim += " AND day <= " + end;
        }

        ArrayList<int[]> dues = new ArrayList<int[]>();
        Cursor cur = null;
        try {
            cur = mCol.getDb().getDatabase()
                    .rawQuery("SELECT (due - " + mCol.getSched().getToday() + ")/" + chunk + " AS day, " // day
                            + "count(), " // all cards
                            + "sum(CASE WHEN ivl >= 21 THEN 1 ELSE 0 END) " // mature cards
                            + "FROM cards WHERE did IN " + _limit() + " AND queue IN (2,3)" + lim
                            + " GROUP BY day ORDER BY day", null);
            while (cur.moveToNext()) {
                dues.add(new int[] { cur.getInt(0), cur.getInt(1), cur.getInt(2) });
            }
        } finally {
            if (cur != null && !cur.isClosed()) {
                cur.close();
            }
        }
        // small adjustment for a proper chartbuilding with achartengine
        if (dues.size() == 0 || dues.get(0)[0] > 0) {
            dues.add(0, new int[] { 0, 0, 0 });
        }
        if (end == -1 && dues.size() < 2) {
            end = 31;
        }
        if (type != TYPE_LIFE && dues.get(dues.size() - 1)[0] < end) {
            dues.add(new int[] { end, 0, 0 });
        } else if (type == TYPE_LIFE && dues.size() < 2) {
            dues.add(new int[] { Math.max(12, dues.get(dues.size() - 1)[0] + 1), 0, 0 });
        }

        mSeriesList = new double[3][dues.size()];
        for (int i = 0; i < dues.size(); i++) {
            int[] data = dues.get(i);
            mSeriesList[0][i] = data[0];
            mSeriesList[1][i] = data[1];
            mSeriesList[2][i] = data[2];
        }
        return dues.size() > 0;
    }

    /* only needed for studyoptions small chart */
    public static double[][] getSmallDueStats(Collection col) {
        ArrayList<int[]> dues = new ArrayList<int[]>();
        Cursor cur = null;
        try {
            cur = col.getDb().getDatabase().rawQuery("SELECT (due - " + col.getSched().getToday() + ") AS day, " // day
                    + "count(), " // all cards
                    + "sum(CASE WHEN ivl >= 21 THEN 1 ELSE 0 END) " // mature cards
                    + "FROM cards WHERE did IN " + col.getSched()._deckLimit()
                    + " AND queue IN (2,3) AND day <= 7 GROUP BY day ORDER BY day", null);
            while (cur.moveToNext()) {
                dues.add(new int[] { cur.getInt(0), cur.getInt(1), cur.getInt(2) });
            }
        } finally {
            if (cur != null && !cur.isClosed()) {
                cur.close();
            }
        }
        // small adjustment for a proper chartbuilding with achartengine
        if (dues.size() == 0 || dues.get(0)[0] > 0) {
            dues.add(0, new int[] { 0, 0, 0 });
        }
        if (dues.get(dues.size() - 1)[0] < 7) {
            dues.add(new int[] { 7, 0, 0 });
        }
        double[][] serieslist = new double[3][dues.size()];
        for (int i = 0; i < dues.size(); i++) {
            int[] data = dues.get(i);
            serieslist[0][i] = data[0];
            serieslist[1][i] = data[1];
            serieslist[2][i] = data[2];
        }
        return serieslist;
    }

    /**
     * Reps and time spent
     * ***********************************************************************************************
     */

    public boolean calculateDone(int type, boolean reps) {
        mType = type;
        mBackwards = true;
        if (reps) {
            mTitle = R.string.stats_review_count;
            mAxisTitles = new int[] { type, R.string.stats_answers };
        } else {
            mTitle = R.string.stats_review_time;
        }
        mValueLabels = new int[] { R.string.statistics_learn, R.string.statistics_relearn,
                R.string.statistics_young, R.string.statistics_mature, R.string.statistics_cram };
        mColors = new int[] { R.color.stats_learn, R.color.stats_relearn, R.color.stats_young, R.color.stats_mature,
                R.color.stats_cram };
        int num = 0;
        int chunk = 0;
        switch (type) {
        case TYPE_MONTH:
            num = 31;
            chunk = 1;
            break;
        case TYPE_YEAR:
            num = 52;
            chunk = 7;
            break;
        case TYPE_LIFE:
            num = -1;
            chunk = 30;
            break;
        }
        ArrayList<String> lims = new ArrayList<String>();
        if (num != -1) {
            lims.add("id > " + ((mCol.getSched().getDayCutoff() - ((num + 1) * chunk * 86400)) * 1000));
        }
        String lim = _revlogLimit().replaceAll("[\\[\\]]", "");
        if (lim.length() > 0) {
            lims.add(lim);
        }
        if (lims.size() > 0) {
            lim = "WHERE ";
            while (lims.size() > 1) {
                lim += lims.remove(0) + " AND ";
            }
            lim += lims.remove(0);
        } else {
            lim = "";
        }
        String ti;
        String tf;
        if (!reps) {
            ti = "time/1000";
            if (mType == 0) {
                tf = "/60.0"; // minutes
                mAxisTitles = new int[] { type, R.string.stats_minutes };
            } else {
                tf = "/3600.0"; // hours
                mAxisTitles = new int[] { type, R.string.stats_hours };
            }
        } else {
            ti = "1";
            tf = "";
        }
        ArrayList<double[]> list = new ArrayList<double[]>();
        Cursor cur = null;
        try {
            cur = mCol.getDb().getDatabase()
                    .rawQuery("SELECT (cast((id/1000 - " + mCol.getSched().getDayCutoff() + ") / 86400.0 AS INT))/"
                            + chunk + " AS day, " + "sum(CASE WHEN type = 0 THEN " + ti + " ELSE 0 END)" + tf + ", " // lrn
                            + "sum(CASE WHEN type = 1 AND lastIvl < 21 THEN " + ti + " ELSE 0 END)" + tf + ", " // yng
                            + "sum(CASE WHEN type = 1 AND lastIvl >= 21 THEN " + ti + " ELSE 0 END)" + tf + ", " // mtr
                            + "sum(CASE WHEN type = 2 THEN " + ti + " ELSE 0 END)" + tf + ", " // lapse
                            + "sum(CASE WHEN type = 3 THEN " + ti + " ELSE 0 END)" + tf // cram
                            + " FROM revlog " + lim + " GROUP BY day ORDER BY day", null);
            while (cur.moveToNext()) {
                list.add(new double[] { cur.getDouble(0), cur.getDouble(1), cur.getDouble(4), cur.getDouble(2),
                        cur.getDouble(3), cur.getDouble(5) });
            }
        } finally {
            if (cur != null && !cur.isClosed()) {
                cur.close();
            }
        }

        // small adjustment for a proper chartbuilding with achartengine
        if (type != TYPE_LIFE && (list.size() == 0 || list.get(0)[0] > -num)) {
            list.add(0, new double[] { -num, 0, 0, 0, 0, 0 });
        } else if (type == TYPE_LIFE && list.size() == 0) {
            list.add(0, new double[] { -12, 0, 0, 0, 0, 0 });
        }
        if (list.get(list.size() - 1)[0] < 0) {
            list.add(new double[] { 0, 0, 0, 0, 0, 0 });
        }

        mSeriesList = new double[6][list.size()];
        for (int i = 0; i < list.size(); i++) {
            double[] data = list.get(i);
            mSeriesList[0][i] = data[0]; // day
            mSeriesList[1][i] = data[1] + data[2] + data[3] + data[4] + data[5]; // lrn
            mSeriesList[2][i] = data[2] + data[3] + data[4] + data[5]; // relearn
            mSeriesList[3][i] = data[3] + data[4] + data[5]; // young
            mSeriesList[4][i] = data[4] + data[5]; // mature
            mSeriesList[5][i] = data[5]; // cram
        }
        return list.size() > 0;
    }

    /**
     * Intervals ***********************************************************************************************
     */

    // public int[][] intervals(int limit, int chunk) {
    // ArrayList<int[]> ivls = new ArrayList<int[]>();
    // Cursor cur = null;
    // try {
    // cur = mDeck.getDB().getDatabase().rawQuery(
    // "SELECT ivl / " + chunk + " AS grp, count() FROM cards " + "WHERE queue = 2 " + _limit()
    // + " AND grp <= " + limit + " GROUP BY grp ORDER BY grp", null);
    // while (cur.moveToNext()) {
    // ivls.add(new int[] { cur.getInt(0), cur.getInt(1) });
    // }
    // } finally {
    // if (cur != null && !cur.isClosed()) {
    // cur.close();
    // }
    // }
    // return (int[][]) ivls.toArray(new int[ivls.size()][ivls.get(0).length]);
    // }
    //
    //
    /**
     * Tools ***********************************************************************************************
     */

    private String _limit() {
        if (mWholeCollection) {
            ArrayList<Long> ids = new ArrayList<Long>();
            for (JSONObject d : mCol.getDecks().all()) {
                try {
                    ids.add(d.getLong("id"));
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
            }
            return Utils.ids2str(Utils.arrayList2array(ids));
        } else {
            return mCol.getSched()._deckLimit();
        }
    }

    private String _revlogLimit() {
        if (mWholeCollection) {
            return "";
        } else {
            return "cid IN (SELECT id FROM cards WHERE did IN " + Utils.ids2str(mCol.getDecks().active()) + ")";
        }
    }
}