js random passage generator

So, three notes on this project:

  1. It is pretty specifically tailored to my needs[1] and I have absolutely no idea whether this will be of any use to anyone but me, but I'm sharing it anyway because...well, what the hell. I'd rather put it up and have nobody care than not put it up and miss out on the chance of helping someone.

  2. It began its life as a Python script, but I converted it to JavaScript when I realized that it would be more convenient to run it in my browser than in VS Code or Terminal.[2] I'm including the Python version just because it might be of some use to someone, but the JavaScript version is more up-to-date.

  3. I'm stripping out some of the design elements which are specific to my site, just to make it easier, but if you want the full (and up-to-date)[3] version, the real actual script I use is here; feel free to use your browser tools to take a look.

Actually (and I'll have to find a more useful/accessible place to put this), as a general policy note: I have absolutely zero objections whatsoever to anyone using any part of my code whatsoever, with or without credit.[4] I like being credited (especially if you tell me about it—I love to hear that people are making use of the things I share!) but it's a nice bonus, not a requirement.

purpose and method

So, what does this thing do?

Short answer: this.

Medium answer: It takes a list of sections of texts and gives me a pseudo-random[5] selection of a line from that list.

Long answer: It takes a list of sections of texts and gives me a pseudo-random selection of a line from that list, but doing so requires that it weight those sections so that, say, the 40-line Aetia and the 476-line Ars Poetica don't show up equally often (which would give something like 10 times the weight to a line in the Aetia compared to a line in the Ars).

building the arrays

So I'm starting with an array of arrays:

texts = [['aetia', 40], ['ars poetica', 476]];

Now I can use this for weighted randomness, with the Aetia having a weight of 40 and the Ars having a weight of 476. This works great for texts that have a single set of lines, like plays.

Unfortunately, most of the texts aren't like that. I'm going to have to deal with epics, like Vergil's Aeneid, which is twelve books of a few hundred lines each. Getting a result of, say, "line 5000" is of extremely limited use to me, because line numbers start over with each new book and I do not want to do that math each time. So now instead of having arrays of ['title', line no.], I'll do ['title', [[bk. no, line no.], [bk. no, line no.]...]]. For works not divided into books, I'll just set the book number as 0, which gives me this:

texts = [['aetia', [[0, 40]]], ['ars poetica', [[0, 476]]], ['aeneid', [[1, 756], [2, 804], [3, 718], [4, 705], [5, 871], [6, 901], [7, 865], [8, 817], [9, 818], [10, 908], [11, 915], [12, 952]]]];

Doubly unfortunately, some of the texts are not epics with ten or twelve books but instead are, say, the 116 poems of Catullus. I'm also not going to be sitting here inputting 116 line counts, especially not when I have to do a fucked-up little work-around to get the numbers in the first place. What I'll do is put the number of poems in the line number of space so I can at least get a random poem.

I'll also add an extra number at the end of the total of lines in the work, either estimated or provided by Wikipedia if possible, which I can use to get an average poem length (for Catullus, about 20 lines). I can use this later to get a random number between 1 and that average length; if the line doesn't exist, I can use a random number generator to get a one-off number, but this should cover most circumstances. Now I've got this for my array (with the spacing changed for readability):

texts = [
    ['aetia', [[0, 40]]], 
    ['ars poetica', [[0, 476]]], 
    ['aeneid', [[1, 756], [2, 804], [3, 718], [4, 705], [5, 871], [6, 901], [7, 865], [8, 817], [9, 818], [10, 908], [11, 915], [12, 952]]],
    ['carmina', [[0, 116]], 2328]
];

Triply unfortunately, some of the texts on my list are prose, which means that they aren't broken up into lines. For this, I put number of chapters in the line number space and used the line count property, for which I basically just took a guess as to the average number of lines per chapter[6] and then multiplied it by the number of chapters. (Why yes, this did take for-fucking-ever.) I also threw in a true at the next index so that I can identify the approximate counts later. Now I've got:

texts = [
    ['aetia', [[0, 40]]], 
    ['ars poetica', [[0, 476]]], 
    ['aeneid', [[1, 756], [2, 804], [3, 718], [4, 705], [5, 871], [6, 901], [7, 865], [8, 817], [9, 818], [10, 908], [11, 915], [12, 952]]],
    ['annales', [[1, 81], [2, 88], [3, 76], [4, 75]], 6400, true],
    ['carmina', [[0, 116]], 2328]
];

Quadrupally unfortunately, sometimes texts have the same names, so we really need to include author names:

texts = [
    ['callimacus', 'aetia', [[0, 40]]], 
    ['horace', 'ars poetica', [[0, 476]]], 
    ['vergil', 'aeneid', [[1, 756], [2, 804], [3, 718], [4, 705], [5, 871], [6, 901], [7, 865], [8, 817], [9, 818], [10, 908], [11, 915], [12, 952]]],
    ['tacitus', 'annales', [[1, 81], [2, 88], [3, 76], [4, 75]], 6400, true],
    ['catullus', 'carmina', [[0, 116]], 2328]
];

So now I have an array a containing arrays a[n], each containing:

  • a[n][0]: author name (string)
  • a[n][1]: text title (string)
  • a[n][2]: books and number of lines/poems/chapters (array containing x arrays containing two numbers)
  • a[n][3]: total line count (number, optional)
  • a[n][4]: approximation marker (boolean, optional)

building the functions

Now that I have my arrays, I need to do a handful of things with them:

  1. Create a list of text weights in line numbers;
  2. Get a weighted random text from a corpus;
  3. Get a random line from a random text in a corpus and display it on a webpage;
  4. Produce a list of all the sections from all the texts in a corpus (optional).

The first two are easy. For the first, I need an array of total line numbers, which I make by either taking a[n][3] if it exists or getting the sum of the line numbers of all books in a text if it doesn't. For the second, I take the corpus array and the weight array and use the both of them to produce a selection from the corpus based on this tutorial.

For the third, I input the corpus and use functions one and two to select a random text, then use function two again to select a book from within the text. Then I use a simple random method to get a line/poem/chapter number within that book. If there is a total line count at a[n][3], I also use that to get an average line count per poem/chapter and then get a random line number within that (these are marked with a star so that I'm not surprised if the line fails to exist). All of this is then formatted so it looks pretty in the real version, but I've stripped that back to pretty basic HTML for my example code here.

For the fourth, I just want to run through the array for the title and author, then through the books array at a[n][2] to get the included books and the line/poem/chapter number of each book. I also have a note on the number of lines total, which is marked if it's an approximation. Each list is enclosed in a <ul> in a <details> tag, with each text in its own <li> tag.

the code

javascript

const testCorp = [
	['catullus', 'carmina', [[0, 116]], 2328],
	['tacitus', 'annals', [[13, 58], [14, 65], [15, 74], [16, 35]], 4640]
];

// READING LIST CORPUS
const rlCorp = [
	// LATIN
	['caesar', 'de bello civile', [[1, 87]], 1740, true],
	['caesar', 'de bello gallico', [[1, 54]], 1080, true],
	['cicero', 'de officiis', [[1, 161]], 2185, true],
	['cicero', 'de oratore', [[1, 265]], 3930, true],
	['cicero', 'in catalinam', [[1, 33], [2, 29]], 682, true],
	['cicero', 'in antoniam', [[2, 119]], 1488, true],
	['cicero', 'pro archia', [[0, 32]], 448, true],
	['cicero', 'pro caelio', [[0, 80]], 1000, true],
	['catullus', 'carmina', [[0, 116]], 2328], 
	['horace', 'ars poetica', [[0, 476]]], 
	['horace', 'carmina', [[1, 38], [2, 20], [3, 30]], 2452], 
	['horace', 'epistulae', [[2.1, 270], [2.2, 216]]], 
	['horace', 'sermones', [[1, 10]], 1029], 
	['juvenal', 'satires', [[1, 171], [2, 170], [3, 322], [4, 154], [5, 173], [6, 695], [7, 243], [8, 275], [9, 150], [10, 366], [11, 208], [12, 130], [13, 249], [14, 331], [15, 174], [16, 60]]],
	['livy', 'ab urbe condita', [[1, 60], [6, 42], [21, 63]], 2805, true], 
	['lucan', 'pharsalia', [[1, 695], [2, 736], [3, 762], [4, 824], [5, 815], [6, 830], [7, 872], [8, 872], [9, 1108], [10, 546]]], 
	['lucretius', 'de rerum naturae', [[1, 1117], [2, 1174], [3, 1094], [4, 1287], [5, 1457], [6, 830]]], 
	['ovid', 'amores', [[1.1, 34], [1.2, 52], [1.3, 26], [1.4, 70], [1.5, 26], [1.6, 74], [1.7, 68], [1.8, 114], [1.9, 46], [1.10, 64], [1.11, 28], [1.12, 30], [1.13, 48], [1.14, 56], [1.15, 42]]], 
	['ovid', 'fasti', [[4, 954]]], 
	['ovid', 'metamorphoses', [[1, 779], [8, 884]]], 
	['plautus', 'amphitruo', [[0, 1146]]], 
	['plautus', 'casina', [[0, 1018]]], 
	['plautus', 'menaechmi', [[0, 1162]]], 
	['propertius', 'elegies', [[1.1, 38], [4.4, 94], [4.6, 86], [4.8, 88]]],
	['quintilian', 'institutio oratoriae', [[10.1, 131]], 655, true],
	['sallust', 'bellum catilinae', [[0, 61]], 1342, true],
	['seneca minor', 'epistulae', [[51, 13], [77, 20], [78, 29], [90, 46], [114, 27], [122, 19]], 1155], 
	['seneca minor', 'phaedra', [[0, 1280]]], 
	['statius', 'silvae', [[4.0, 37], [4.1, 47], [4.2, 67], [4.3, 163], [4.4, 105], [4.5, 60], [4.6, 109], [4.7, 56], [4.8, 62], [4.9, 55]]],
	['suetonius', 'vita augustae', [[0, 101]], 750, true], 
	['sulpicia', 'elegies', [[0, 6]], 40],
	['tacitus', 'agricola', [[0, 46]], 650],
	['tacitus', 'annales', [[1, 81], [2, 88], [3, 76], [4, 75]], 6400, true],
	['tacitus', 'dialogus', [[0, 42]], 840, true],
	['tacitus', 'historiae', [[1, 90]], 1800, true], 
	['terence', 'adelphoe', [[0, 997]]], 
	['terence', 'phormio', [[0, 1055]]], 
	['tibullus', 'elegies', [[1.1, 78], [1.3, 94], [1.4, 84], [2.3, 80], [2.5, 122]]], 
	['vergil', 'aeneid', [[1, 756], [2, 804], [3, 718], [4, 705], [5, 871], [6, 901], [7, 865], [8, 817], [9, 818], [10, 908], [11, 915], [12, 952]]], 
	['vergil', 'eclogues', [[1, 83], [2, 73], [3, 111], [4, 63]]], 
	['vergil', 'georgics', [[1, 514], [4, 566]]],

	// GREEK
	['aeschylus', 'agamemnon', [[0, 1673]]], 
	['aeschylus', 'choephoroi', [[0, 1075]]], 
	['aeschylus', 'eumenides', [[0, 1470]]], 
	['apollonius rhodius', 'argonautica', [[3, 1407]]], 
	['aristophanes', 'nubes', [[0, 1510]]], 
	['aristophanes', 'ranae', [[0, 1533]]],
	['callimachus', 'aetia', [[0, 40]]], 
	['callimachus', 'hymn to zeus', [[0, 96]]], 
	['callimachus', 'hymn to apollo', [[0, 113]]], 
	['callimachus', 'hymn to athena', [[0, 142]]], 
	['callimachus', 'hymn to demeter', [[0, 138]]],
	['demosthenes', 'olynthiacs', [[1, 28], [2, 31], [3, 36]], 714, true],
	['demosthenes', 'de corona', [[0, 324]], 2430, true],
	['demosthenes', 'against neiara', [[0, 123]], 923, true],
	['dio chrysostom', 'orationes', [[7, 152]], 1140, true],
	['euripides', 'bacchae', [[0, 1392]]], 
	['euripides', 'hippolytus', [[0, 1466]]], 
	['euripides', 'medea', [[0, 1419]]], 
	['hesiod', 'theogony', [[0, 1022]]],
	['herodotus', 'histories', [[1, 216], [6, 140], [7, 239], [8, 144]], 14780, true], 
	['homer', 'iliad', [[1, 611], [2, 877], [3, 461], [4, 544], [5, 909], [6, 529], [9, 713], [16, 867], [18, 617], [19, 424], [22, 515], [23, 897], [24, 804]]], 
	['homer', 'odyssey', [[1, 444], [5, 493], [6, 331], [7, 347], [8, 586], [9, 566], [10, 574], [11, 640], [12, 543], [19, 604], [21, 434], [22, 501], [23, 548]]],
	['longus', 'daphnis and chloe', [[1, 32], [2, 39], [3, 34], [4, 40]], 2900, true],
	['lucian', 'true histories', [[0, 47]], 705, true],
	['lysias', 'on the murder of eratosthenes', [[0, 50]], 300, true],
	['lysias', 'against eratosthenes', [[0, 100]], 750, true], 
	['menander', 'dyscolus', [[0, 964]]], 
	['pindar', 'isthmian ode', [[7, 51]]], 
	['pindar', 'olympian ode', [[1, 116], [2, 100], [7, 95]]], 
	['pindar', 'pythian ode', [[1, 100], [8, 100], [10, 72]]],
	['plutarch', 'vita ciceronis', [[0, 49]], 1764],
	['plutarch', 'demosthenes and cicero', [[0, 5]], 96, true], 
	['sophocles', 'antigone', [[0, 1353]]], 
	['sophocles', 'oedipus coloneus', [[0, 1779]]], 
	['sophocles', 'oedipus tyrannos', [[0, 1530]]], 
	['theocritus', 'idylls', [[1, 152], [2, 166], [7, 157], [11, 81]]],
	['thucydides', 'peloponnesian war', [[1, 146], [6, 105], [7, 87]], 10140, true],
	['xenophon', 'anabasis', [[3.1, 47], [3.2, 39], [3.3, 20], [3.4, 49], [3.5, 18]], 865, 1]
];

// FOR FUN CORPUS
const myCorp = [
	// LATIN
	['catullus', 'carmina', [[0, 116]], 2328], 
	['lucan', 'pharsalia', [[1, 695], [2, 736], [3, 762], [4, 824], [5, 815], [6, 830], [7, 872], [8, 872], [9, 1108], [10, 546]]], 
	['ovid', 'amores', [[1.1, 34], [1.2, 52], [1.3, 26], [1.4, 70], [1.5, 26], [1.6, 74], [1.7, 68], [1.8, 114], [1.9, 46], [1.10, 64], [1.11, 28], [1.12, 30], [1.13, 48], [1.14, 56], [1.15, 42]]],
	['ovid', 'heroides', [[1, 116], [2, 148], [3, 154], [4, 176], [5, 158], [6, 164], [7, 196], [8, 122], [9, 168], [10, 150], [11, 128], [12, 212], [13, 166], [14, 132], [15, 220]]],
	['ovid', 'metamorphoses', [[1, 779], [2, 875], [3, 733], [4, 803], [5, 678], [6, 721], [7, 865], [8, 884], [9, 797], [10, 739], [11, 795], [12, 628], [13, 968], [14, 851], [15, 879]]], 
	['seneca minor', 'phaedra', [[0, 1280]]], 
	['seneca minor', 'medea', [[0, 1027]]], 
	['seneca minor', 'thyestes', [[0, 1112]]], 
	['tacitus', 'annals', [[13, 58], [14, 65], [15, 74], [16, 35]], 4640],
	['vergil', 'aeneid', [[1, 756], [2, 804], [3, 718], [4, 705], [5, 871], [6, 901], [7, 865], [8, 817], [9, 818], [10, 908], [11, 915], [12, 952]]], 
	['vergil', 'georgics', [[1, 514], [4, 566]]],

	// GREEK
	['aeschylus', 'agamemnon', [[0, 1673]]], 
	['aristophanes', 'ranae', [[0, 1533]]], 
	['callimachus', 'aetia', [[0, 40]]], 
	['euripides', 'medea', [[0, 1419]]],
	['homer', 'iliad', [[1, 611], [6, 529], [22, 515], [23, 897], [24, 804]]], 
	['homer', 'hymn', [[2, 495], [3, 546], [4, 580], [5, 293]]],
	['sophocles', 'antigone', [[0, 1353]]], 
	['sophocles', 'elektra', [[0, 1353]]], 
	['sophocles', 'oedipus tyrannos', [[0, 1530]]],
	['unknown', 'batrachomyomachia', [[0, 304]]]
	];

// FUNCTIONS
function textLengths(corpus) {
	var len = [];

	for (let i of corpus) {
		let x = 0;

		if (i.length == 4) {
			x = i[3];
		} else {
			list = Array.from(i[2]);

			for (let j of i[2]) {x += j[1]};
		}

		len.push(x);
	}

	return(len);
}

function weightedRandom(items, weights) {
	// via https://www.codementor.io/@trehleb/weighted-random-in-javascript-1mxquk46q0
  
	// Preparing the cumulative weights array.
	const cumulativeWeights = [];
	for (let i = 0; i < weights.length; i += 1) {
	  cumulativeWeights[i] = weights[i] + (cumulativeWeights[i - 1] || 0);
	}
  
	// Getting the random number in a range of [0...sum(weights)]
	const maxCumulativeWeight = cumulativeWeights[cumulativeWeights.length - 1];
	const randomNumber = maxCumulativeWeight * Math.random();
  
	// Picking the random item based on its weight.
	for (let itemIndex = 0; itemIndex < items.length; itemIndex += 1) {
	  if (cumulativeWeights[itemIndex] >= randomNumber) {
		return items[itemIndex];
	  }
	}
  }

function randomLine(corpus) {
	length = textLengths(corpus);

	// (PSEUDO-)RANDOM SELECTION
	let choice = weightedRandom(corpus, length);

	const auth = choice[0];
	const text = choice[1];

	let lns = [];

	for (let i of choice[2]) {
		let x = i[1];
		lns.push(x);
	}

	let loc = weightedRandom(choice[2], lns)

	const bk = loc[0];
	const ln = Math.floor(Math.random() * loc[1]) + 1;

	// MARK NON-LINE RESULTS
	let star = '';

	if (choice.length == 4) {
		sections = 0;

		for (let i of choice[2]) {
			sections += i[1];
		}

		avgLen = choice[3] / sections;
		provRand = Math.floor(Math.random() * avgLen) + 1;
		star = '.' + provRand.toString() + '*';
	}

	// PRINT RESULT
	let result = '';

	if (bk == 0){
		result = auth + ", <em>" + text + "</em> " + ln.toString() + star;
	} else {
		result = auth + ", <em>" +  text + "</em> " + bk.toString() + "." + ln.toString() + star;
	}

	resultHTML = '<div><h3>';

	if(corpus == rlCorp) {
		resultHTML += 'reading list';
	} else {
		resultHTML += 'my list';
	}

	resultHTML += '</h3><p>' + result + '</p></div>';

	return(resultHTML);
}

function corpList(corpus) {
	let resultHTML = '<details><summary>';

	if(corpus == rlCorp) {
		resultHTML += 'reading list</summary>';
	} else {
		resultHTML += 'my list</summary>';
	}

	resultHTML += '<ul>'

	for (let i of corpus) {
		auth = i[0];
		text = i[1];

		range = '';

		for (let j of i[2]) {
			if (j[0] == 0) {
				range += '1–' + j[1].toString();
				break;
			} else if (i[2].indexOf(j) < i[2].length - 1) {
				range += j[0].toString() + '.1–' + j[1].toString() + ', ';
			} else {
				range += j[0].toString() + '.1–' + j[1].toString();
			}
		}

		if (i[3]) {
			ct = i[3].toString().replace(/'B(?<!'.'d*)(?=('d{3})+(?!'d))/g, ",");

			if (i[4]) {
				range += ' (approx. ' + ct + ' lns.)'
			} else {
				range += ' (' + ct + ' lns.)'
			}
		}

		resultHTML += '<li>' + auth + ', <em>' + text + '</em>: ' + range + '</li>';
	}

	resultHTML += '</ul></details>';

	return(resultHTML);
}

// TEST RESULTS
// let pageHTML = randomLine(testCorp, testlen)

// PRODUCE HTML
let pageHTML = '';

// RANDOM SELECTIONS
pageHTML += '<div><h2>random passages</h2>';
pageHTML += '<div>' + randomLine(rlCorp) + '</div>';
pageHTML += '<div>' + randomLine(myCorp) + '</div>';
pageHTML += '</div>';

// CORPORA LISTS
pageHTML += '<div><h2>corpus information</h2>'
pageHTML += corpList(rlCorp, 'reading list');
pageHTML += corpList(myCorp, 'personal list');
pageHTML += '</div>'

// INSERT HTML
const element = document.getElementById('result');
if (element) element.innerHTML = pageHTML;

python

Again, note that this is out-of-date relative to the JS, but I'm including it just in case.

import random
from re import sub

# CORPUS VARIABLES
# FORMAT: [author, title [[bk, lines](, [bk, lines]...)](, total lines)]
# (bits in parentheses are optional)

## NOTES
# the line numbers are for weighting the number generation
# so that longer works show up more often
# you could remove this if you wanted but it would be kind of a pain

# when a work is made up of short enough sections, e.g. catullus
# i put poem numbers, etc. in place of line numbers and add a total line count at the end
# this will generate a poem number with a star at the end
# for a random line within that, you'll need an additional random number generator
# if you have all the bk/poem numbers with associated lines, the total is unneccessary

# if the work is all one piece (e.g. a play) book no. is 0

# LATIN CORPUS
latCorp = [\
	['catullus', 'carmen', [[0, 116]], 2328], \
	['horace', 'ars poetica', [[0, 476]]], \
	['horace', 'carmina', [[1, 38], [2, 20], [3, 30]], 2452], \
	['horace', 'epistulae', [[2, 2]], 486], \
	['horace', 'sermones', [[1, 10]], 1029], \
	['juvenal', 'satires', [[0, 16]], 3871], \
	['lucan', 'pharsalia', [[1, 695], [2, 736], [3, 762], [4, 824], [5, 815], [6, 830], [7, 872], [8, 872], [9, 1108], [10, 546]]], \
	['lucretius', 'de rerum naturae', [[1, 1117], [2, 1174], [3, 1094], [4, 1287], [5, 1457], [6, 830]]], \
	['ovid', 'amores', [[1.1, 34], [1.2, 52], [1.3, 26], [1.4, 70], [1.5, 26], [1.6, 74], [1.7, 68], [1.8, 114], [1.9, 46], [1.10, 64], [1.11, 28], [1.12, 30], [1.13, 48], [1.14, 56], [1.15, 42]]], \
	['ovid', 'fasti', [[4, 954]]], \
	['ovid', 'metamorphoses', [[1, 779], [8, 884]]], \
	['plautus', 'amphitruo', [[0, 1146]]], \
	['plautus', 'casina', [[0, 1018]]], \
	['plautus', 'menaechmi', [[0, 1162]]], \
	['propertius', 'elegies', [[1.1, 38], [4.4, 94], [4.6, 86], [4.8, 88]]], \
	['seneca minor', 'phaedra', [[0, 1280]]], \
	['statius', 'silvae', [[4.0, 37], [4.1, 47], [4.2, 67], [4.3, 163], [4.4, 105], [4.5, 60], [4.6, 109], [4.7, 56], [4.8, 62], [4.9, 55]]], \
	['sulpicia', 'elegy', [[0, 6]], 40], \
	['terence', 'adelphoe', [[0, 997]]], \
	['terence', 'phormio', [[0, 1055]]], \
	['tibullus', 'elegy', [[1.1, 78], [1.3, 94], [1.4, 84], [2.3, 80], [2.5, 122]]], \
	['vergil', 'aeneid', [[1, 756], [2, 804], [3, 718], [4, 705], [5, 871], [6, 901], [7, 865], [8, 817], [9, 818], [10, 908], [11, 915], [12, 952]]], \
	['vergil', 'eclogues', [[1, 83], [2, 73], [3, 111], [4, 63]]], \
	['vergil', 'georgics', [[1, 514], [4, 566]]]\
]

latLen = []
for i in latCorp:
	x = 0

	if len(i) == 4:
		x = i[3]
	else:
		for j in i[2]:
			x += j[1]

	latLen.append(x)

# GREEK CORPUS
grkCorp = [\
	['aeschylus', 'agamemnon', [[0, 1673]]], \
	['aeschylus', 'choephoroi', [[0, 1075]]], \
	['aeschylus', 'eumenides', [[0, 1470]]], \
	['apollonius rhodius', 'argonautica', [[3, 1407]]], \
	['aristophanes', 'nubes', [[0, 1510]]], \
	['aristophanes', 'ranae', [[0, 1533]]], \
	['callimachus', 'aetia', [[0, 40]]], \
	['callimachus', 'hymn to zeus', [[0, 96]]], \
	['callimachus', 'hymn to apollo', [[0, 113]]], \
	['callimachus', 'hymn to athena', [[0, 142]]], \
	['callimachus', 'hymn to demeter', [[0, 138]]], \
	['euripides', 'bacchae', [[0, 1392]]], \
	['euripides', 'hippolytus', [[0, 1466]]], \
	['euripides', 'medea', [[0, 1419]]], \
	['hesiod', 'theogony', [[0, 1022]]], \
	['homer', 'iliad', [[1, 611], [2, 877], [3, 461], [4, 544], [5, 909], [6, 529], [9, 713], [16, 867], [18, 617], [19, 424], [22, 515], [23, 897], [24, 804]]], \
	['homer', 'odyssey', [[1, 444], [5, 493], [6, 331], [7, 347], [8, 586], [9, 566], [10, 574], [11, 640], [12, 543], [19, 604], [21, 434], [22, 501], [23, 548]]], \
	['menander', 'dyscolus', [[0, 964]]], \
	['pindar', 'isthmian ode', [[7, 51]]], \
	['pindar', 'olympian ode', [[1, 116], [2, 100], [7, 95]]], \
	['pindar', 'pythian ode', [[1, 100], [8, 100], [10, 72]]], \
	['sophocles', 'antigone', [[0, 1353]]], \
	['sophocles', 'oedipus coloneus', [[0, 1779]]], \
	['sophocles', 'oedipus tyrannos', [[0, 1530]]], \
	['theocritus', 'idyll', [[1, 152], [2, 166], [7, 157], [11, 81]]]\
	]

grkLen = [] # in hundreds of lines
for i in grkCorp:
	x = 0

	if len(i) == 4:
		x = i[3]
	else:
		for j in i[2]:
			x += j[1]

	grkLen.append(x)

# LANGUAGE
# lang = input("lang: ")
lang = ""

if lang == 'latin' or lang == 'lat':
	corpus = latCorp
	length = latLen
elif lang == 'greek' or lang == 'grk':
	corpus = grkCorp
	length = grkLen
else:
	corpus = latCorp + grkCorp
	length = latLen + grkLen

# GET RANDOM TEXT
choice = random.choices(corpus, length)
choice = choice[0]

auth = choice[0]
text = choice[1]

loc = random.choice(choice[2])

bk = loc[0]
ln = random.randrange(1, loc[1])

# MARK NON-LINE RESULTS
if len(choice) == 4:
	star = '*'
else:
	star = ''

# PRINT RESULT
if bk == 0:
	result = auth + ", " + text + " " + str(ln) + star + '\n'
else:
	result = auth + ", " +  text + " " + str(bk) + "." + str(ln) + star + '\n'

print(result)

  1. Namely: a large but well-defined list of things which I am technically responsible for having studied and on which I will be tested but which no one could possibly cover in any detail, hence the utility of a (pseudo-)random generator that will help me practice my language skills and get a good, broad survey of a corpus. (And if that sounds like a shit system: yeah, it totally is. Academia is broken at some fundamental level and the language exam system in classics PhD programs is a turbo-fucked monstrosity and no, I'm definitely not bitter about it or anything.)^
  2. I am an absolute baby about running things in Terminal. I hate it. I always feel like I'm going to break my stupid computer with a typo or something.^
  3. I don't update my code posts unless I find something egregiously wrong with them (or, like, a typo that annoys me), but I'm constantly fucking around with my actual code, so the versions I share are out-of-date pretty quickly.^
  4. Though I will note that some of the things I use on my site were created by others who have shared their work under different policies (e.g. my guestbook uses Virtual Observer's comment widget, which does require that you not remove the credit statement).^
  5. If you're not familiar with the distinction between randomness and pseudo-randomness, the section "What does it mean to be random?" on this page is a good intro. Since we're not using this for cryptographical/security purposes, generic pseudo-randomness work well enough.^
  6. A chapter in a classical text is usually a long paragraph by modern standards, and depending on the text I assigned values of 5–25 lines (usually closer to the latter).^