<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="http://mkmozgawa.com/feed.xml" rel="self" type="application/atom+xml" /><link href="http://mkmozgawa.com/" rel="alternate" type="text/html" /><updated>2026-07-22T10:00:13+00:00</updated><id>http://mkmozgawa.com/feed.xml</id><title type="html">mkmozgawa.com</title><subtitle>Write an awesome description for your new site here. You can edit this line in _config.yml. It will appear in your document head meta (for Google search results) and in your feed.xml site description.</subtitle><author><name>{&quot;bio&quot;=&gt;&quot;Quidquid agis, prudenter agas et respice finem.&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/mkmozgawa&quot;}]}</name></author><entry><title type="html">J2ME</title><link href="http://mkmozgawa.com/misc/j2me-for-nokia-3310-in-2020/" rel="alternate" type="text/html" title="J2ME" /><published>2020-10-08T23:00:00+00:00</published><updated>2020-10-08T23:00:00+00:00</updated><id>http://mkmozgawa.com/misc/j2me-for-nokia-3310-in-2020</id><content type="html" xml:base="http://mkmozgawa.com/misc/j2me-for-nokia-3310-in-2020/"><![CDATA[<p>Come to think about it, quite a few personal projects I’ve worked on over the years came about because I felt I needed to go against the grain in terms of conditions or expectations. In this particular case, I started working on J2ME because a certain university professor said <em>you can do this project with any mobile technology</em>, which led to a conversation along the lines of:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Me: So I can go with any mobile technology I want, correct?

Him: Yes, most people do Android projects, but I've seen a few successful ones for iOS.

Me: I have a Nokia 3310 refurbished and it should be possible to write apps for that, _somehow_.
</code></pre></div></div>
<p>It took me a couple evenings to set things up correctly and once I got a reliable environment going, everything got much easier. (Or at least as easy as things can be when developing things for a 10-year-old platform with a heavily limited API.) So here’s a quick intro to writing your own apps for Nokia 3310 2017. This tutorial assumes a basic level of knowledge when it comes to Java.</p>

<h2 id="installation-steps">Installation steps</h2>
<p>These steps work for Windows and have been tested on a Windows 10 VM. I strongly recommend following them on a virtual machine, as there’s going to be a certain amount of outdated software involved.</p>

<ol>
  <li><a href="https://www.oracle.com/java/technologies/javase-java-archive-javase6-downloads.html">Download and install Java 6.</a></li>
  <li><a href="https://www.oracle.com/java/technologies/javame-sdk/java-me-sdk-v30.html">Download and install Java ME Platform SDK 3.0.</a></li>
</ol>

<h2 id="using-eclipse--netbeans-etc-for-development">Using Eclipse / Netbeans etc for development</h2>
<p>Can you use Eclipse, Netbeans or any other IDE? Possibly. However, after spending a considerable amount of time trying to set it up reliably I gave up. The IDE shipped with the J2ME SDK 3.0 was good enough for me.</p>

<h2 id="setup-steps">Setup steps</h2>
<p>Start the Java ME Platform SDK 3.0 app (henceforth: IDE). From now on, you need to create your new project,</p>

<h3 id="creating-a-new-project">Creating a new project</h3>
<p>Go to file -&gt; New Project. Choose “MIDP Application” from the list. Set your project name to something (e.g. <em>HelloWorld</em>) and change the project location if you so desire. Leave “Set as Main Project” and “Create Hello MIDlet” as is. On the final step, the emulator platform should be set to CLDC…SDK 3.0. The version of the device configuration and profile are left to you – I went with the defaults. Hit finish and wait a moment.</p>

<h3 id="running-the-project">Running the project</h3>
<p>Running is fairly simple - you have the big green <em>Play</em> button on the top bar.</p>

<h3 id="renaming-your-classes">Renaming your classes</h3>
<p>What if you decided that you wanted to remain the autogenerated class from HelloMIDlet to, say, <em>HelloWorld</em>? Even if you change the class name and its definition, the project is not going to run. In such a case right-click your project, select Properties and go to Application Descriptor. There, under MIDlets, you still have your old class (by now probably marked in red). Select it to remove it from the list, then click Add and confirm the choice of the new MIDlet class. Now the app should start without any issues.</p>

<h2 id="j2me-basics-creating-a-simple-app">J2ME basics: creating a simple app</h2>
<p>J2ME apps don’t have a <em>main</em> function; instead, one of the classes has to inherit from the MIDlet class and implement the CommandListener interface. The blueprint for that class has to include the following:</p>
<ul>
  <li>one or more fields; it has to at least contain a Display-type field if it’s to show anything on the screen;</li>
  <li>the class constructor, which defines the elements used in the app, such as Forms (views), Fields, Commands and more;</li>
  <li>three void methods: startApp, pauseApp and destroyApp, which define what happens on the application start, on pause (i.e. when you take a phone call while the app is running), and on exit;</li>
  <li>the “main” void method commandAction that defines the logical flow of the application based on the actions of the user.</li>
</ul>

<p>Let’s try to make a simple app that takes in the user’s name and puts it on screen.</p>

<h3 id="fields">Fields</h3>
<p>We’re going to need the following fields:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>private Display display;
private TextField field;
private TextField greeting;
private String name;
private Command writeName;
private Command exit;
private Form form;
</code></pre></div></div>

<h3 id="view">View</h3>
<p>Forms are the views of the app, so let’s start with the drawing out our form in the constructor. To create a new form, simply create a new Form object:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>form = new Form("My first form");
</code></pre></div></div>
<p>To add new elements to a form, you define them and them append them to the form, for example</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>field = new TextField("Tell me your name", "", "30", TextField.ANY);
form.append(field);
greeting = new TextField("", "Hello, person!", 30, TextField.UNEDITABLE);
form.append(greeting);
</code></pre></div></div>
<p>and then append some actions to it</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>writeName = new Command("What's my name?", Command.OK, 0);
form.addCommand(writeName);
exit = new Command("Exit", Command.EXIT, 0);
form.addCommand(exit);
</code></pre></div></div>
<p>Finally, set the command listener to listen for any commands in the app:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>form.setCommandListener(this);
</code></pre></div></div>
<p>and then set the display to show the form:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>display = Display.getDisplay(this);
display.setCurrent(form);
</code></pre></div></div>

<h3 id="methods">Methods</h3>
<p>We want the usual subjects: startApp(), pauseApp() and destroyApp(), but we’ll leave them as is. In the commandAction we’re going to read the name from the textField and then invoke the name-writing function, as well as define the exit:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public void commandAction(Command c, Displayable s) {
  if (c.getCommandType() == Command.OK) {
    name = field.getString();
    writeName(name);
  }
  if (c.getCommandType() == Command.EXIT) {
    destroyApp(false);
    notifyDestroyed();
  }
}
</code></pre></div></div>
<p>Finally, we need to define what writeName does behind the scenes, which is just a simple replacement:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>private void writeName(String name) {
  greeting.setString("Hello, " + name + "!");
}
</code></pre></div></div>

<p>All in all, this should give us <a href="https://gist.github.com/mkmozgawa/b87d0d2125e924b4802ffdf1de578553">the following code</a>.</p>

<h2 id="other-stuff">Other stuff</h2>
<p>Here are some things I’m not going to get into details here but might be worth your while.</p>

<h3 id="persisting-data">Persisting data</h3>
<p>If you need to keep data in-between application runs, <a href="https://docs.oracle.com/javame/config/cldc/ref-impl/midp2.0/jsr118/javax/microedition/rms/RecordStore.html">use the RecordStore</a>.</p>

<h3 id="adding-third-party-libraries">Adding third-party libraries</h3>
<p>It is possible to import third-party libraries; you can add them by right-clicking the project, selecting Properties and going to the Libraries and Resources tab.</p>

<p>Note, however, that if the library is not extremely small you will have to manually take in the classes you might need for it to build and run on the emulator device, which is time-consuming and definitely not fun. (I’m looking at you, the “lightweight” BouncyCastle API.)</p>

<h3 id="running-apps-on-a-real-device">Running apps on a real device</h3>
<p>It is possible to run J2ME on modern device; a good example is the 2017 Nokia 3310 Refurbished. Once you’ve built and tested the app on the emulator, copy the jar and jad files available at dist/ to your device (in the case of this Nokia you need a memory card), then navigate to that directory on the phone and install your app.</p>

<p><strong>All in all</strong>, writing these apps for basic things is fairly easy, but gets insanely messy after a while. I can only imagine how hard it gets on really capped devices (Nokia 3310 2017 has a ~360MHz CPU; a lot of J2ME-running devices these days are micro-controllers with around 40-50MHz.)</p>]]></content><author><name>{&quot;bio&quot;=&gt;&quot;Quidquid agis, prudenter agas et respice finem.&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/mkmozgawa&quot;}]}</name></author><category term="misc" /><category term="Java" /><category term="J2ME" /><category term="mobile" /><summary type="html"><![CDATA[Come to think about it, quite a few personal projects I’ve worked on over the years came about because I felt I needed to go against the grain in terms of conditions or expectations. In this particular case, I started working on J2ME because a certain university professor said you can do this project with any mobile technology, which led to a conversation along the lines of: ``` Me: So I can go with any mobile technology I want, correct?]]></summary></entry><entry><title type="html">Tiny CTF, Beginner’s Web edition</title><link href="http://mkmozgawa.com/ctf/tiny-ctf-beginners-web/" rel="alternate" type="text/html" title="Tiny CTF, Beginner’s Web edition" /><published>2020-09-18T19:40:00+00:00</published><updated>2020-09-18T19:40:00+00:00</updated><id>http://mkmozgawa.com/ctf/tiny-ctf-beginners-web</id><content type="html" xml:base="http://mkmozgawa.com/ctf/tiny-ctf-beginners-web/"><![CDATA[<p>I’ve been participating in the CTF contests <a href="https://ctftime.org/team/111943">for the past few months</a>. They have their pros and cons, but there’s definitely a lot to be learnt from almost every contest, so I wanted to introduce my colleagues to the concept and wrote a simple page with a few flags. I’m happy to say at least some of them seemed to enjoy solving problems as much as I enjoyed bashing the site together ;)</p>

<p>All tasks are intro-level CTF-wise, but require basic knowledge of API use, session management, HTTP communication and modern Web development in general. The fastest solve by my CTF team colleague was 5m 36s, but I’d expect it to take anywhere between half an hour and a good afternoon to someone with the grasp of the listed concepts but not much practice in CTFs or penetration tests.</p>

<p>Have fun and hack responsibly!</p>

<p><a href="https://small-ctf.herokuapp.com/">https://small-ctf.herokuapp.com/</a></p>]]></content><author><name>{&quot;bio&quot;=&gt;&quot;Quidquid agis, prudenter agas et respice finem.&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/mkmozgawa&quot;}]}</name></author><category term="CTF" /><category term="CTF" /><category term="educational" /><summary type="html"><![CDATA[I’ve been participating in the CTF contests for the past few months. They have their pros and cons, but there’s definitely a lot to be learnt from almost every contest, so I wanted to introduce my colleagues to the concept and wrote a simple page with a few flags. I’m happy to say at least some of them seemed to enjoy solving problems as much as I enjoyed bashing the site together ;)]]></summary></entry><entry><title type="html">MoneyPrinter().go() =&amp;gt; brrr</title><link href="http://mkmozgawa.com/python/money-printer-go-brrr/" rel="alternate" type="text/html" title="MoneyPrinter().go() =&amp;gt; brrr" /><published>2020-04-13T17:40:00+00:00</published><updated>2020-04-13T17:40:00+00:00</updated><id>http://mkmozgawa.com/python/money-printer-go-brrr</id><content type="html" xml:base="http://mkmozgawa.com/python/money-printer-go-brrr/"><![CDATA[<p>As you may or may not have realized by now, I am an avid memer. Recently one of my favourites is the <a href="https://knowyourmeme.com/memes/money-printer-go-brrr">Money Printer Go Brrr meme</a> that’s so well-fitting given the current state of affairs. I also came across a modification that I’ve enjoyed even more:</p>

<p>Thus, the <a href="https://pypi.org/project/moneyprintergobrrr/">moneyprintergobrr pip package</a> was born.</p>

<p><strong>Installation:</strong></p>

<p><code class="language-plaintext highlighter-rouge">pip install moneyprintergobrr</code></p>

<p><strong>Usage:</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from moneyprinter.moneyprinter import MoneyPrinter

MoneyPrinter().go() ## prints out 'brrr'
</code></pre></div></div>

<p><strong>Actual usage:</strong></p>

<p>Use it instead of printing out whatever you usually use for debugging.</p>

<p><strong>Sources:</strong></p>

<p><a href="https://pypi.org/project/moneyprintergobrrr/">pip package</a> / <a href="https://github.com/mkmozgawa/moneyprintergobrrr">GitHub project</a></p>]]></content><author><name>{&quot;bio&quot;=&gt;&quot;Quidquid agis, prudenter agas et respice finem.&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/mkmozgawa&quot;}]}</name></author><category term="Python" /><category term="Python" /><summary type="html"><![CDATA[As you may or may not have realized by now, I am an avid memer. Recently one of my favourites is the Money Printer Go Brrr meme that’s so well-fitting given the current state of affairs. I also came across a modification that I’ve enjoyed even more:]]></summary></entry><entry><title type="html">The Sims mods, engineering life, and what’s next</title><link href="http://mkmozgawa.com/general/sims-engineers-adversarial/" rel="alternate" type="text/html" title="The Sims mods, engineering life, and what’s next" /><published>2020-03-19T21:00:00+00:00</published><updated>2020-03-19T21:00:00+00:00</updated><id>http://mkmozgawa.com/general/sims-engineers-adversarial</id><content type="html" xml:base="http://mkmozgawa.com/general/sims-engineers-adversarial/"><![CDATA[<p>Oops, so I noticed that my last post here was in November and so decided to do a more general dump (and an outline of what’s in store in the following months, barring a global uber-crisis leading everyone alive to get back to farming).</p>

<p>I am an engineer now! I defended my thesis on <em>Quality Assurance in Web and mobile applications</em> a little over a month ago. Thus, I am now legally allowed to say “trust me, I’m an engineer”. Not much has changed, really, but I’m happy to have done it sooner rather than later.</p>

<p>I celebrated the above-mentioned achievement by taking a week off to just play video games and do nothing productive. Today’s actually my first day after that break and I’m happy to say (a) I finally finished Persona 5 after whooping 90 hours (started before the holiday, though, I didn’t spend 90 hours just playing one game) and immediately started an NG+ to have a brain cleanser ready whenever I might require it, (b) I fulfilled my long-time dream of creating my own The Sims 4 mod: now it’s possible to play Kerbal Space Program in the game and boost your rocket science skill this way:</p>

<p>I’ve tried really hard to use Windows 10 as my main private-laptop OS and it took me about two weeks to give up on it. Along the way, I’ve discovered <a href="https://support.microsoft.com/en-us/help/982736/0x80070057-error-message-when-you-back-up-files-in-windows-7">just how stupid it is</a> (note that this link is for a Windows 7 error, but the same issue applies to Windows 10, it just has a different code I can’t seem to be able to locate right now).</p>

<p>Speaking of stupid, I’m doing a Master’s degree! It took me a while to decide but eventually I settled for doing it and will, if everything goes well, do my thesis on attacks on computer vision systems (hopefully on both adversarial examples in deep learning and old-school, likes Viola Jones, oriented gradients, etc). I am equally terrified by and excited about the topic.</p>

<p>Finally, speaking of excitement, I switched jobs in October and am now well onto my way to ~world domination~ happiness and professional fulfillment. Also, can’t imagine going back to on-site work… I have all the tea I need right there in my kitchen :)</p>

<p>So, what’s next on the blog?</p>
<ol>
  <li>I’ve been working on a J2ME (yes) tutorial for a while and will publish it soon.</li>
  <li>Some other weird tutorials I might be in the mood for.</li>
</ol>

<p>Cheers!</p>]]></content><author><name>{&quot;bio&quot;=&gt;&quot;Quidquid agis, prudenter agas et respice finem.&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/mkmozgawa&quot;}]}</name></author><category term="General" /><category term="general" /><summary type="html"><![CDATA[Oops, so I noticed that my last post here was in November and so decided to do a more general dump (and an outline of what’s in store in the following months, barring a global uber-crisis leading everyone alive to get back to farming).]]></summary></entry><entry><title type="html">On some points of RFC 1738</title><link href="http://mkmozgawa.com/web/on-some-points-of-rfc-1738/" rel="alternate" type="text/html" title="On some points of RFC 1738" /><published>2019-10-28T15:00:00+00:00</published><updated>2019-10-28T15:00:00+00:00</updated><id>http://mkmozgawa.com/web/on-some-points-of-rfc-1738</id><content type="html" xml:base="http://mkmozgawa.com/web/on-some-points-of-rfc-1738/"><![CDATA[<blockquote>
  <p>Note: this is a 2014 argumentative essay I had to write for my classes. I couldn’t choose the subject I wanted, so I settled on the linguistical aspects of a certain word. I was very contrarian back then. :)</p>

</blockquote>

<p>The word <em>fuck</em> is used and abused by people, in contexts way exceeding the primary meaning of “to have sexual intercourse with (someone)” (OxfordDictionaries.com). Thus, it is fairly understandable why the word itself has been the topic of research of different branches of humanities, such as linguistics, culture studies, sociology, and more, united under the common name of <em>scatolinguistics</em> (Zwicky, 1992:ix).</p>

<p>The major points of investigation seem to be <em>fuck</em>’s etymology and first recorded uses, the linguistic analysis of its syntactic and semantic properties, and its appearances in popular culture.</p>

<p>There are numerous myths and misconceptions about the origins of <em>fuck</em> circulating in the media. Contrary to popular urban legends, the word <em>fuck</em> is not an acronym for “Found Under Carnal Knowledge”, “For Unlawful Carnal Knowledge” or, better yet, “Fornication Under Consent of the King”; all these explanations emerged in the 1960s and 1970s in popular newspapers, but there is no grain of truth to them (Sheidlower, Black, 2009:i). <em>Fuck</em> is not, in fact, an Anglo-Saxon word at all.</p>

<p>Instead, etymologists point out to borrowings from either French and Latin or Old Norse, due to both the historical relation between those languages and English in the Middle Ages and the similarity of forms - French foutre, Latin futuere, Old Norse fukja, Middle Dutch fokken, Swedish focka, etc (Hughes, 2006:188). Sheidlower and Black (2009:ii-iii), however, oppose to the French/Latin roots hypothesis and are particularly of the belief that the word was borrowed from Low German, Flemish or Dutch.</p>

<p>Not only is the etymology of <em>fuck</em> unclear, but also the date of its premiere appearance in written sources arouses considerable doubt. Buck (1949) writes about one “John le Fucker”, a name that supposedly dates back to 1278, but there is neither the source of the name nor reference provided, and the name itself could, in fact, have nothing to do with <em>fuck</em> itself (Sheidlower, Black; 2009:ii). According to Hughes’ An Encyclopaedia of Swearing, <em>fuck</em> as a functional word first appeared in a 1503 poem by a Scot poet and aristocrat, William Dunbar, and it is also recorded in the 1540s as a part of a flyting, that is, a “verbal contest of insult and obscenity” (2006:189). In England, the first instances of <em>fuck</em> seem to have occurred at the brink of the 16th century, with Sheidlower and Black citing two cases: an Anglo-Latin poem from 1475, Flen flyys, and an annotation on a margin, “O d fuckin Abbot”, made by an anonymous monk in 1528 (2009:ii-iii). A popular teenage saying “I don’t give a fuck” may seem to date back to a 1790ish poem by George Tucker, though the piece itself had not been unveiled before the broader public until 1977 (Mohr, 2013a:iv).</p>

<p>Yet, common though it might have been in everyday speech and writing, <em>fuck</em> did not appear in monolingual dictionaries until 1965: it was present in neither Dr Samuel Johnson’s A Dictionary of the English Language nor in the Oxford English Dictionary, and it remained unlisted even in the 1961 edition of Webster Dictionary. This was mainly owing to the legislative as, since the mid-19th century, printing <em>fuck</em> could result in trial and incarceration. After the 1960 case Regina vs Penguin Books, there was a surge of obscenely-titled books; but when Kenneth Tynan dropped the first-ever “f-bomb” (that is, used the world <em>fuck</em> in a broadcast) on BBC, he provoked a nation-wide debate, with politicians roaring with rage and trying to persecute him for public obscenities. Meanwhile, in the United States, <em>fuck</em> appeared in abundance in slogans during the protests against the Vietnam War, such as “Fuck the Draft” and “Fuck the Pig” (Hughes, 2006:191). It also made its way into the scientific papers of that time, with linguists trying to establish the syntactic and semantic properties of <em>fuck</em> and its derivations.</p>

<p>A comprehensive literature search shows that there seem to be two kinds of <em>fuck</em>, namely, <em>fuck</em>(1) and <em>fuck</em>(2); for the sake of clarity, these will be referred to, respectively, as the copulative <em>fuck</em> and disapproving <em>fuck</em> (Dong; in Zwicky, 1992:3). While the former of them simply encompasses the meaning of engaging in sexual intercourse, the latter proves considerably more ambiguous. The striking difference between the two <em>fucks</em> can be observed by juxtaposing pairs of sentences (asterisks denote ungrammatical utterances):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(1) *Fuck you. (copulative)
(2) Fuck yourself. (copulative)
(3) Fuck you. (disapproving)
(4) *Fuck yourself. (disapproving) As the examples above indicate, the copulative _fuck_ requires using a reflexive pronoun for the interlocutor’s self-reference (cf. _Fuck yourself and I’ll give you a dollar._), while the disapproving _fuck_ bars the usage of reflexive pronouns (cf. *_Fuck yourself and leave me alone._). Moreover, as Dong (in Zwicky, 1992:4) observes, while imperative verb phrases can be conjoined, it cannot be accomplished when using the disapproving _fuck_:

(5) Wash the dishes and sweep the floor.
(6) *Wash the dishes and fuck you.
(7) *Fuck you and wash the dishes. This, along with an observation that the disapproving _fuck_ cannot take adverbial adjuncts (_Fuck communism_ *_on the sofa_/*_tomorrow_/*_gently_.), leads Dong to thinking that _fuck_ is, in fact, a "quasi-verb" (1992:7). Furthermore, Shad (in Zwicky, 1992:34) notes that _fucking_ (disapproving) does not behave like a typical adjective, either, as its insertion is allowed in the contexts in which other adjectives and degree adverbs cannot be used:

(8)  That’s too fucking bad.
(9) *That’s too sad bad.
(10)  That’s too fucking much.
(11) *That’s too very much. Likewise, Bopp (in Zwicky, 1992:61) also proves that the word cannot be used in a relative clause like any typical adjective:

(12)  Turn off that playing radio.
(13)  Turn off that radio which is playing.
(14)  Turn off that fucking radio.
(15)  *Turn off that radio which is fucking. Bopp also points to the fact that though _fucking well_ might seem to be an adverbial phrase, just like quietly, it does not in fact behave like one (in Zwicky, 1992:62):

(16)  John quietly picked the lock.
(17)  Quietly, John picked the lock.
(18)  You fucking well took your time.
(19) *Fucking well, you took your time. To sum it up so far, _fuck_ and _fucking_ (disapproving) have been shown to represent quasi-verbs, quasi-adjectives and quasi-adverbs. However, _fucking_ displays another interesting property; namely, it can be used as an infix morpheme to emphasize the gravity of seemingly any non-monosyllabic word: _kanga-fuckin’-roo_, _Ala-fuckin’-bama_, _fun-fuckin’-tastic_. McCarthy (1982:577-589) notes that the placement of this infix depends on the prosodic structure of a given word: it is not allowed to break the stress pattern of a foot, there is a preference for placing the infixing before the primary stress in a word, etc.
</code></pre></div></div>

<p>The linguistic approach to the word <em>fuck</em> seems to suggest that, although its primary definition revolves around the copulative meaning, it is the disapproving connotation that has been drawing linguists’ attention. Both readings, however, have appeared in books, films and music throughout the centuries.</p>

<p>Even the oldest examples of the usage of <em>fuck</em> may seem somehow ambiguous in its reading. While the meaning of <em>fuck</em> in Flen flyys is clearly copulative (“They are not in heaven, since they fuck the wives of Ely” in Modern English), the meaning behind “O d fucking Abbot” is shrouded in mystery; it might mean that the friar’s engaging in sexual intercourse was frowned upon, or it might be the first attestation of the F-word in its disapproving meaning. Additionally, the d letter may stand for damned, which makes it even more interesting since it suggests that it was damned that was a real taboo at that time (Mohr, 2013b). And one of the founding fathers of the British literature, William Shakespeare, might not have used the word explicitly, but his works do contain numerous references to <em>fuck</em>. In The Merry Wives of Windsor, there is the focative case – a pun on the vocative case; a character in Henry V often uses the word <em>firk</em>, an Elizabethan euphemism for <em>fuck</em>; and there are numerous puns employing the French cognate <em>foutre</em> (Sheidlower, Black, 2009:12-13)</p>

<p>However, while <em>fuck</em> might have appeared or at least been hinted at in the written sources of the Middle Ages and the Enlightenment, its occurrence in the nineteenth- and early twentieth-century literature would still meet with the outbursts of public opinion, culminating in the above-mentioned case Regina vs. Penguin Books. The case itself was the public prosecution of Penguin Books for the publication of Lady Chatterley’s Lover by D.H. Lawrence, a book which contains such fine examples of the usage of the copulative <em>fuck</em> as “Fellows with swaying waists fucking little jazz girls” (in Sheidlower, Black, 2009:124) and “I’m not just my lady’s fucker, after all” (in Sheidlower, Black, 2009:170). Interestingly enough, while Lawrence’s book is often pointed to when discussing profanity, few remember about James Joyce’s Ulysses, which also contains numerous instances of the f-word in both the disapproving (“I’ll wring the neck of any bugger says a word against my fucking king”; in Sheidlower, Black, 2009:182) and copulative sense (“His wife is fucked yes and damn well fucked too”; in Sheidlower, Black, 2009:124).</p>

<p>Shocking though the above-presented examples might seem, modern cinema provides any careful observer with many more instances of the word. The movies directed by Quentin Tarantino have, for a long time, been regarded as “exceptionally vulgar”; but while Reservoir Dogs and Pulp Fiction, indeed, employ plenty of <em>fucks</em> in both readings, they only placed 20th and 21st, respectively, on the list of films using the f-word most often (Sheidlower, Black, 2009:22-23). Pulp Fiction’s use of <em>fuck</em> inspired Dr Sandhya Sundaresan of Leipzig University to claim that swearwords such as <em>fuck</em> play a semantic role similar to please (2014). More recently, Scorsese’s The Wolf of Wall Street made headlines for being the most obscene movie in history, and it indeed seems to be the most profane mainstream/Hollywood movie of all time, with the ballpark figure of 500 <em>fucks</em> as cited by Wickman (2014). This 2013 biographical drama not only uses the f-word in both readings but also pushes the boundaries of its morphological and pragmatic productivity: there are infix <em>fucks</em> (“Oompa-fuckin’-Loompas”), chants (“Fuck you” sung by traders) and neologisms (“Did the Emperor of <em>Fucksville</em> come down from <em>fucking Fucksville</em>?”, italics mine). The frequent use of <em>fuck</em> in the movie is reflected in numerous YouTube videos compiling the utterances into long chains of f-words.</p>

<p>All things considered, it seems fair to say that <strong><em>fuck</em> transcends both time and space</strong>; it has been in constant use since its premiere appearance sometime in the Middle Ages, even if it has always been rejected by social mores. Its high morphological productivity has allowed countless people to express their innermost feelings and desires and stimulated the creative faculties of numerous artists. Finally, <em>fuck</em> has been the focal point of research for a substantial number of academics, allowing for the exploration of why exactly is the employing of the f-word so intrinsic to successful expression in everyday life.</p>

<p>Bibliography:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Hughes, Geoffrey. 2006. An Encyclopedia of Swearing. The Social History of Oaths, Profanity, Foul Language, and Ethnic Slurs in the English-Speaking World. Armonk, New York/London, England.
2. McCarthy, John J. 1982. Prosodic structure and expletive information.
3. Mohr, Melissa. 2013a. Holy Sh*t: A Brief History of Swearing. Oxford University Press, New York.
4. Mohr, Melissa. 2013b. A F*cking Short History of the F-Word. Huffington Post online; last accessed 30/05/15 at http://www.huffingtonpost.com/melissa-mohr/a-fcking-short-history-of_b_3352948.html.
5. Sheidlower, Jesse; Black, Lewis. 2009. The F-Word. Oxford University Press, New York.
6. Sundaresan, Sanhya. 2014. Why Swearing Is Just Like Saying ‘Please’ (Kind Of). Slate online; last accessed 30/05/15 at http://www.slate.com/blogs/lexicon_valley/2014/11/18/swearing_saying_please_two_ways_to_imply_an_extra_attitude_to_what_you_re.html.
7. Wickman, Forrest. 2014. Is Wolf of Wall Street Really the Sweariest Movie of All Time? A Slate Investigation. Slate online; last accessed 30/05/15 at http://www.slate.com/blogs/browbeat/2014/01/07/wolf_of_wall_street_sets_f_word_record_we_counted_every_last_f_bomb_in_the.html
8. Zwicky, Arnold (ed.) 1992. Studies Out in Left Field: Defamatory essays presented to James D. Cawley on his 33rd or 34th birthday. John Benjamins Publishing Company, Amsterdam.
</code></pre></div></div>]]></content><author><name>{&quot;bio&quot;=&gt;&quot;Quidquid agis, prudenter agas et respice finem.&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/mkmozgawa&quot;}]}</name></author><category term="Web" /><category term="Web" /><category term="TIL" /><summary type="html"><![CDATA[Note: this is a 2014 argumentative essay I had to write for my classes. I couldn’t choose the subject I wanted, so I settled on the linguistical aspects of a certain word. I was very contrarian back then. :)]]></summary></entry><entry><title type="html">Cypress + {mochawesome-report-generator, allure}</title><link href="http://mkmozgawa.com/qa/cypress-reports/" rel="alternate" type="text/html" title="Cypress + {mochawesome-report-generator, allure}" /><published>2019-07-14T10:00:00+00:00</published><updated>2019-07-14T10:00:00+00:00</updated><id>http://mkmozgawa.com/qa/cypress-reports</id><content type="html" xml:base="http://mkmozgawa.com/qa/cypress-reports/"><![CDATA[<p>Since I got back to using Cypress I’ve created <a href="https://github.com/mkmozgawa/cypress-reporter">a simple setup for using Cypress and generating test results</a>. Plug it into Jenkins/Bamboo/your favourite CI/CD tool, preferably onto a Docker-based worker with nvm, and snatch your run artifacts from <code class="language-plaintext highlighter-rouge">mochawesome-report/</code>.</p>

<p>Update: If you’re feelin’ fancy, there’s also <a href="https://github.com/mkmozgawa/cypress-allure">a setup with Allure</a> for more colourful test reports that your upper management is going to luv. Snatch them from <code class="language-plaintext highlighter-rouge">allure-report/</code>.</p>]]></content><author><name>{&quot;bio&quot;=&gt;&quot;Quidquid agis, prudenter agas et respice finem.&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/mkmozgawa&quot;}]}</name></author><category term="QA" /><category term="Automation" /><summary type="html"><![CDATA[Since I got back to using Cypress I’ve created a simple setup for using Cypress and generating test results. Plug it into Jenkins/Bamboo/your favourite CI/CD tool, preferably onto a Docker-based worker with nvm, and snatch your run artifacts from mochawesome-report/.]]></summary></entry><entry><title type="html">hub.github.com, or how I have to do one click fewer</title><link href="http://mkmozgawa.com/devops/hub-github-one-click-fewer/" rel="alternate" type="text/html" title="hub.github.com, or how I have to do one click fewer" /><published>2019-07-08T10:00:00+00:00</published><updated>2019-07-08T10:00:00+00:00</updated><id>http://mkmozgawa.com/devops/hub-github-one-click-fewer</id><content type="html" xml:base="http://mkmozgawa.com/devops/hub-github-one-click-fewer/"><![CDATA[<p>HN turns out to be useful from time to time. During the weekend I came across <a href="https://github.com/github/hub/">Hub</a>, a project by GitHub which allows to navigate some of the features of GitHub from the command line level.</p>

<p>Since the workflow of this blog involves pushing to a branch other than master any time I have to do even the smallest of changes (and then putting up a PR and high-fiving myself ;)) it’s always a welcome change when I can manage with one click fewer. I’ve changed my post-commit hook for the project from</p>

<p><code class="language-plaintext highlighter-rouge">pelican content -o output -s pelicanconf.py &amp;&amp; ghp-import output &amp;&amp; git push origin gh-pages</code></p>

<p>to</p>

<p><code class="language-plaintext highlighter-rouge">pelican content -o output -s pelicanconf.py &amp;&amp; ghp-import output &amp;&amp; git push origin gh-pages &amp;&amp; hub pull-request --force -b master -h gh-pages -m "Personal site update"</code></p>

<p>and can now enjoy just clicking the url and hitting <em>Merge</em>. If you can see this post it means it works!</p>

<p>And yes, I know that opening pull requests with the same title every time isn’t ideal…</p>]]></content><author><name>{&quot;bio&quot;=&gt;&quot;Quidquid agis, prudenter agas et respice finem.&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/mkmozgawa&quot;}]}</name></author><category term="Devops" /><category term="Devops" /><category term="TIL" /><summary type="html"><![CDATA[HN turns out to be useful from time to time. During the weekend I came across Hub, a project by GitHub which allows to navigate some of the features of GitHub from the command line level.]]></summary></entry><entry><title type="html">Flask + Tesseract in a Docker container</title><link href="http://mkmozgawa.com/devops/docker-tesseract-flask/" rel="alternate" type="text/html" title="Flask + Tesseract in a Docker container" /><published>2019-05-29T10:00:00+00:00</published><updated>2019-05-29T10:00:00+00:00</updated><id>http://mkmozgawa.com/devops/docker-tesseract-flask</id><content type="html" xml:base="http://mkmozgawa.com/devops/docker-tesseract-flask/"><![CDATA[<p>Imagine you have a Flask app with some sort of a model that depends on Tesseract and you want to Dockerize it (because why not?)</p>

<p>…but then it turns out you also have to have Leptonica installed first,</p>

<p>…but it doesn’t want to work off apt, so you decide to build it from source,</p>

<p>…but then it turns out that the Leptonica’s docs <em>are not</em> up to date but you somehow manage to make it work,</p>

<p>…but then it turns out that the Python:3.7 image is built atop Debian Stretch which has Tesseract 3.x in apt and you want Tesseract 4.x.</p>

<p>This is basically the story of this Dockerfile, sans the tears and sweat. Enjoy!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FROM python:3.7

RUN echo 'deb http://ftp.debian.org/debian stretch-backports main' &gt; /etc/apt/sources.list.d/backports.list \
    &amp;&amp; apt-get dist-upgrade \
    &amp;&amp; apt-get update \
    &amp;&amp; apt-get install -y \
        make \
        git \
        g++ \
        autoconf automake libtool \
        pkg-config \
        libpng-dev \
        libjpeg62-turbo-dev \
        libtiff5-dev \
        zlib1g-dev \
    &amp;&amp; mkdir /install \
    &amp;&amp; cd /install \
    &amp;&amp; git clone https://github.com/DanBloomberg/leptonica \
    &amp;&amp; cd /install/leptonica \
    &amp;&amp; ./autogen.sh \
    &amp;&amp; ./configure \
    &amp;&amp; make \
    &amp;&amp; make install \
    &amp;&amp; apt -t stretch-backports install -y tesseract-ocr  \
    &amp;&amp; apt -t stretch-backports install -y libtesseract-dev

WORKDIR /install
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ /install/app/
WORKDIR /install/app
CMD ["python", "app.py"]

</code></pre></div></div>

<p>PS so you might ask <em>why not use Ubuntu:18.04 instead?</em> to which I say:</p>

<ol>
  <li>bloated</li>
  <li>weird <code class="language-plaintext highlighter-rouge">module Flask cannot be found</code> issues that took me a while to give up on.</li>
</ol>]]></content><author><name>{&quot;bio&quot;=&gt;&quot;Quidquid agis, prudenter agas et respice finem.&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/mkmozgawa&quot;}]}</name></author><category term="Devops" /><category term="Devops" /><summary type="html"><![CDATA[Imagine you have a Flask app with some sort of a model that depends on Tesseract and you want to Dockerize it (because why not?)]]></summary></entry><entry><title type="html">telegren, a simple Telegram bot / joke fuel</title><link href="http://mkmozgawa.com/python/telegren/" rel="alternate" type="text/html" title="telegren, a simple Telegram bot / joke fuel" /><published>2019-05-03T10:00:00+00:00</published><updated>2019-05-03T10:00:00+00:00</updated><id>http://mkmozgawa.com/python/telegren</id><content type="html" xml:base="http://mkmozgawa.com/python/telegren/"><![CDATA[<p>If you use Telegram and have any kind of a running joke / gag with your friends, chances are at some point you’ve come up with the idea of writing your own Telegram bot to push it even further (not that there aren’t any other valid uses for them…but I’m just in for the lolz). I actually decided to implement the thing and here I am after two nice evenings working with <a href="https://github.com/python-telegram-bot/python-telegram-bot">python-telegram-bot</a> which is a really nice wrapper around the Telegram Bot API.</p>

<p>The version 0.1 of telegren is <a href="https://github.com/mkmozgawa/telegren">available on GitHub</a> – all you need to do is to create your own bot by talking to the <a href="https://telegram.me/botfather">BotFather</a> to get your own Bot Token, then run the <code class="language-plaintext highlighter-rouge">setup</code> script and finally the <code class="language-plaintext highlighter-rouge">telegren</code> script wherever you want to host the bot. It’s moderately stable now but if you come across any issues or want to add something extra just hit the <a href="https://github.com/mkmozgawa/telegren/issues">Github issues</a> for the project.</p>

<p>Don’t ask me to explain the name, though. It’s an inside joke.</p>]]></content><author><name>{&quot;bio&quot;=&gt;&quot;Quidquid agis, prudenter agas et respice finem.&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/mkmozgawa&quot;}]}</name></author><category term="Python" /><category term="Python" /><summary type="html"><![CDATA[If you use Telegram and have any kind of a running joke / gag with your friends, chances are at some point you’ve come up with the idea of writing your own Telegram bot to push it even further (not that there aren’t any other valid uses for them…but I’m just in for the lolz). I actually decided to implement the thing and here I am after two nice evenings working with python-telegram-bot which is a really nice wrapper around the Telegram Bot API.]]></summary></entry><entry><title type="html">Readability, revisited</title><link href="http://mkmozgawa.com/python/readability-revisited/" rel="alternate" type="text/html" title="Readability, revisited" /><published>2019-04-18T10:00:00+00:00</published><updated>2019-04-18T10:00:00+00:00</updated><id>http://mkmozgawa.com/python/readability-revisited</id><content type="html" xml:base="http://mkmozgawa.com/python/readability-revisited/"><![CDATA[<p>A long, long time ago (to be exact – in February 2018) I prepared a Shiny app to explore the books of the most popular <a href="https://www.gutenberg.org/catalog/">Project Gutenberg</a> authors. The original app is still available (and bugged) <a href="https://mzgw.shinyapps.io/book-recommendation/">via ShinyApps</a>, and was inspired by <a href="https://blog.vocapouch.com/do-20-pages-of-a-book-gives-you-90-of-its-words-795a405afe70">this article</a>. I first decided to port the unique words plot from Python to R, then utilised some <a href="https://github.com/mkmozgawa/flescher-readability">readability scripts</a> I’d written earlier, and all of a sudden it was all nice and Shiny. (ba-dum-tss) Also, it helped that I needed to create some kind of a project for my uni labs.</p>

<p>I recently digged back into the project due to my incessant interest in the concepts of readability tests and other measurable text features. While the more complex work is still underway (I need some data from humans, never easy), I thought the app itself could use some improvements, and at least one of them could potentially be done without touching the existing codebase. Then there’s the issue with how to calculate the readability score – and stay sane.</p>

<h3 id="dealing-with-the-books-galore">Dealing with the books galore</h3>

<p>If you consider the “Books on this level” plot, there’s way too much data if you go for the B1 level:</p>

<p>This is due to the fact that there are way more books on the A2 - B2 levels than on any else or, putting it in the statistical lingo, the flesch.value vector data is skewed (left-skewed if you look at the flesch.value, and right-skewed if you consider the level). This becomes appearent when you group all the books by the level and count your chickens:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="n">pd</span>
<span class="kn">import</span> <span class="nn">seaborn</span> <span class="k">as</span> <span class="n">sns</span>
<span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="o">%</span><span class="n">matplotlib</span> <span class="n">inline</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">book_list</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">read_csv</span><span class="p">(</span><span class="s">'book_list.csv'</span><span class="p">,</span> <span class="n">sep</span><span class="o">=</span><span class="s">','</span><span class="p">,</span> <span class="n">header</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sns</span><span class="p">.</span><span class="nb">set</span><span class="p">(</span><span class="n">style</span><span class="o">=</span><span class="s">"ticks"</span><span class="p">)</span>
<span class="n">book_list</span><span class="p">.</span><span class="n">head</span><span class="p">()</span>
</code></pre></div></div>

<div>
<style scoped="">
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>gutenberg.id</th>
      <th>title</th>
      <th>author</th>
      <th>gutenberg.author.id</th>
      <th>gutenberg.bookshelf</th>
      <th>flesch.value</th>
      <th>flesch.grade</th>
      <th>level</th>
      <th>average.goodreads.rating</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>17621</td>
      <td>One Day More: A Play In One Act</td>
      <td>Conrad, Joseph</td>
      <td>125</td>
      <td>One Act Plays</td>
      <td>90.418363</td>
      <td>5</td>
      <td>A1</td>
      <td>2.90</td>
    </tr>
    <tr>
      <th>1</th>
      <td>28554</td>
      <td>Beyond Lies the Wub</td>
      <td>Dick, Philip K.</td>
      <td>33399</td>
      <td>Science Fiction</td>
      <td>90.431191</td>
      <td>5</td>
      <td>A1</td>
      <td>3.81</td>
    </tr>
    <tr>
      <th>2</th>
      <td>28644</td>
      <td>Beyond the Door</td>
      <td>Dick, Philip K.</td>
      <td>33399</td>
      <td>Science Fiction</td>
      <td>93.401235</td>
      <td>5</td>
      <td>A1</td>
      <td>3.06</td>
    </tr>
    <tr>
      <th>3</th>
      <td>32032</td>
      <td>Second Variety</td>
      <td>Dick, Philip K.</td>
      <td>33399</td>
      <td>Science Fiction</td>
      <td>91.175281</td>
      <td>5</td>
      <td>A1</td>
      <td>3.96</td>
    </tr>
    <tr>
      <th>4</th>
      <td>41562</td>
      <td>The Hanging Stranger</td>
      <td>Dick, Philip K.</td>
      <td>33399</td>
      <td>Science Fiction</td>
      <td>91.919921</td>
      <td>5</td>
      <td>A1</td>
      <td>3.90</td>
    </tr>
  </tbody>
</table>
</div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">book_list</span><span class="p">.</span><span class="n">groupby</span><span class="p">(</span><span class="s">'level'</span><span class="p">).</span><span class="n">size</span><span class="p">()</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>level
A1     14
A2    155
B1    187
B2     90
C1     22
C2     10
dtype: int64
</code></pre></div></div>

<p>Another thing that becomes apparent when you plot the flesch.value (<a href="https://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_tests">click for more info on that</a>) against the average.goodreads.rating (pulled from the <a href="https://www.goodreads.com/">Goodreads</a> <a href="https://www.goodreads.com/api">API</a>) is that it’s also left-skewed (right-skewed on the plot below because of the nature of the Y axis).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sns</span><span class="p">.</span><span class="n">jointplot</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="s">'flesch.value'</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="s">'average.goodreads.rating'</span><span class="p">,</span><span class="n">data</span><span class="o">=</span><span class="n">book_list</span><span class="p">,</span><span class="n">kind</span><span class="o">=</span><span class="s">'hex'</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;seaborn.axisgrid.JointGrid at 0x7fd0b6787860&gt;
</code></pre></div></div>

<p>What I get from this is that, essentially, the rating may be a useless metric (“every product and business on earth is assigned a numerical rating from 1 to 5 on some site, but everything ends up at 3.7 +/- 0.1, so it means nothing, but we just keep doing it because our first goal is quantification as such, accuracy and precision be damned” to quote <a href="https://twitter.com/garybernhardt/status/1117664396757520384">Gary Bernhardt</a>). But I promised to myself not to dwell on the past and so I’m not changing the logic of the app, and I shall just pick fewer books for each grade level to make it look nicer on the actual plot. Also, these are the cream of the crop volumes, so maybe they’re just really good on average? Or maybe people are afraid to rate them too low?</p>

<p>But how do I pick only some of the books in such a way that I get a sample of each readability level (and Goodreads rating)? Given the plot above, there are some really hot regions (think 3.7 stars, 78 on the Flesch-Kincaid scale) but the rest could potentially stay as it is. As this is more for the demonstration purposes and not some actual sample-sized statistical research, the simplest way would be to first bucket the books in an even more fine manner (90-88 flesch.value, 88-86 flesch.value, and so on) and then from each such bucket, if there is more than one book, choose on the basis of their Goodreads rating (how varied it is).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">bins_flesch</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linspace</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="n">num</span><span class="o">=</span><span class="mi">101</span><span class="p">)</span>
<span class="n">cuts_flesch</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">cut</span><span class="p">(</span><span class="n">book_list</span><span class="p">[</span><span class="s">'flesch.value'</span><span class="p">],</span> <span class="n">bins_flesch</span><span class="p">)</span>
<span class="n">bins_rating</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linspace</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="n">num</span><span class="o">=</span><span class="mi">11</span><span class="p">)</span>
<span class="n">cuts_rating</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">cut</span><span class="p">(</span><span class="n">book_list</span><span class="p">[</span><span class="s">'average.goodreads.rating'</span><span class="p">],</span> <span class="n">bins_rating</span><span class="p">)</span>
<span class="n">groups</span> <span class="o">=</span> <span class="n">book_list</span><span class="p">.</span><span class="n">groupby</span><span class="p">(</span><span class="n">by</span><span class="o">=</span><span class="p">[</span><span class="n">cuts_flesch</span><span class="p">,</span> <span class="n">cuts_rating</span><span class="p">])</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">groups</span><span class="p">.</span><span class="n">size</span><span class="p">().</span><span class="n">head</span><span class="p">()</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flesch.value  average.goodreads.rating
(26.0, 27.0]  (3.0, 3.5]                  1
(43.0, 44.0]  (3.5, 4.0]                  2
(44.0, 45.0]  (3.5, 4.0]                  1
(46.0, 47.0]  (3.5, 4.0]                  1
(47.0, 48.0]  (2.5, 3.0]                  1
dtype: int64
</code></pre></div></div>

<p>Let’s assume that for each of these buckets, the goal would be to have a single element in it, and this element would be as close to the average of that particular bucket as possible. Note that this is going to be a pretty small data frame (I’m starting with &lt;500 records) so performance isn’t an issue here. Normally you’d want to pre-allocate indices, and stuff like that.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">res_df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="nb">list</span><span class="p">(</span><span class="n">book_list</span><span class="p">.</span><span class="n">columns</span><span class="p">.</span><span class="n">values</span><span class="p">))</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># https://stackoverflow.com/questions/2566412/find-nearest-value-in-numpy-array
</span><span class="k">def</span> <span class="nf">find_nearest</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
    <span class="n">array</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">asarray</span><span class="p">(</span><span class="n">array</span><span class="p">)</span>
    <span class="n">idx</span> <span class="o">=</span> <span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="nb">abs</span><span class="p">(</span><span class="n">array</span> <span class="o">-</span> <span class="n">value</span><span class="p">)).</span><span class="n">argmin</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">array</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">els</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">groups</span><span class="p">:</span>
    <span class="n">g</span> <span class="o">=</span> <span class="n">groups</span><span class="p">.</span><span class="n">get_group</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
    <span class="n">g_mean</span> <span class="o">=</span> <span class="n">g</span><span class="p">[</span><span class="s">'average.goodreads.rating'</span><span class="p">].</span><span class="n">mean</span><span class="p">()</span>
    <span class="n">g_mean_choice</span> <span class="o">=</span> <span class="n">find_nearest</span><span class="p">(</span><span class="n">g</span><span class="p">[</span><span class="s">'average.goodreads.rating'</span><span class="p">],</span> <span class="n">g_mean</span><span class="p">)</span>
    <span class="n">g_choice</span> <span class="o">=</span> <span class="n">g</span><span class="p">[</span><span class="n">g</span><span class="p">[</span><span class="s">'average.goodreads.rating'</span><span class="p">]</span> <span class="o">==</span> <span class="n">g_mean_choice</span><span class="p">]</span>
    <span class="n">f</span> <span class="o">=</span> <span class="p">[</span><span class="n">res_df</span><span class="p">,</span> <span class="n">g_choice</span><span class="p">]</span>
    <span class="n">res_df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">concat</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">res_df</span><span class="p">.</span><span class="n">head</span><span class="p">()</span>
</code></pre></div></div>

<div>
<style scoped="">
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>gutenberg.id</th>
      <th>title</th>
      <th>author</th>
      <th>gutenberg.author.id</th>
      <th>gutenberg.bookshelf</th>
      <th>flesch.value</th>
      <th>flesch.grade</th>
      <th>level</th>
      <th>average.goodreads.rating</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>469</th>
      <td>15</td>
      <td>Moby Dick</td>
      <td>Melville, Herman</td>
      <td>9</td>
      <td>Best Books Ever Listings/Adventure</td>
      <td>26.599868</td>
      <td>&gt;= 16 (college graduate)</td>
      <td>C2</td>
      <td>3.47</td>
    </tr>
    <tr>
      <th>471</th>
      <td>16833</td>
      <td>Auguste Comte and Positivism</td>
      <td>Mill, John Stuart</td>
      <td>1705</td>
      <td>Philosophy</td>
      <td>43.411004</td>
      <td>&gt;= 13 (college)</td>
      <td>C2</td>
      <td>3.65</td>
    </tr>
    <tr>
      <th>476</th>
      <td>18188</td>
      <td>Homer and Classical Philology</td>
      <td>Nietzsche, Friedrich Wilhelm</td>
      <td>779</td>
      <td>Philosophy</td>
      <td>44.382562</td>
      <td>&gt;= 13 (college)</td>
      <td>C2</td>
      <td>3.84</td>
    </tr>
    <tr>
      <th>468</th>
      <td>2009</td>
      <td>The Origin of Species by Means of Natural Sele...</td>
      <td>Darwin, Charles</td>
      <td>485</td>
      <td>Harvard Classics/Biology</td>
      <td>46.535282</td>
      <td>&gt;= 13 (college)</td>
      <td>C2</td>
      <td>3.97</td>
    </tr>
    <tr>
      <th>470</th>
      <td>35421</td>
      <td>A System of Logic: Ratiocinative and Inductive...</td>
      <td>Mill, John Stuart</td>
      <td>1705</td>
      <td>Philosophy</td>
      <td>47.673194</td>
      <td>&gt;= 13 (college)</td>
      <td>C2</td>
      <td>2.82</td>
    </tr>
  </tbody>
</table>
</div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cuts_flesch_2</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">cut</span><span class="p">(</span><span class="n">res_df</span><span class="p">[</span><span class="s">'flesch.value'</span><span class="p">],</span> <span class="n">bins_flesch</span><span class="p">)</span>
<span class="n">cuts_rating_2</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">cut</span><span class="p">(</span><span class="n">res_df</span><span class="p">[</span><span class="s">'average.goodreads.rating'</span><span class="p">],</span> <span class="n">bins_rating</span><span class="p">)</span>
<span class="n">groups</span> <span class="o">=</span> <span class="n">res_df</span><span class="p">.</span><span class="n">groupby</span><span class="p">(</span><span class="n">by</span><span class="o">=</span><span class="p">[</span><span class="n">cuts_flesch_2</span><span class="p">,</span> <span class="n">cuts_rating_2</span><span class="p">])</span>
<span class="n">groups</span><span class="p">.</span><span class="n">size</span><span class="p">().</span><span class="n">head</span><span class="p">()</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flesch.value  average.goodreads.rating
(26.0, 27.0]  (3.0, 3.5]                  1
(43.0, 44.0]  (3.5, 4.0]                  1
(44.0, 45.0]  (3.5, 4.0]                  1
(46.0, 47.0]  (3.5, 4.0]                  1
(47.0, 48.0]  (2.5, 3.0]                  1
dtype: int64
</code></pre></div></div>

<p>Now there should be just 1 book per bucket. The final result is 142 books (<code class="language-plaintext highlighter-rouge">res_df.shape[0]</code>). Plugging this into the existing app gives us the following result:</p>

<h3 id="calculating-readability">Calculating readability</h3>

<p>Another thing I wanted to touch upon is the readability score calculation – which is pretty hard. To calculate the Flesch-Kincaid readability score you need to correctly parse the text into sentences, words and syllables. The raw implementation of the formula would be as follows:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">calculate_flesch_score</span><span class="p">(</span><span class="n">no_of_sentences</span><span class="p">,</span> <span class="n">no_of_words</span><span class="p">,</span> <span class="n">no_of_syllables</span><span class="p">):</span>
    <span class="k">return</span> <span class="mf">206.835</span> <span class="o">-</span> <span class="mf">1.015</span> <span class="o">*</span> <span class="p">(</span><span class="n">no_of_words</span> <span class="o">/</span> <span class="n">no_of_sentences</span><span class="p">)</span> <span class="o">-</span> <span class="mf">84.6</span> <span class="o">*</span> <span class="p">(</span><span class="n">no_of_syllables</span> <span class="o">/</span> <span class="n">no_of_words</span><span class="p">)</span>
</code></pre></div></div>

<p>Even leaving aside the issue of what is a word vs what is a sentence (take the <a href="https://en.wikipedia.org/wiki/Inuit_languages#Morphology_and_syntax">Inuit languages</a> with their extreme agglutination), determining the syllable count of a text is not that easy. You can use <a href="http://www.speech.cs.cmu.edu/cgi-bin/cmudict">CMUDict</a>, which is conveniently available via the nltk package, but there are a lot of words missing from there. Then there’s <a href="https://pyphen.org/">Pyphen</a> but it’s pretty error-prone. I also came across the <a href="https://pypi.org/project/readability/">Readability package</a> which also has its specific requirements pertaining to the pre-processing of the text.</p>

<p>Let’s see how all these perform on the benchmark of “Heart of Darkness”, one of the rare cases of the books I hate so much, any time I see a copy of it somewhere I want to throw it out of the closest window:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">nltk</span>
<span class="kn">from</span> <span class="nn">nltk</span> <span class="kn">import</span> <span class="n">word_tokenize</span><span class="p">,</span> <span class="n">sent_tokenize</span>
<span class="kn">from</span> <span class="nn">nltk.corpus</span> <span class="kn">import</span> <span class="n">cmudict</span>
<span class="kn">import</span> <span class="nn">readability</span>
<span class="kn">import</span> <span class="nn">pyphen</span>
<span class="kn">import</span> <span class="nn">re</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s">'heart_of_darkness.txt'</span><span class="p">)</span>
<span class="n">raw</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">raw</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="s">'The Nellie'</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>646
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">raw</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="s">'End of the Project Gutenberg EBook of Heart of Darkness, by Joseph Conrad'</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>211098
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">raw</span> <span class="o">=</span> <span class="n">raw</span><span class="p">[</span><span class="mi">646</span><span class="p">:</span><span class="mi">211098</span><span class="p">]</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">lower_parsed</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="n">sub</span><span class="p">(</span><span class="s">'[^0-9a-zA-Z]+'</span><span class="p">,</span> <span class="s">' '</span><span class="p">,</span> <span class="n">raw</span><span class="p">).</span><span class="n">lower</span><span class="p">()</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">words</span> <span class="o">=</span> <span class="n">word_tokenize</span><span class="p">(</span><span class="n">lower_parsed</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">len</span><span class="p">(</span><span class="n">words</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>39064
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">unique_words</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">words</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">len</span><span class="p">(</span><span class="n">unique_words</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>5727
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sentences</span> <span class="o">=</span> <span class="n">sent_tokenize</span><span class="p">(</span><span class="n">raw</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">len</span><span class="p">(</span><span class="n">sentences</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2417
</code></pre></div></div>

<p>This gives me 39064 words (5727 of them unique) and 2417 sentences. Let’s see how many of these words can be found in the CMUdict:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cmudict</span> <span class="o">=</span> <span class="n">cmudict</span><span class="p">.</span><span class="nb">dict</span><span class="p">()</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">no_dict</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">unique_words</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">word</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">cmudict</span><span class="p">.</span><span class="n">keys</span><span class="p">():</span>
        <span class="n">no_dict</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">word</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">len</span><span class="p">(</span><span class="n">no_dict</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>359
</code></pre></div></div>

<p>This means that 359 unique words are not accounted for in the CMUdict. Let’s see how often they appear in the whole book:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">c</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">unique_word</span> <span class="ow">in</span> <span class="n">no_dict</span><span class="p">:</span>
    <span class="k">for</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">words</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">unique_word</span> <span class="o">==</span> <span class="n">word</span><span class="p">:</span>
            <span class="n">c</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">print</span><span class="p">(</span><span class="n">c</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>507
</code></pre></div></div>

<p>That’s a total of 507 words out of 39064, or ~1.3% of all the words in the book. Since this is a quick and dirty solution let’s say I can live with this fact, and move on. The plan is as follows:</p>
<ol>
  <li>create a lookup dictionary based off the stress patterns in the CMUdict,</li>
  <li>calculate the number of syllables in the whole book based off the lookup dictionary.</li>
</ol>

<p>The join-fu and error handling is due to the CMUdict sometimes having two different pronunciation of the same word (obviously, but not useful to me at that moment).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">lookup_dict</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">reg</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="nb">compile</span><span class="p">(</span><span class="sa">r</span><span class="s">'[^\d]+'</span><span class="p">)</span>

<span class="k">for</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">unique_words</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">word</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">cmudict</span><span class="p">.</span><span class="n">keys</span><span class="p">():</span>
        <span class="n">lookup_dict</span><span class="p">[</span><span class="n">word</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">lookup_dict</span><span class="p">[</span><span class="n">word</span><span class="p">]</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">reg</span><span class="p">.</span><span class="n">sub</span><span class="p">(</span><span class="s">''</span><span class="p">,</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">cmudict</span><span class="p">[</span><span class="n">word</span><span class="p">][</span><span class="mi">0</span><span class="p">])))</span>
        <span class="k">except</span> <span class="nb">TypeError</span><span class="p">:</span>
            <span class="n">lookup_dict</span><span class="p">[</span><span class="n">word</span><span class="p">]</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">reg</span><span class="p">.</span><span class="n">sub</span><span class="p">(</span><span class="s">''</span><span class="p">,</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">cmudict</span><span class="p">[</span><span class="n">word</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">])))</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">n_syl</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">words</span><span class="p">:</span>
    <span class="n">n_syl</span> <span class="o">+=</span> <span class="n">lookup_dict</span><span class="p">[</span><span class="n">word</span><span class="p">]</span>
<span class="k">print</span><span class="p">(</span><span class="n">n_syl</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>52487
</code></pre></div></div>

<p>52487 sounds a bit low, but let’s see how this translates into the score:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">print</span><span class="p">(</span><span class="n">calculate_flesch_score</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">sentences</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">words</span><span class="p">),</span> <span class="n">n_syl</span><span class="p">))</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>76.76050250923326
</code></pre></div></div>

<p>Another implementation, now fetching the syllable count with Pyphen:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">pyphen</span>
<span class="n">dic</span> <span class="o">=</span> <span class="n">pyphen</span><span class="p">.</span><span class="n">Pyphen</span><span class="p">(</span><span class="n">lang</span><span class="o">=</span><span class="s">'en_UK'</span><span class="p">)</span> <span class="c1"># In reality, Conrad was a Pole.
</span></code></pre></div></div>

<p>Pyphen returns a hyphenated string; I’m going to count these hyphens and add 1 to get the predicted number of syllables.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">lookup_dict</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">reg</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="nb">compile</span><span class="p">(</span><span class="sa">r</span><span class="s">'[^-]+'</span><span class="p">)</span>

<span class="k">for</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">unique_words</span><span class="p">:</span>
    <span class="n">lookup_dict</span><span class="p">[</span><span class="n">word</span><span class="p">]</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">reg</span><span class="p">.</span><span class="n">sub</span><span class="p">(</span><span class="s">''</span><span class="p">,</span> <span class="n">word</span><span class="p">))</span><span class="o">+</span><span class="mi">1</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">n_syl</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">words</span><span class="p">:</span>
    <span class="n">n_syl</span> <span class="o">+=</span> <span class="n">lookup_dict</span><span class="p">[</span><span class="n">word</span><span class="p">]</span>
<span class="k">print</span><span class="p">(</span><span class="n">n_syl</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>39064
</code></pre></div></div>

<p>This gives me 39064 syllables, which is even more dubious.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">print</span><span class="p">(</span><span class="n">calculate_flesch_score</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">sentences</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">words</span><span class="p">),</span> <span class="n">n_syl</span><span class="p">))</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>105.8303827058337
</code></pre></div></div>

<p>This makes no sense whatsover. A score of 105 would be for sentences in the style of “I see you.” (essentially, a lot of single-syllable words in really short sentences), and this book is far from that. Let’s see the final implementation via the Readability package:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">readability</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sentences</span> <span class="o">=</span> <span class="p">[</span><span class="n">s</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">,</span> <span class="s">' '</span><span class="p">)</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">sentences</span><span class="p">]</span>
<span class="n">text</span> <span class="o">=</span> <span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">sentences</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">results</span> <span class="o">=</span> <span class="n">readability</span><span class="p">.</span><span class="n">getmeasures</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">lang</span><span class="o">=</span><span class="s">'en'</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">print</span><span class="p">(</span><span class="n">results</span><span class="p">[</span><span class="s">'readability grades'</span><span class="p">][</span><span class="s">'FleschReadingEase'</span><span class="p">])</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>82.8232249133162
</code></pre></div></div>

<p>The score of almost 83 again seems a bit too high but it’s at least close to the one calculated with the CMU dictionary. Finally, let’s see how the treetagger-based implementation performed last year:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">book_list</span><span class="p">[</span><span class="n">book_list</span><span class="p">[</span><span class="s">'title'</span><span class="p">]</span><span class="o">==</span><span class="s">'Heart of Darkness'</span><span class="p">]</span>
</code></pre></div></div>

<div>
<style scoped="">
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>gutenberg.id</th>
      <th>title</th>
      <th>author</th>
      <th>gutenberg.author.id</th>
      <th>gutenberg.bookshelf</th>
      <th>flesch.value</th>
      <th>flesch.grade</th>
      <th>level</th>
      <th>average.goodreads.rating</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>219</th>
      <td>219</td>
      <td>Heart of Darkness</td>
      <td>Conrad, Joseph</td>
      <td>125</td>
      <td>Best Books Ever Listings/Movie Books</td>
      <td>77.935178</td>
      <td>7</td>
      <td>B1</td>
      <td>3.42</td>
    </tr>
  </tbody>
</table>
</div>

<p>The treetagger version has a similar score of ~78. Maybe I just really hate this book…</p>]]></content><author><name>{&quot;bio&quot;=&gt;&quot;Quidquid agis, prudenter agas et respice finem.&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Website&quot;, &quot;icon&quot;=&gt;&quot;fas fa-fw fa-link&quot;, &quot;url&quot;=&gt;&quot;https://&quot;}, {&quot;label&quot;=&gt;&quot;GitHub&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/mkmozgawa&quot;}]}</name></author><category term="Python" /><category term="NLP" /><category term="Python" /><summary type="html"><![CDATA[A long, long time ago (to be exact – in February 2018) I prepared a Shiny app to explore the books of the most popular Project Gutenberg authors. The original app is still available (and bugged) via ShinyApps, and was inspired by this article. I first decided to port the unique words plot from Python to R, then utilised some readability scripts I’d written earlier, and all of a sudden it was all nice and Shiny. (ba-dum-tss) Also, it helped that I needed to create some kind of a project for my uni labs.]]></summary></entry></feed>