patternpythonMinor
Flashcard memory training
Viewed 0 times
trainingflashcardmemory
Problem
I have written a simple flashcard program that runs in the terminal:
-
It loads the cards from an ordinary
-
It provides a way to add flashcard from within the program to avoid that beginners editing the plain-text mess the formatting up.
-
It asks questions one after the other, because it maximizes learning (no asking of "do you want to continue?")
-
It does not check input for validity, by design, I have a library to check for input validity but I am not using it to allow people to more easily run my code.
-
It deletes a question if you say it correctly 3 times in one session and duplicates it each time you say it wrong. This way you are asked on your weaknesses most often than your strengths.
-
It contains no noise as it clears the screen before asking each question.
-
It should run in a terminal as the code used for cleaning the screen does not work in IDLE (why would you run this in the IDLE anyway? It can not even be made fullscreen.)
-
It is about 50 lines long, also counting blank lines and imports, so it does suffer code bloat.
-
If you give a wrong answer it sleeps more than when you give a correct answer, so you can have time to read the correction.
-
It captures CTRL+C and goes back to asking if you want to add or view flashcards. If you want to close it in one keypress, use CTRL+D
-
It uses one global variable even if global variables should generally be avoided because: practicality beats purity
```
import random
import time
from collections import defaultdict
FLASHCARD_FILENAME = "flashcards.txt"
correct_dict = defaultdict(int)
def clear_terminal():
# Credit to: http://stackoverflow.com/questions/2084508/clear-terminal-in-python
print(chr(27) + "[2J")
def remove_string(filename, string):
with open(filename) as f:
content = f.read()
with open(filename, "w+") as ff:
ff.write(content.replace(string, '', 1))
def add_flashcard():
question = input("Enter the question for the new fl
-
It loads the cards from an ordinary
.txt file.-
It provides a way to add flashcard from within the program to avoid that beginners editing the plain-text mess the formatting up.
-
It asks questions one after the other, because it maximizes learning (no asking of "do you want to continue?")
-
It does not check input for validity, by design, I have a library to check for input validity but I am not using it to allow people to more easily run my code.
-
It deletes a question if you say it correctly 3 times in one session and duplicates it each time you say it wrong. This way you are asked on your weaknesses most often than your strengths.
-
It contains no noise as it clears the screen before asking each question.
-
It should run in a terminal as the code used for cleaning the screen does not work in IDLE (why would you run this in the IDLE anyway? It can not even be made fullscreen.)
-
It is about 50 lines long, also counting blank lines and imports, so it does suffer code bloat.
-
If you give a wrong answer it sleeps more than when you give a correct answer, so you can have time to read the correction.
-
It captures CTRL+C and goes back to asking if you want to add or view flashcards. If you want to close it in one keypress, use CTRL+D
-
It uses one global variable even if global variables should generally be avoided because: practicality beats purity
```
import random
import time
from collections import defaultdict
FLASHCARD_FILENAME = "flashcards.txt"
correct_dict = defaultdict(int)
def clear_terminal():
# Credit to: http://stackoverflow.com/questions/2084508/clear-terminal-in-python
print(chr(27) + "[2J")
def remove_string(filename, string):
with open(filename) as f:
content = f.read()
with open(filename, "w+") as ff:
ff.write(content.replace(string, '', 1))
def add_flashcard():
question = input("Enter the question for the new fl
Solution
-
I would use
I have had problems with
(I know the cool kids use PowerShell now... but cmd is still there!)
-
I personally dislike that you are reading the 'file based dictionary'.
I would prefer to not ware out my hard-drive.
After one day of card revision, and bam, my hard-drive no longer works.
The price you pay for passing exams...
-
Python prefers to limit the line length to 79, as then things are more readable.
For example we don't need to scroll in CR.
And this is also why newspapers use columns.
-
I personally would fail silently on the keyboard interrupt to exit the program.
Leaving with an error is extremely ugly.
-
If you run out of cards, then your program abruptly stops.
I would at least explain something to the user.
(This may be part of that extra validation you removed).
I would write a 'constructor' and 'deconstructor' for this program,
which would be the only two times you read or write to the file.
To hold the data, I would use the following structure:
If you didn't know you can use
This is immensely helpful for the deconstructor.
This will look like:
Now if you change all the file operations to use the dictionary instead.
The state of your program will be easier to understand.
And my review would be over.
As someone who supports using the battery's in Python when needed,
you can remove
And you can use
Your file looks like a csv with a different delimiter... So you may want to try that out.
If I were to re-write your code:
(It's longer, but pretty colours!)
I would use
os.system('cls' if os.name == 'nt' else 'clear') rather than ANSI, if you need to support for all terminal emulators.I have had problems with
'\b', so I don't have much faith in cmd.(I know the cool kids use PowerShell now... but cmd is still there!)
-
I personally dislike that you are reading the 'file based dictionary'.
I would prefer to not ware out my hard-drive.
After one day of card revision, and bam, my hard-drive no longer works.
The price you pay for passing exams...
-
Python prefers to limit the line length to 79, as then things are more readable.
For example we don't need to scroll in CR.
And this is also why newspapers use columns.
-
I personally would fail silently on the keyboard interrupt to exit the program.
Leaving with an error is extremely ugly.
-
If you run out of cards, then your program abruptly stops.
I would at least explain something to the user.
(This may be part of that extra validation you removed).
I would write a 'constructor' and 'deconstructor' for this program,
which would be the only two times you read or write to the file.
To hold the data, I would use the following structure:
{
'say': [3, 'said said'],
...
}If you didn't know you can use
finally to execute something even if the program fails.This is immensely helpful for the deconstructor.
This will look like:
if __name__ == "__main__":
load_file()
try:
main()
except KeyboardInterrupt:
print(chr(27) + "[36m goodbye!" + chr(27) + "[0m")
finally:
save_file()Now if you change all the file operations to use the dictionary instead.
The state of your program will be easier to understand.
And my review would be over.
As someone who supports using the battery's in Python when needed,
you can remove
defaultdict if you change to a dict, rather than a file.And you can use
csv or json to store your data.Your file looks like a csv with a different delimiter... So you may want to try that out.
If I were to re-write your code:
(It's longer, but pretty colours!)
import random
from time import sleep
FLASHCARD_FILENAME = "flashcards.txt"
questions = {}
def clear_terminal():
print(chr(27) + "[2J")
def add_question(question, answer):
try:
questions[question][0] += 3
except KeyError:
questions[question] = [3, answer.strip().lower()]
def load_file():
with open(FLASHCARD_FILENAME) as f:
for line in f:
add_question(*line.split(' : '))
def save_file():
with open(FLASHCARD_FILENAME, 'w') as f:
for question, info in questions.items():
output = question + ' : ' + info[1] + '\n'
for _ in range((info[0] + 2) // 3):
f.write(output)
def add_flashcard():
add_question(
input("Enter the question for the new flashcard: "),
input("Enter the answer for the new flashcard: ")
)
def view_flashcard():
clear_terminal()
try:
question = random.choice(list(questions.keys()))
except IndexError:
print("We have run out of questions.")
raise KeyboardInterrupt
answer = questions[question][1]
correct = input(question + " ").lower() == answer
print("{0}[32mCorrect!{0}[0m".format(chr(27))
if correct else
"The correct answer was: '{}'".format(answer))
info = questions[question]
if not correct:
info[0] += 3
else:
info[0] -= 1
if not info[0]:
del questions[question]
sleep(2 if not correct else 0.5)
def main():
while True:
tmp = int(input("\nEnter 0 to add flashcards, 1 to view them: "))
action = [add_flashcard, view_flashcard][tmp]
try:
while True:
action()
except KeyboardInterrupt:
pass
if __name__ == "__main__":
load_file()
try:
main()
except KeyboardInterrupt:
print("\n" + chr(27) + "[36m goodbye!" + chr(27) + "[0m")
finally:
save_file()Code Snippets
{
'say': [3, 'said said'],
...
}if __name__ == "__main__":
load_file()
try:
main()
except KeyboardInterrupt:
print(chr(27) + "[36m goodbye!" + chr(27) + "[0m")
finally:
save_file()import random
from time import sleep
FLASHCARD_FILENAME = "flashcards.txt"
questions = {}
def clear_terminal():
print(chr(27) + "[2J")
def add_question(question, answer):
try:
questions[question][0] += 3
except KeyError:
questions[question] = [3, answer.strip().lower()]
def load_file():
with open(FLASHCARD_FILENAME) as f:
for line in f:
add_question(*line.split(' : '))
def save_file():
with open(FLASHCARD_FILENAME, 'w') as f:
for question, info in questions.items():
output = question + ' : ' + info[1] + '\n'
for _ in range((info[0] + 2) // 3):
f.write(output)
def add_flashcard():
add_question(
input("Enter the question for the new flashcard: "),
input("Enter the answer for the new flashcard: ")
)
def view_flashcard():
clear_terminal()
try:
question = random.choice(list(questions.keys()))
except IndexError:
print("We have run out of questions.")
raise KeyboardInterrupt
answer = questions[question][1]
correct = input(question + " ").lower() == answer
print("{0}[32mCorrect!{0}[0m".format(chr(27))
if correct else
"The correct answer was: '{}'".format(answer))
info = questions[question]
if not correct:
info[0] += 3
else:
info[0] -= 1
if not info[0]:
del questions[question]
sleep(2 if not correct else 0.5)
def main():
while True:
tmp = int(input("\nEnter 0 to add flashcards, 1 to view them: "))
action = [add_flashcard, view_flashcard][tmp]
try:
while True:
action()
except KeyboardInterrupt:
pass
if __name__ == "__main__":
load_file()
try:
main()
except KeyboardInterrupt:
print("\n" + chr(27) + "[36m goodbye!" + chr(27) + "[0m")
finally:
save_file()Context
StackExchange Code Review Q#111511, answer score: 3
Revisions (0)
No revisions yet.