NML Says

Open Source Development 10 - Docker and Open Source

Refs for Todays Session

Model Solutions Previous Lessons

We shall illustrate the work you/we did in session 9. It is all compiled into a repository at Codeberg, please find it at https://codeberg.org/arosano/library9

At the time of writing, we did not yet receive any solutions to the exercises, so we bring you our attempt.

This solution may be found and/or cloned from library9

Example 1. README.md
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Library 9

Copyright (c) 2025 Niels Müller Larsen

## Content 
Functions and test of them in relation to 
classical encryption ciphers and language
analysis.

## Licensed under the BSD-3 license. Please
refer to the LICENSE document here.
Example 2. LICENSE
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
BSD-3 License

Copyright © 2025 Niels Müller Larsen

Redistribution and use in source and binary forms, with
or without modification, are permitted provided that the
following conditions are met:

1.  Redistributions of source code must retain the above 
    copyright notice, this list of conditions and the 
    following disclaimer.

2.  Redistributions in binary form must reproduce the 
    above copyright notice, this list of conditions and
    the following disclaimer in the documentation and/or
    other materials provided with the distribution.

3.  Neither the name of the copyright holder nor the names
    of its contributors may be used to endorse or promote
    products derived from this software without specific 
    prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
“AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
Example 3. Compilation of Our Work - testsuite.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
'''
    testsuite.py
    
    Copyright (c) 2025 Niels Müller Larsen
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
'''

import unittest
from lib9 import *

class Testing(unittest.TestCase):
    '''
    OSD.9.1
    Copyright (c) 2025 Niels Müller Larsen
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
    '''
    def test_vigenE0(self):
        self.assertEqual(vigenE('DUH', 'THEY DRINK THE TEA'), 'WBLBXYLHRWBLWYH')

    '''
    OSD.9.2
    Copyright (c) 2025 Niels Müller Larsen
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
    '''
    def test_vigenD0(self):
        self.assertEqual(vigenD('DUH', 'WBLBXYLHRWBLWYH'), 'THEYDRINKTHETEA')

    '''
    OSD.9.3
    Copyright (c) 2025 Niels Müller Larsen
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
    '''
    def test_bigram0(self):
        s = 'The quick brown fox jumps over the lazy dog'
        d2 = {
            'AZ': 1,
            'BR': 1,
            'CK': 1,
            'DO': 1,
            'EL': 1,
            'EQ': 1,
            'ER': 1,
            'FO': 1,
            'HE': 2,
            'IC': 1,
            'JU': 1,
            'KB': 1,
            'LA': 1,
            'MP': 1,
            'NF': 1,
            'OG': 1,
            'OV': 1,
            'OW': 1,
            'OX': 1,
            'PS': 1,
            'QU': 1,
            'RO': 1,
            'RT': 1,
            'SO': 1,
            'TH': 2,
            'UI': 1,
            'UM': 1,
            'VE': 1,
            'WN': 1,
            'XJ': 1,
            'YD': 1,
            'ZY': 1
        }
        self.assertEqual(bigram(s), d2)

    '''
    OSD.9.4
    Copyright (c) 2025 Niels Müller Larsen
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
    '''
    def test_trigram0(self):
        s = 'The quick brown fox jumps over the lazy dog'
        d3 = {
            'AZY': 1,
            'BRO': 1,
            'CKB': 1,
            'DOG': 1,
            'ELA': 1,
            'EQU': 1,
            'ERT': 1,
            'FOX': 1,
            'HEL': 1,
            'HEQ': 1,
            'ICK': 1,
            'JUM': 1,
            'KBR': 1,
            'LAZ': 1,
            'MPS': 1,
            'NFO': 1,
            'OVE': 1,
            'OWN': 1,
            'OXJ': 1,
            'PSO': 1,
            'QUI': 1,
            'ROW': 1,
            'RTH': 1,
            'SOV': 1,
            'THE': 2,
            'UIC': 1,
            'UMP': 1,
            'VER': 1,
            'VER': 1,
            'WNF': 1,
            'XJU': 1,
            'YDO': 1,
            'ZYD': 1
        }
        self.assertEqual(trigram(s), d3)

    '''
    OSD.9.5
    Copyright (c) 2025 Niels Müller Larsen
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
    '''
    def test_n_gram0(self):
        s = 'The quick brown fox jumps over the lazy dog'
        n = 3
        dn = {
            'AZY': 1,
            'BRO': 1,
            'CKB': 1,
            'DOG': 1,
            'ELA': 1,
            'EQU': 1,
            'ERT': 1,
            'FOX': 1,
            'HEL': 1,
            'HEQ': 1,
            'ICK': 1,
            'JUM': 1,
            'KBR': 1,
            'LAZ': 1,
            'MPS': 1,
            'NFO': 1,
            'OVE': 1,
            'OWN': 1,
            'OXJ': 1,
            'PSO': 1,
            'QUI': 1,
            'ROW': 1,
            'RTH': 1,
            'SOV': 1,
            'THE': 2,
            'UIC': 1,
            'UMP': 1,
            'VER': 1,
            'VER': 1,
            'WNF': 1,
            'XJU': 1,
            'YDO': 1,
            'ZYD': 1
        }
        self.assertEqual(n_gram(s, n), dn)
    
	def test_n_gram1(self):
        s = 'The quick brown fox jumps over the lazy dog'
        n = 3
        self.assertEqual(n_gram(s, n), trigram(s))
    def test_n_gram2(self):
        s = 'The quick brown fox jumps over the lazy dog'
        n = 2
        self.assertEqual(n_gram(s, n), bigram(s))
    def test_n_gram3(self):
        s = 'The quick brown fox jumps over the lazy dog'
        n = 1
        self.assertEqual(n_gram(s, n), unigram(s))


if __name__ == '__main__':
    unittest.main()
Example 4. Compilation of Our Work - lib9.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
'''
    Copyright © 2025 Niels Müller Larsen
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
'''

import re

def cryptoPrep(s):
    s = s.upper()
    s = re.sub(r'[^\w]', '', s)
    return s

alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

def vig(key, s, enc=True):
    '''
        Encrypts/decrypts a string with the Vigenére Cipher
        s is a string
        key is an alphanumeric key (string)
        returns a string

        key mustn't be upper case because ALPHABET already is.
    '''
    s = cryptoPrep(s)
    out = ''

    ki = 0
    for char in s:
        if char not in alphabet: 
            out += char 
            continue
        keychar = key[ki % len(key)]
        ki += 1
        j = alphabet.index(char)
        if enc:
            j += alphabet.index(keychar)
        else:
            j -= alphabet.index(keychar)
        j %= len(alphabet)
        out += alphabet[j]
    return out

'''
    OSD.9.1
'''
def vigenE(key, s):
    '''
        Encrypts a string with the Vigenére Cipher
    '''
    return vig(key, s, True)

'''
    OSD.9.2
'''
def vigenD(key, s):
    '''
        Decrypts a string with the Vigenére Cipher
    '''
    return vig(key, s, False)


'''
    OSD.9.3a 
    Copyright © 2025 NML
    Licensed under the BSD-3 License,
    Please refer to the LICENSE document.
'''
def unigram(s):
    '''
        receives a string of any length
        returns a dictionary of unigram frequencies
    '''
    s = cryptoPrep(s)
    dic = {}

    for c in s:
        if c in dic:
            dic[c] = dic[c] + 1
        else:
            dic[c] = 1
    return dic

'''
    OSD.9.3
'''
def bigram(s):
    s = cryptoPrep(s)
    dic = {}
    for i in range(len(s)-1):
        item = s[i:i+2]

        if item in dic:
            dic[item] += 1
        else:
            dic[item] = 1

    return dic

'''
    OSD.9.4
'''
def trigram(s):
    s = cryptoPrep(s)
    dic = {}
    for i in range(len(s)-2):
        item = s[i:i+3]

        if item in dic:
            dic[item] += 1
        else:
            dic[item] = 1

    return dic

'''
    OSD.9.5
'''
def n_gram(s, n):
    s = cryptoPrep(s)
    dic = {}
    for i in range(len(s)-(n-1)):
        item = s[i:i+n]

        if item in dic:
            dic[item] += 1
        else:
            dic[item] = 1
    return dic



'''
    OSD.8.14 updated
'''
def caesarE(s, key):
    '''
        eh?
    '''
    s = cryptoPrep(s)
    ci = ''
    key = key % len(alphabet)
    if key == 0:
        return s
    for ch in s:
        ci += alpha[(alphabet.index(ch) + key) % len(alphabet)]
    return ci

'''
    OSD.8.15
'''
def caesarD(s, key):
    '''
        eh?
    '''
    return caesarE(s, -key)

Docker in an Open Source Context

Docker

Exercises

Exercise OSD.A.0

Take your solution (or ours) to Exercise OSD.9.5 and write a function that takes the output from `n_gram(s, n) and prints it as %-frequencies:

'a': 2  50%
'h': 2  50%

given a call `n_gram(‘haha’, 1).

You MUST make the solution in a Docker container made from a Docker image built from this Dockerfile

1
2
3
4
5
FROM alpine
RUN apk update
RUN apk add git
RUN apk add python3
RUN apk add nano

after you have built the image, solve the exercise in a running container. When done, upload it to Docker Hubhttps://hub.docker.com/) and send the name of the image to us at nmla@iba.dk