Monday, July 18, 2011

SRS - implementation part 2

Ok, yesterday, we implemented the *input* part of our SRS algorithm, and it seemed to test out ok. Today, we do the output part - that is:

1. For correct answers
a. calculated the time since last the last review,
b. Calculate a new review date
c. create or update a row in the data base with the new scheduled date
c. If there was never an answer set the new scheduled date according to a fixed schedule

2. For incorrect answers:
update or write the scheduled record for the same day, meaning it must be reviewed again before the level is advanced.

Let's look in the old blog entry for how I had come up with a rough schedule of how to set the next date.


Before I do that, I'm going to get rid of level and number from the schedule table. They are joining on _id, and that makes them superfluous.

Ok, I have to drop the table and recreate it to drop the columns. I'm also going to drop the rating. It's superfluous.

CREATE TABLE word_review_schedule
("_id" INTEGER,
last_review_date TEXT(25),
next_review_date TEXT(25))

Drop then:

CREATE TABLE word_review_schedule("_id" INTEGER, last_review_date TEXT(25), next_review_date TEXT(25))


Drop the view then; then create an index on the date.

Now, re-add a row:


insert into word_review_schedule (_id, last_review_date,next_review_date) values (1098, "2001-01-01", "2011-07-19");

Test it's showing up when selected by next date parameter:

select * from v_words_schedule_join where next_review_date > 1

Good! Ok, let's test it.

Copy jlpt to the eclipse project.

And get rid of the database on the device.

Btw, what a great idea it was to root the phone. I can get at whatever I want now.

Ok, now let's go ahead an run it.

Ok. It's hard to tell, but I think the index picked up the pace a little bit.

A couple of quick adjustments to the timing on the question display and get rid of some of the debug statements....

Ok. So, we're now at the point where I'm ready to get going on the srs row creation. Waht was that url again?

Here's where the enum is:

http://gettingintomobile.blogspot.com/2011/06/srs-coding.html


public enum Rating {
WRONG, SLOW, MEDIUM, FAST
};


And the values I was going to assign?

http://gettingintomobile.blogspot.com/2011/06/spaced-repetition-learning-systems-srs.html

There, I thought:

Let's say they get it right in under 2 seconds. That's "by heart". That will have the longest repeat, and will be scheduled in say, seven days.

The next one is between 2 and five. That's we'll say, repeat in 2 days.

If it's 5-10, that's one day.

Ok, let's just say we have Wrong, Slow, and Fast. That's simpler.

Fast is 5 seconds or under. Slow is anything over that.

Now, what were the formulae?

If it's wrong, you reschedule for today. That's easy.

Ah here it is:

Good answer = 2 * (today - last scheduled date)

So, if the difference is 5 days, it will become 10.

If it's 30 days, it will become 60.


A medium answer, over 5 seconds, will be 3 days initially, and times 1.5 the review interval.

So if review interval was 6 days, you'll see it in 1.5 * 5 = 7 days. The again 7 * 1.5 = 10.5, then if it's 10, 1.5 * 10 = 15.

Ok. I'll take it. I can always fine tune it later.

Ok, let's get to coding this. Let's make it a separate module, because Question Activity is bloated already. We'll call it HandleSrsUpdate, and just pass it a method with the parameters it's going to need. It's going to need the id, and the time timer count when the question was answered. Anything else?


In the code, it well determine which of the three intervals it falls into. For that, it will access the row from the schedule, using a new method in database helper. If it's not found,it will calculate the next review data based on the wrong, slow and fast "initial" values - 0, 3 and 5. it will get the difference between the last reviewed data and todays date, and do the multiplication. Or if it was wrong, just reassign today's date.

Ok, I have no more excuses. To the code, gentlemen!

Ok, here's what I have so far:




public class UpdateSRSUtil {

enum Rating {
WRONG, SLOW, FAST
}

Rating rating;

/***********************************************************************************
*
*
*/

public void updateSRSschedule(int _id, int secRemaining) {

// calculate the difference
if (0 == secRemaining ) {
rating = Rating.WRONG;
} else if (secRemaining > 5 ){
rating = Rating.SLOW;
}
else {
rating = Rating.FAST;
}

// retrieve data

}

}


Ok, the retrive should be easy. I can model on the retrive of the word row selection. Wait, I need the id. Actually, a lot of the information will, or can be already int the Question. No, I have them in a separate object, wordSchedule. Hmmm. I wonder of I could just make it an attribute of the question? Actually, although I had previously coded a select of word, I'm not using it because of the join read, and just added next review date to question. All I need to do is add id and last review date to question, and then I can just use that.

Ok, I've added them to Question. Now, I just have to add Database helper select statement.

Wait, wait wait wait wait. There are a couple of other places where I'm selecting question, where I don't need next review date. But, I was already thinking of unifying on the select from the view.

I think my best bet would be not to make any assumptions about whether effective date is set or not. And I'm not sure I should be accessing questions through the scheduled table join if I don't need to. All I really need is the ID, then I can just grab the info from the database.

I'll compromise. I'll add the id, leave the next review date, and leave out last review date. This means no one will have any illusions that Question has the last_reviewed date. But I will be able to use id, because I've just changes the tow other question access routines to add id to question (and set the nextReviewDate to null).

That way, I don't have any entangling alliances with the schedule join. I can just get the last reviewed date from the database when I need it. It's slightly less efficient, but it reduces complexity.

Ok, now I can get back to coding the select statement for the schedule. And I actually have one out there, which I can just change to select on id instead of level / number. Wait I can select on level number. Should I? It would have to be on the join table/view - which really isn't a problem. But I'll just go with id.

Ok, here is the database select:


public WordSchedule selectScheduleById(int id) {


Cursor cursor = this.myDataBase.query(SCHEDULE_TABLE_NAME,
new String[] { "_id", "last_review_date",
"next_review_date" }, "_id = ?",
new String[] { Integer.toString(id) }, null, null, null);
ArrayList rows = new ArrayList();

if (cursor.moveToFirst()) {
do {

int read_id = cursor.getInt(0);
String lastReviewDateStr = cursor.getString(1);
String nextReviewDateStr = cursor.getString(2);
Date lastReviewDate = Utils.stringToDate(lastReviewDateStr);
Date nextReviewDate = Utils.stringToDate(nextReviewDateStr);

WordSchedule wordSchedule = new WordSchedule(read_id, lastReviewDate, nextReviewDate);

rows.add(wordSchedule);
} while (cursor.moveToNext());
}

if (cursor != null && !cursor.isClosed()) {
cursor.close();
}

// close();

return rows.get(0);

}



And to access it:

DataBaseHelper dbHelper = DataBaseHelper.createDB(context);

WordSchedule ws = dbHelper.selectScheduleById(id);

dbHelper.close();

Ok. Let's see if what we have so far is working. Let's add a call to this from QuestionActivity and print to the logcat to make sure it's working.

Oky dokey. After a fair a amount of tweaking and adjusting, I've gotten to the point of accessing the correct row in the schedule. Yipee-yi-yay, cowboy.

On thing I did, which I should do more often, is give the code a looking over before running the test. I found that I'd forgotten to code the call to the method I was supposed to be testing, for one thing. I also neatened up the code that I checked. Eyeballing the code and catching a bug there is *exponentially* better than running and debugging. Debugging takes time, and so often, I've traced it back to a careless mistake. Plus, it gets you more familiar with what your code is doing, as well. It makes you calmer, mor in control, less stressed. This is a skill I'm really going to cultivate.

Ok, this post is getting kind of long. I'm going to break it off here, and resume the final part - creating the output and seeing if it works - in my next post.

No comments:

Post a Comment