Closed pwintz closed 1 year ago
Ah, you've hit on probably the second most important problem with Anki in my opinion, the first, of course, being the lack of any collaborative deck functionality at all.
New Card Sort Order
Controls how cards are sorted after they have been gathered. By default, Anki sorts by template first, to avoid multiple cards of the same note from being shown in succession. This results in cards appearing in the order they have been added, with the first card template (eg front->back) appearing before later card templates (eg back->front).
So by default, Anki sorts new cards by creation date, at a high-level. This is a problem for ki decks, as the creation date is not stored in the card format. It's actually a problem with Anki in general, in my opinion, since often many cards reside in a single deck that have dependencies, of a sort. It doesn't make sense to study a Grassmannian
card if you haven't already learned the cards for Hausdorff
or locally Euclidean of dimension n
(in my opinion). I refer to this in my head as the prerequisites problem.
Deck A < Deck B
implies that the cards in Deck A
are prerequisites for the cards in Deck B
. Then suspend accordingly. Perhaps the deck comes with most cards suspended a priori. Note that ki does not support storing this data yet either. It is an open question whether or not this should be in-scope, though the implementation is trivial.My opinion is that (2) is the only really viable option. Lots of people (myself included) study on mobile, so a separate addon that's essential for reviews is a no-go. Note that (1) is horribly impractical, and (3) is, in my opinion, just a less transparent version of (2).
Simple, we add a created: <date>
field to the lark
grammar, and then add the corresponding ops on the col: Collection
object to the update_note()
function, and friends. In practice this would take a little time, but it doesn't seem too hard to me.
The grammar modification would go here:
Instead of that line, we'd have:
header: (title NID MODEL tags MARKDOWN CREATED)
And the CREATED
terminal would probably look like this:
CREATED: "created:" " "* (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) "\n"
I've stolen the above date regex from here. An alternative is this:
(?=\d)(?:(?:31(?!.(?:0?[2469]|11))|(?:30|29)(?!.0?2)|29(?=.0?2.(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))(?:\x20|$))|(?:2[0-8]|1\d|0?[1-9]))([-./])(?:1[012]|0?[1-9])\1(?:1[6-9]|[2-9]\d)?\d\d(?:(?=\x20\d)\x20|$))?(((0?[1-9]|1[012])(:[0-5]\d){0,2}(\x20[AP]M))|([01]\d|2[0-3])(:[0-5]\d){1,2})?$
Lol.
I was thinking of something along the lines of item 4, where we can specify the prerequisites for each card. I don't think we would need an Anki add-on to make it work, though. If we can modify the creation time of each card, then we can set the order they are displayed, right? Manually setting the order would be a good start, but I think it would be awesome to have the order automatically generated based on a list of prerequisites given for each card.
Here is the workflow that I'm picturing:
I create a deck with Anki+ki. For each card, I have a "name" field and a "prerequisites" field. For example, we could have
## Note
nid: 1655372139959
model: Cloze
tags:
markdown: false
### Text
The set of {{c2::eigenvalues}} of a matrix is called its {{c1::specturm}}.
### Name
spectrum
### Prerequisites
eigenvalue
Then, have an option (perhaps in ki push
) to reorder the creation dates to ensure that cards are not displayed until after their prerequisites. This might be outside the scope of ki, but if we could get it to work, we'd have a powerful for developing decks to share!
After thinking about this a bit more, I think it will require an add-on, or something like an add-on. Note that new cards are sorted by creation date, but reviews are sorted by due date, which is a stochastic function of review history.
The above subtlety is something I had not realized when I wrote my first comment, so some of those solutions are not really viable.
Even if we could handle this with only creation date manipulation, my feeling is that this is out of the scope of the ki push
op. But I could be convinced otherwise!
I agree that building and suspending a myriad of decks for each 'level' in the dependency DAG could turn into a mess. But what are your thoughts on using an extra field vs tags to actually store the dependencies? It would be nice to be able to use this with stock Basic
notetypes, etc.
Overall, my favorite strategy right now is using tags to store dependencies, and using an add-on to manage review-order, or a separate tiny command line tool if it turns out that an add-on is not necessary.
@pwintz Still interested in chatting about this? I thought about it some more today, and I would like to investigate how Anki actually orders due
cards. Are they in the order than they became due, or are they reordered according to some other ordering as more due cards pile up?
Yeah, I'll be slow to respond but am still interested in this project!
When working with notes with dependencies, I only care about the order of new cards—once a card is studied the first time, we can let Anki's normal scheduling run its course.
Instead of modifying the creation date, what if we used the "forget" option to move cards to the end of the new card queue? What I am picturing is iterating through all of the new cards in a deck in the desired order (how we determine that is TDB) and forgetting them in the order they should be studied. So the first card forgotten will be the next new card studied and so on.
Would this work with the Anki API that Ki uses? If so, could it be done quickly for large decks?
So to answer your question very directly, yes this is possible, even easy (naively, perhaps it could be a pain and I don't see why). And it can be done quickly (iterating through every card is generally not too slow). The thing that slows ki down the most is media-related things, and we need not deal with those here.
I only care about the order of new cards—once a card is studied the first time, we can let Anki's normal scheduling run its course.
However, I must disagree with you on this point. I feel that it makes very little sense to be reviewing examples involving some definition if you need a refresher on the definition itself. I worry this could be difficult for the chief reason that messing with the scheduler is not portable (I would want this to work on mobile).
Edit. One idea I've come up with is a server that hosts a copy of your collection. It automatically buries notes with dependencies at the start of the day, and then maybe polls every 15 minutes or so to unbury notes as their dependencies are reviewed.
I hear what you are saying—it would be nice to have an intelligent scheduler that schedules reviews based on the ease of prerequisite cards. This feels like it would add a lot of complexity, however, without providing a lot of value.
In my experience, reviewing a prerequisite card once or twice is enough for me to feel comfortable learning a dependent card. In fact, learning them concurrently can help add context about why each is important. If, later on, I forget the details of the prerequisite card, then I'll usually remember the gist of it enough to still be able to review a dependent card. In the rare cases that I don't, then I'm OK rescheduling them manually.
What I'm much more concerned with—and what motivated this feature request—is preventing a new dependent card from being introduced a long time before its prerequisites. In particular, I would like to use Ki to design decks that I can share with other people (who aren't necessarily using Ki) and ensure that the cards are introduced in a sensible order. This only requires an upfront ordering of the cards (perhaps using "forget") and so would be much simpler and not require any add-ons or other witchcraft.
Yes, okay, you make a good point. New cards are the priority, hot-patching reviews will be considered only after I've built something to support ordering new cards.
However, this is out-of-scope for ki. Thus, current priorities are, in order:
PermissionError
s and symlinks).Would using "forget" work if our goal is to export a deck and share it that way? (Presumably with the "include scheduling information" check box ticked.)
I've been pondering how I would implement this. Initially, I strongly prefered using note fields instead of tags. As in my example above, one field would be "Name" and another would be "Prerequisites." The Prerequisites field would contain a list of line-separated or comma-separated names of other cards. After writing out some pros and cons, however, I'm on the fence regarding tags vs. fields.
chapter_2
and linear_map
. (We could, I suppose, allow comma-separated names in a "Name" field as well.)Edit: We could also use a mix of both! Have name and prerequisite fields but also allow for tag prerequisites (so all of the cards with a given tag must be introduced first, but the prerequisites are all still given in the field).
Whatever way we decide to encode the prerequisites, an algorithm of how to do this would be as follows:
Steps 1-5 should be pretty straightforward, especially if we use an existing graph theory package. Step 6 has several open questions:
I was looking at https://addon-docs.ankiweb.net/the-anki-module.html for information on using the anki
package, but that's very limited. Is there better documentation that you have been using?
Would using "forget" work if our goal is to export a deck and share it that way? (Presumably with the "include scheduling information" check box ticked.)
I think that it would. If a user is studying a ki-generated deck, they got it in one of three ways. Either (1) they cloned their collection and then used git submodule add
on some existing repository, (2) the maintainer uploaded the .apkg
binary to AnkiWeb, or (3), they got the .apkg
file some other way (maybe a GitHub releases page).
In every case, we can use either "forget" or creation date modification. Ki will have to be able to set these things in the database, but that is trivial, so with this information in the note grammar, (1) is taken care-of. In the other cases, as you mentioned, you can embed as much info as you want in a .apkg
file (even scheduling data if needed). So that takes care of (2) and (3).
Advantages to using Fields / Downsides to using Tags
* a unique "name" tag for each note will clutter the list of tags. * tags are limited to a single word whereas a field is not. * A Name field makes for a nice sort field in the card browser. * Putting the name and prerequisites in fields will make them easier to scan in the browser instead of needing to open each and look through the list of tags (which might be in who-knows-what-order).
I feel that on many big decks, the tag list is already cluttered. I imagine tags of the form prereq=spectral-triple
would (1) make it obvious these are not normal tags, and (2) allow multi-word titles/names. So I don't feel tags are really limited to a single word in practice. ~They allow a variety of delimiters, even spaces, I think.~ Tags cannot contains spaces.
A name field does make for a nice sort field, I will admit.
I disagree about the browser ease-of-scanning. You can only see the sort field in the browser, so if prerequisites
is a separate field, the user can't see it at all. You can see tags in the browser. And you can also filter on tag: prereq=spectral-triple
. That seems pretty nice. If we generate a two-way mapping, then you could see all the cards that depend on learning a given card as well, but that would definitely clutter the tags.
Downsides to using Fields / Advantages to using Tags
* If you want to use fields named "Name" or "Prerequisite" for some other reason then you cannot. * Notes must use a card type explicitly designed to work with the prerequisite sorting tool. * How do you handle a card that doesn't have a suitable name to put in the name field? * If we use tags, can we give a card two "names"? (In this case, perhaps "name" is no longer the right name.) An example would be if we want a name tag for `chapter_2` and `linear_map`. (We could, I suppose, allow comma-separated names in a "Name" field as well.) * Can you rename tags and have the name automatically updated on every card?
I feel as though picking something other than Name
would probably avoid the first issue above.
The notetype requirement is a killer, though. I think this makes the barrier-to-entry much too high. What if you want to use a special notetype like cloze-overlapper
or image-occlusion
? If these add-ons have the notetype name or id hardcoded, suddenly this prerequisites tool is completely incompatible. I feel this is an untenable requirement.
How do you generate suitable names? Well this is exactly what get_note_path()
does. You either require that users input one (analogous to your Name
field), or make something up when you run into issues. See my latest comments in #87.
If you're talking about prerequisite tags like prereq=chapter_2
, that sounds like you're adding groups of cards now. I would say keep the initial implementation fully granular. If you want to require a whole group, add each prereq individually.
Renaming is tough. We could incorporate the guid
into the data of the tag somehow, or make this out-of-scope. The user could of course use something like ki to do this batch edit (that is what ki is for). Open to ideas here.
Edit: We could also use a mix of both! Have name and prerequisite fields but also allow for tag prerequisites (so all of the cards with a given tag must be introduced first, but the prerequisites are all still given in the field).
Once again, requiring users to mess with their notetypes raises unacceptable compatibility issues, I think. Happy to be proved wrong, though!
- How do we choose the order between cards that are not directly related to each other (e.g, two leaves on the graph)? Should we just use creation time?
My initial thought is yes, this seems good, but then I read your next bulletpoint, haha.
- Do we (1) go all the way up a branch before moving to the next leaf, (2) visit every leaf before moving upwards, or (3) some mix of both (perhaps based on creation time)?
I'm a bit confused by your terminology, but it seems like you're asking whether we should do DFS vs. BFS on the prerequisite graph.
Let me know if you disagree, but I feel there is actually a lot of philosophical weight to this question. This is asking whether it makes sense to go deep in a specific topic right-away, and thus become an expert in a small area rather quickly, or learn the basics of everything first, and proceed more slowly, but gain a strong, general view of the whole area. I have heard from many mathematicians that the former is the only right way to do things, but I have always disagreed. I feel mastering the basics is fundamentally important, and allows you to gain insights from 'cross-training' that you wouldn't otherwise get. Super-specialized knowledge doesn't come in handy very often anyway. The usefulness of facts, definitions, and theorems follows a power-law distribution, and the really frequently used, useful stuff is all very basic.
- Do we reschedule cards that have no prerequisites and are not themselves prerequisites for other cards?
Islands like these I think should either be left untouched (just order by creation date, comparing against the root of each tree), or scheduled last. If they aren't related to other things, I feel that makes them somewhat unimportant.
I was looking at https://addon-docs.ankiweb.net/the-anki-module.html for information on using the
anki
package, but that's very limited. Is there better documentation that you have been using?
There is nothing very intimidating about the API. It's quite straightforward, if a bit messy. It is undocumented, but that just means there isn't a nice website explaining everything in prose. I would poke around in anki/pylib/anki/
a bit and take a look. Whenever I need to figure out how to do something, I just grep
in there for the relevant words. Start with collection.py
if you're just curious how things work.
See the AnkiDroid dev docs for a bit more info on the database.
If you run into trouble, just ping me.
As an aside, are you planning on taking a stab at an implementation of this yourself?
I disagree about the browser ease-of-scanning. You can only see the sort field in the browser, so if
prerequisites
is a separate field, the user can't see it at all. You can see tags in the browser. And you can also filter ontag: prereq=spectral-triple
. That seems pretty nice. If we generate a two-way mapping, then you could see all the cards that depend on learning a given card as well, but that would definitely clutter the tags.
There is an option in the card browser to show any template field, but I agree that tags are a better way to go for now. (Down the line, if this tool turns into a fully featured, widely used, it might be worthwhile to give users more flexibility about where they place the prerequisites, but that's a big "if".)
If you're talking about prerequisite tags like
prereq=chapter_2
, that sounds like you're adding groups of cards now. I would say keep the initial implementation fully granular. If you want to require a whole group, add each prereq individually.
Yes, I agree. If we want to require all the cards from, say, Chapter 2 as prerequisites for Chapter 3 cards, then we would add a chapter_2
tag to each Chapter 2 card and a tag prereq=chapter_2
to each Chapter 3 card.
Renaming is tough. We could incorporate the
guid
into the data of the tag somehow, or make this out-of-scope. The user could of course use something like ki to do this batch edit (that is what ki is for). Open to ideas here.
Oh, we don't have to worry about renaming tags. Anki already has that feature built in! You simply open the context menu for any of the tags in the left panel of the card browser and click "rename".
- Do we (1) go all the way up a branch before moving to the next leaf, (2) visit every leaf before moving upwards, or (3) some mix of both (perhaps based on creation time)?
I'm a bit confused by your terminology, but it seems like you're asking whether we should do DFS vs. BFS on the prerequisite graph.
Yes, that's right.
Let me know if you disagree, but I feel there is actually a lot of philosophical weight to this question. This is asking whether it makes sense to go deep in a specific topic right-away, and thus become an expert in a small area rather quickly, or learn the basics of everything first, and proceed more slowly, but gain a strong, general view of the whole area. I have heard from many mathematicians that the former is the only right way to do things, but I have always disagreed. I feel mastering the basics is fundamentally important, and allows you to gain insights from 'cross-training' that you wouldn't otherwise get. Super-specialized knowledge doesn't come in handy very often anyway. The usefulness of facts, definitions, and theorems follows a power-law distribution, and the really frequently used, useful stuff is all very basic.
I agree with you that a breadth-first approach to learning the basics makes sense, but as we design this tool, I think we should make it in such a way that we don't impose a preferred ordering. To that end, the way I want to choose the ordering is as follows. Start with all of the cards in the collection partitioned into two categories: "Free" contains every card that does not have any prerequisites and "Blocked" contains every card that has a prerequisite in the Free or Blocked cards.
forget
to put that card at the end of the new card queue and remove it from the Free cards.This will cause cards to be introduced in the order that users created them except when necessary to satisfy prerequisites. If a user wants to learn cards depth-first, then they can create cards in that order and the same for breadth-first.
It might be better to use the new card queue order instead of the creation date so that if a user repositions a card before using our tool it would affect the outcome.
As an aside, are you planning on taking a stab at an implementation of this yourself?
Yes, I would like to, but it might take me a while to find time for it, so if you get around to it first, don't wait for me! 😊
Oh, we don't have to worry about renaming tags. Anki already has that feature built in! You simply open the context menu for any of the tags in the left panel of the card browser and click "rename".
I think the trouble is when the content of a note fundamentally changes. Your example suggests that each prerequisite note will have a tag, and the prereq=<some-identifier-or-name>
will refer to it from the taglists of other notes that depend on it. But what will this identifier be, and will it change when the note contents change? If it's semantically meaningful, then it may be some sort of abbreviated version of one or more fields. But what if those fields change?
This will cause cards to be introduced in the order that users created them except when necessary to satisfy prerequisites. If a user wants to learn cards depth-first, then they can create cards in that order and the same for breadth-first.
This doesn't seem like a bad approach. I think this will require creation date to be added to the note grammar, obviously. I also worry a bit about making users worry about the order in which they create notes. Suppose you go back and add more detail to a section or topic. Well now you've messed with the introduction order and incur some overhead for fixing it. Pure BFS/DFS only using creation date to break ties does not have this problem.
It might be better to use the new card queue order instead of the creation date so that if a user repositions a card before using our tool it would affect the outcome.
Agreed!
Yes, I would like to, but it might take me a while to find time for it, so if you get around to it first, don't wait for me! 😊
Yes of course. I'm currently deep into a refactor of the whole codebase. I have found that the complexity is beginning to bloom beyond the point where things are maintainable, so I'm optimizing for a reduction in line count in __init__.py
. I'm about a 1/3 of the way through, and cut off 500 lines so far.
I have started working on this here. So far, I've written the code to extract the prerequisite notes for each note. Next up is figuring out how to sort the cards.
Hey! That's exciting stuff! Looks good so far.
Oh, we don't have to worry about renaming tags. Anki already has that feature built in! You simply open the context menu for any of the tags in the left panel of the card browser and click "rename".
I think the trouble is when the content of a note fundamentally changes. Your example suggests that each prerequisite note will have a tag, and the
prereq=<some-identifier-or-name>
will refer to it from the taglists of other notes that depend on it. But what will this identifier be, and will it change when the note contents change? If it's semantically meaningful, then it may be some sort of abbreviated version of one or more fields. But what if those fields change?
I'm not sure I follow. It sounds like you are hoping to automatically generate or update tags based on the content of notes. I think it is reasonable to simply make editing tags completely manual. If the content of a card fundamentally changes, then the user is already editing the note so they should just update the tags at the same time.
This will cause cards to be introduced in the order that users created them except when necessary to satisfy prerequisites. If a user wants to learn cards depth-first, then they can create cards in that order and the same for breadth-first.
This doesn't seem like a bad approach. I think this will require creation date to be added to the note grammar, obviously. I also worry a bit about making users worry about the order in which they create notes. Suppose you go back and add more detail to a section or topic. Well now you've messed with the introduction order and incur some overhead for fixing it. Pure BFS/DFS only using creation date to break ties does not have this problem.
Would it be possible to add the new card queue placement to the grammar instead of the creation date? I'd prefer to avoid editing the creation date to change the order if only to preserve the "Cards Created" statistics.
Oh, we don't have to worry about renaming tags. Anki already has that feature built in! You simply open the context menu for any of the tags in the left panel of the card browser and click "rename".
I think the trouble is when the content of a note fundamentally changes. Your example suggests that each prerequisite note will have a tag, and the
prereq=<some-identifier-or-name>
will refer to it from the taglists of other notes that depend on it. But what will this identifier be, and will it change when the note contents change? If it's semantically meaningful, then it may be some sort of abbreviated version of one or more fields. But what if those fields change?I'm not sure I follow. It sounds like you are hoping to automatically generate or update tags based on the content of notes. I think it is reasonable to simply make editing tags completely manual. If the content of a card fundamentally changes, then the user is already editing the note so they should just update the tags at the same time.
Ah, I understand. I was thinking that when you designate A
as a prerequisite of B
, then we were making the assumption that both A
and B
were notes. But in fact the system that you are proposing is one in which A: Tag
and B: Note
. Is that right? If so, I like it. Seems like a much more elegant way to do things. And you're right, this means that it is reasonable to make changes to the tags the user's responsibility.
This will cause cards to be introduced in the order that users created them except when necessary to satisfy prerequisites. If a user wants to learn cards depth-first, then they can create cards in that order and the same for breadth-first.
This doesn't seem like a bad approach. I think this will require creation date to be added to the note grammar, obviously. I also worry a bit about making users worry about the order in which they create notes. Suppose you go back and add more detail to a section or topic. Well now you've messed with the introduction order and incur some overhead for fixing it. Pure BFS/DFS only using creation date to break ties does not have this problem.
Would it be possible to add the new card queue placement to the grammar instead of the creation date? I'd prefer to avoid editing the creation date to change the order if only to preserve the "Cards Created" statistics.
Possible? Certainly. I think it is a good idea to avoid messing with the creation date unnecessarily. I will reserve judgement on what would be the best thing to put in the grammar until we've got a working implementation of beyondki
. Something that can read a correctly tagged deck from anki
and compute an ordering.
I am somewhat worried about the complexity of this. Consider that however we serialize the ordering, it must make sense if decks are excised from a collection, or if many foreign decks, each with their own ordering, are assimilated into an existing collection. The obvious solution to this problem is to just recompute the ordering on every push
, but that would mean that we'd have to at least import the sorting logic within ki
, which I'm somewhat loathe to do, because it adds a dependency to the application, and it forces one thing to do two things. It just generally increases program complexity.
Let's proceed under the assumption that we do recompute the ordering on each, though. In that case, we need not add anything to the note grammar or the repository format. We need only pass the relevant data structures to some function imported from beyondki
.
I think my current solution to this issue is just, "don't collaborate on decks". This will be here for anyone who wants to implement it in the future, but I think the scope of this project ends at the ability to push individual decks to GitHub.
Is it possible when using Ki to set the order that cards are introduced? It would be nice to be able to collaboratively design a deck that has a logical order to the introduction of cards