Writing a basic Vocabulary Trainer in 30 minutes with Python

(Original post from 31. May 2012)

Below is a vocabulary trainer for my effort of improving my French. It took me about 30 minutes to write and another 30 minutes with some good music on the headphones to write the relevant XML structure. I will probably try to expand this list as I read books and articles etc.

To start, you just need to call it from command line with the correct parameters. For example to test general vocabulary with English as the given language and French as the one to be tested you would need to type in (this is for linux):

./main.py general en fr

#!usr/bin/env python
# -*- coding: iso-8859-1 -*-

import random
import string
import sys

from lxml import etree

vocab = {'chem': 'chemvocab.xml',
         'general': 'general.xml'}

def load_xml2dict(fname, lang1, lang2):
    """Load xml data from file into dict depending on flag"""

    d = {}
    with open(fname) as xml:
        tree = etree.parse(xml)
    root = tree.getroot()

    for elem in root:
        l1 = []
        l2 = []
        for child in elem:
            if child.tag == lang1:
                l1.append(child.text)
            elif child.tag == lang2:
                l2.append(child.text)
        d[tuple(l1)] = l2
    return d


def main(fname, lang1, lang2):
    d = load_xml2dict(fname, lang1, lang2)

    while True:
        choice = random.choice(d.keys())
        print '[%s]: %s' % (lang1.upper(), ', '.join(choice))
        raw_input('Press Enter for the answer')
        print '[%s]: %s' % (lang2.upper(), ', '.join(d[choice]))

        end = raw_input('Do you want to continue Enter/N: ')
        if end == "N" or end == "n":
            break

if __name__ == "__main__":
    args = sys.argv
    main(vocab[args[1]], args[2], args[3])

The XML structure is fairly straightforward and is designed with possible expansion in mind and further filtering. It looks something like this:

<vocabulary>
    <word type="verb">
        <en>to agree</en>
        <de>jmdm./etw. zustimmen</de>
        <de>in etw. einwilligen</de>
        <fr>acquiescer à qn./qc.</fr>
    </word>
    <word type="adj">
        <en>horrible</en>
        <de>schrecklich</de>
        <de>fürchterlich</de>
        <fr>épouvantable</fr>
        <fr>terrible</fr>
    </word>
    <word type="noun">
        <en>murmur</en>
        <de gender="n">Murmeln</de>
        <fr gender="m">frémissement</fr>
    </word>
</vocabulary>

Note the type attribute for . It will hopefully allow me to filter by type of word. The gender attribute is only necessary for German and French and I only really put it in for French as there are cases where it is not clear, e.g. l’émission. If I really expand on this thing, I may use it to put the gender in square brackets after the word.

Of course, this is nothing to write home about, but maybe, I will expand it a little and create a GUI for it with some filtering options or abandon python altogether and put this on my website with some jquery. We shall see. I thought it was worth sharing.

RPG Music’N’SFX – The “Previous Song” Function

(Original post from 25.11.2011)

Another little bloglet about my soon-to-be oh so awesome RPG Audioplayer. This time, I have been wondering how to implement a useable previous function.

I decided to create a list of integers, where each integer represents an index of the playlist. Initially, this list will collect the previous indices of the list, e.g. a list of 40 songs. It starts at 0 and goes regularly and playing the first 5 songs, i.e. the list will be [0,1,2,3,4]. Then it is switched to random and songs number 34 and 27 are played, leaving the list at [0,1,2,3,4,33,26]. This will go on until a certain threshold length (say 20 items). Once it reaches length 20, the first item will be removed and the latest one will be appended to the end. That way, this list will not reach ridiculous lengths when used for a long time.

When someone moves backwards, the new index will be the last item in the list, which will then be removed from the list. If someone then truly presses 20 times previous, then the code should just check for an empty list and stay at its current index.

How the audiere code is introduced with first resetting and only if x milliseconds were run before allowing a previous to be called, remains to be seen, but we’ll cross that bridge when we come to it.

So talking in code all the above operations should be standard python fare. Let’s try it:

l = range(0,5)
for i in range(15):
    l.append(random.choice(xrange(41)))

# example l is:
# [0, 1, 2, 3, 4, 14, 17, 33, 12, 20, 16, 7, 28, 
# 38, 24, 18, 29, 5, 36]

# Another index is added
l.pop(0)
l.append(nex_index)

# New list is:
# [1, 2, 3, 4, 14, 17, 33, 12, 20, 16, 7, 28, 
# 38, 24, 18, 29, 5, 36, new_index]

# Someone presses "Previous"
if l = []: # when someone presses previous 20 times
    new_index = index
else:
    new_index = l.pop() # removes last list item

I’m a little surprised myself now, that I only had to use the list.pop() method, but indeed using it, should solve all the problems outlined here.

I shall now implement them and see if it works 😀