---
# System prepended metadata

title: AI POET HackMD
tags: [' Python', ' AIPoet']

---

---
title: AI POET HackMD
tags: AIPoet, Python
---

<style>
    
    :root {
      --pri:#990000;
      --prid:#800000;
      --sec:#9E9E9E;
      --secd:#353535;
      --white: #f0efee;
    }
    
    .markdown-body h1,
    .markdown-body h2,
    .markdown-body h3,
    .markdown-body h4,
    .markdown-body h5,
    .markdown-body h6 {
        color: var(--prid);
        /*
        background-color: var(--prid);
        color: var(--white);
        padding: 10px;
        border-radius: 5px;
        */
    }

    .markdown-body code {
        background-color: #f7f7f7;
    }


    .hljs-keyword {
        color: var(--pri);
    }

    summary {/*收折文字標題*/
        color: var(--prid);
        font-weight: 500;
    }

    .community-button {
    color: var(--prid);
    }
    .community-button:hover {
    color: var(--pri);
    background-color: #eae8e6;
    }

    #comment-app .open-comments {
        background: transparent;
    }
    .ui-comment-app .open-comments .btn.ui-open-comments {
    color: var(--prid);
    }
    .ui-comment-app .open-comments .btn.ui-open-comments:hover {
    color: var(--pri);
    background-color: #eae8e6;
    }
    
    a {
        color: var(--prid);
        text-decoration:underline;
    }
    
    a:hover {
        color: var(--sec);
    }
    
    ::selection {
        color: var(--white);
        background: var(--prid);
    }
    
    .alert-danger {
        background-color: #f7f7f7;
    }


</style>


# Resources
:::danger
[Colab Notebook](https://colab.research.google.com/drive/17fkKcBgKhKdgfqiCjYwHEBpQAPRvReBs?usp=sharing)
[Google Drive Folder](https://drive.google.com/drive/folders/1McPx3LSrlEHH8ySdn6PwN9bKUAI9jKPt?usp=share_link)
[Lecture Slides](https://www.canva.com/design/DAFjDuH8YcA/D5u1tp9YU8UoHigZKl1Nbw/view?utm_content=DAFjDuH8YcA&utm_campaign=designshare&utm_medium=link&utm_source=homepage_design_menu#1)
[AI Poet Website](https://ai-poet.de.r.appspot.com/) *(deactivated bc i aint gettin paid)*
:::

# Code Explanation
**AI POET** is a rather big program that includes complex and repetitive tasks, so the code is actually made up of several helper functions and a main function. We'll look at the helper functions first, then learn about the main one!

<details>
<summary>
    What is a function?
</summary>

A function is a block of code that does certain things when called. for example, a function that adds up two numbers would look like:

```python
def add(num1, num2):
  return num1 + num2
```

the first line defines ```add()``` the function as the following code, with two inputs that will be referred to as "num1" and "num2" in the function code block. The second line returns the sum of the two input values.

```python
add(2, 3)
```

Now we can call the function in other parts of the program like this.
</details>

## The Helper Functions
```python
def wordListSum(wordList):
    sum = 0
    for value in wordList.values():
        sum += value
    return sum
```
Given a dictionary, the ```wordListSum(wordList)```  function returns the sum of all values in that dictionary.

<details>
<summary>
    What is a dictionary?
</summary>

Dictionary is an unordered set of key:value pairs, with the requirement that the keys are unique (within one dictionary). 

</details>
<p></pp>

```python
def retrieveRandomWord(wordList):
    randIndex = randint(1, wordListSum(wordList))
    for word, value in wordList.items():
        randIndex -= value
        if randIndex <= 0:
            return word
```
The function retrieves a key from the dictionary, with their values as the probabilities to be selected.

<details>
<summary>
    How is that implemented?
</summary>
The function first generates a random number called randIndex within the range of 1~(the sum of all values in the dictionary). It then iterates through the dictionary, substracts the value of the current dictionary element from randIndex until it's zero or smaller. After the iteration the key of the current dictionary element will be returned.
</details>
<p></p>

```python
def buildWordDict(fn):
    d = {}
    infile =  open(fn, "r")
    body = infile.read()
    infile.close()
    addWord(body, d)
    return d
```
Given a file name, ```buildWordDict(fn)``` finds the file, reads it, and calls ```addWord(words, wordDict)``` for help!

```python
def addWord(words, wordDict):
    import jieba
    words = words.replace("？", "")
    words = jieba.lcut(words)
    for i in range(1, len(words)):
        if words[i-1] not in wordDict:
            #Create a new dictionary for this word
            wordDict[words[i-1]] = {}
        if words[i] not in wordDict[words[i-1]]:
            wordDict[words[i-1]][words[i]] = 0
        wordDict[words[i-1]][words[i]] += 1     
```
```addWord(words, wordDict)``` cuts the input text into words and iterates through them. It counts how many times a word appears after the current word and organizes the results into this format:
```python
{
    
    "高雄": {
        "女中": 3,    # "女中" appears after "高雄" 3 times
        "市政府": 1,    # "市政府" appears after "高雄" just once
        "中學": 2    # "中學" appears after "高雄" 2 times
    },
    
    "程式": {
        "語言": 2,
        "教學": 1
    }
}
```
(It's a dictionary with strings as keys and dictionaries as values.)

It tells us what word might come after a certain word and what word is less likely to be the next one. I'll refer to this helpful dictionary as "**wordDict**" from now on.


## The Main Function

```python
def write(n, fn):
    re = []
    wordDict = buildWordDict(files[fn])
```
The function takes in 2 arguments: ```n``` indicates how many poems it should write, and ```fn``` tells it which training text to read.
The first line creates an empty list that would contain the poems later. The second line calls ```buildWordDict()``` to get a **wordDict** of the training text.


```python
    while len(re)<n:
        passed = True
        initialWord = choice(list(wordDict.keys()))
        text = []
        currentWord = initialWord
        line = ""
```
It starts a loop that wouldn't stop until the length of ```re```, the list of poems, becomes greater than ```n```, the number of poems it's supposed to write.

```passed``` is a flag for the poem it's working on, which is set to ```True``` for now. The poems would be checked after they're finished, and if something's wrong this flag would be updated to ```False```.

Then an ```initialWord``` is chosen from all the keys in **wordDict**. It would, literally, be used as the initial word of this poem.

```text``` is a list that maintains the poem, and ```line``` is a string that maintains a single line in the poem. Every time a word is written it's appended at the end of ```line```, and when a line is finished the current ```line``` would be added to the ```text``` list, meanwhile ```line``` would be cleared (reset to an empty string) for the next line.

```python
        while True:
            if currentWord not in wordDict:
                currentWord = initialWord
            currentWord = retrieveRandomWord(wordDict[currentWord])

            line += currentWord
```
Here comes the actual writing! It's an infinite loop in which the next word is chosen based on the probability of it's appearance after the current word, and then added to ```line```.


```python
            if len(line) > 7:
                line += '\n'
                currentWord = '\n'
            if currentWord == '\n':
                for c in line:
                    if c in no:
                        passed = False
                while line[0] in notfirst:
                    line = line[1:]

                else:
                    text.append(line)
```
A line is finished when an End-of-Line special character '\n' is written. (It'd be mandatorily ended if longer than 7 characters. ) After that, the function does some checks and edits before adding it to the ```text``` list.

```python
                    if len(text)>4 and len(text)<6:
                        while text[0][0] == '\n':
                            del text[0]
                        while text[-1][0] == '\n':
                            del text[-1]
                        while len(line)>2 and line[-2] in notlast:
                            text[-1] = line[:-2] + '\n'
                        break
                    line = ""

        if passed:
            re.append(text)
```

When enough lines are written, the function would proofread the whole poem one last time and then break the writing loop. If ```passed``` was never updated to ```False``` during the loop, it should be a poem decent enough to output (I guess?) so the function would save it to ```re``` .
            
```python
    return re
```
After more than ```n``` rounds in the loop we've finally got all the poems! Return them and the main function's job is all done.
