---
date: '2026-02-10'
description: conversion, parser
id: results
modified: 2026-06-05 15:08:41 GMT-04:00
tags:
  - sfwr4tb3
  - assignment
title: regex equivalence
created: '2026-02-10'
published: '2026-02-10'
pageLayout: default
slug: thoughts/university/twenty-five-twenty-six/sfwr-4tb3/88-Assignment-4/results
permalink: https://aarnphm.xyz/thoughts/university/twenty-five-twenty-six/sfwr-4tb3/88-Assignment-4/results.md
generator:
  quartz: v4.6.0
  hostedProvider: Cloudflare
  baseUrl: aarnphm.xyz
full: https://aarnphm.xyz/llms-full.txt
---
## A1

For each of the following equalities, state either that it is true or give a counterexample if it is false!

1. $(a \mid b)^* a (a \mid b)^* = (a \mid b)^*$

   **false.** counterexample: $\varepsilon$. the LHS requires at least one $a$ (the middle $a$ is mandatory), so it cannot produce $\varepsilon$ or any string of pure $b$‘s. the RHS accepts both.

2. $a^* (b a^*)^* = (a \mid b)^*$

   **true.** any string $w \in \{a,b\}^*$ decomposes as: leading $a$‘s (matching $a^*$), then repeated segments of a $b$ followed by $a$‘s (each matching $ba^*$). conversely, LHS only produces strings over $\{a,b\}$.

3. $(a b \mid a)^* = a (b a)^*$

   **false.** counterexample: $\varepsilon$. the LHS accepts $\varepsilon$ (zero iterations), while the RHS mandates a leading $a$. additionally, LHS accepts $aa$ (two iterations of the $a$ alternative), while RHS cannot produce $aa$ since $a(ba)^*$ yields strings of the form $a, aba, ababa, \ldots$

4. $(a \mid \varepsilon) b^* = b^* \mid a b^*$

   **true.** LHS $= \{\varepsilon, a\} \cdot \{b\}^* = \{b\}^* \cup a\{b\}^* =$ RHS.

5. $(a^* b)^* a^* = (a \mid b)^*$

   **true.** any string over $\{a,b\}$ decomposes as segments of $a$‘s each terminated by a $b$ (matching $(a^* b)$), followed by trailing $a$‘s (matching $a^*$). if $w$ contains no $b$, it matches $(a^* b)^0 \cdot a^* = a^*$.

6. $(a^* b a^*)^* = (a \mid b)^*$

   **false.** counterexample: $a$. with zero iterations, LHS yields only $\varepsilon$. with $\geq 1$ iterations, each factor $a^* b \; a^*$ contributes at least one $b$. so LHS $= \{\varepsilon\} \cup \{w \in \{a,b\}^* \mid w \text{ contains at least one } b\}$. strings consisting solely of $a$‘s (length $\geq 1$) are excluded.

## A2

Visa card numbers start with a 4. New Visa cards have 16 digits, and old cards have 13 digits. MasterCard numbers start with the numbers 51 through 55. All have 16 digits. American Express card numbers start with 34 or 37 and have 15 digits. The last digit is a checksum calculated using Luhn’s algorithm; see <http://en.wikipedia.org/wiki/Credit_card_number>. With web apps in mind, use JavaScript regular expressions to check if the credit card is well-formed and of proper length. Implement Luhn’s algorithm in JavaScript.

The Jupyter (IPython) cell magic javascript allows JavaScript to be embedded and executed. Complete the JavaScript functions isValidCreditCard and luhnCheckSum!

You can use `console.log(...)` to output to your web browser’s JavaScript console for debugging. The expected output below is one line with Valid! and one line with Invalid! Your implementation will be tested on additional credit card numbers.

<div class="notebook-runtime" data-notebook-runtime="notebook-runtime-1mc2qh2"></div>

<div class="notebook-code-cell" data-notebook-cell-frame="code-cell-1" id="code-cell-1" data-notebook-language="javascript">

<div class="notebook-runtime-cell" data-notebook-cell="code-cell-1" data-notebook-execution-count=""><span class="notebook-execution-prompt" data-notebook-execution-label="code-cell-1" aria-live="polite">In [ ]:</span></div>

<div class="notebook-cell-actions" data-notebook-cell-actions="code-cell-1">
<span class="notebook-language-badge notebook-language-badge-javascript" data-notebook-language="javascript" title="JavaScript cell"><span class="notebook-language-icon" aria-hidden="true"><svg class="notebook-language-svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect width="18" height="18" x="3" y="3" fill="currentColor" rx="2"/><text x="12" y="16.5" text-anchor="middle" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="7.5" font-weight="900" fill="var(--light)">JS</text></svg></span><span class="notebook-language-label">JavaScript cell</span></span>
<button type="button" class="notebook-icon-button" data-notebook-run-cell="code-cell-1" aria-label="Run code-cell-1" title="Run code-cell-1"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M8 5v14l11-7z"/></svg></button>
<button type="button" class="notebook-icon-button" data-notebook-edit-cell="code-cell-1" aria-label="Edit code-cell-1" title="Edit code-cell-1"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m4 16.5-.5 4 4-.5L19 8.5 15.5 5z"/><path d="m14 6.5 3.5 3.5"/></svg></button>
<button type="button" class="notebook-icon-button" data-notebook-save-cell="code-cell-1" aria-label="Save code-cell-1 locally" title="Save code-cell-1 locally" hidden><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M5 4h11l3 3v13H5z"/><path d="M8 4v6h8V4"/><path d="M8 20v-6h8v6"/></svg></button>
<button type="button" class="notebook-icon-button" data-notebook-revert-cell="code-cell-1" aria-label="Revert code-cell-1 local edit" title="Revert code-cell-1 local edit" hidden><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/></svg></button>
<button type="button" class="notebook-icon-button" data-notebook-vim-cell="code-cell-1" aria-label="Enable Vim mode" title="Enable Vim mode" hidden><svg class="notebook-vim-icon" viewBox="0 0 602 734" aria-hidden="true" focusable="false"><g transform="translate(2 3)"><path class="notebook-vim-icon-left" d="M0 155.5704 155-1l-.000003 728L0 572.237919z"/><path class="notebook-vim-icon-right" d="M443.060403 156.982405 600-1l-3.181208 728L442 572.219941z" transform="translate(521 363.5) scale(-1 1) translate(-521 -363.5)"/><path class="notebook-vim-icon-cross" d="M154.986294 0 558 615.189696 445.224605 728 42 114.172017z"/></g></svg></button>
<span class="notebook-local-source-status" data-notebook-local-source-status="code-cell-1" hidden></span>
</div>

<div class="notebook-source-editor" data-notebook-source-editor="code-cell-1" hidden></div>

```js
function isValidCreditCard(sText) {
  var reVisa = /^4\d{12}(\d{3})?$/
  var reMasterCard = /^5[1-5]\d{14}$/
  var reAmericanExpr = /^3[47]\d{13}$/
  if (
    (reMasterCard.test(sText) || reVisa.test(sText) || reAmericanExpr.test(sText)) &&
    luhnCheckSum(sText) === 0
  ) {
    element.append('Valid!')
  } else {
    element.append('Invalid!')
  }
}

function luhnCheckSum(sCardNum) {
  var sum = 0
  var numDigits = sCardNum.length
  var parity = numDigits % 2
  for (var i = 0; i < numDigits; i++) {
    var digit = parseInt(sCardNum[i], 10)
    if (i % 2 === parity) {
      digit *= 2
      if (digit > 9) digit -= 9
    }
    sum += digit
  }
  return sum % 10
}

isValidCreditCard('378282246310005') // American Express
isValidCreditCard('37873449367100') // American Express
isValidCreditCard('5555555555554444') // MasterCard
isValidCreditCard('5105105105105100') // MasterCard

isValidCreditCard('4111111111111111') // Visa
isValidCreditCard('4222222222222') // Visa
var br = document.createElement('br')
element.appendChild(br)

isValidCreditCard('378282246310003')

isValidCreditCard('5555555555554445')
isValidCreditCard('4111111111111114')
isValidCreditCard('3787344936710007')
isValidCreditCard('411111111111111')
```

<div class="notebook-runtime-output" data-notebook-output="code-cell-1" hidden></div>

</div>

## A3

### Part A

Replace two spaces at the line end with an HTML line break `<br>`.

```python
import re

match_pattern = r'  $'
replacement_pattern = r'<br>'
```

`  $` matches exactly two space characters anchored at end-of-string. test 2 (`three spaces`) replaces only the last two, leaving one space before `<br>`. test 3 (spaces in the middle) has no two-space suffix, so no match.

### Part B

Paragraphs (blocks of text separated by blank lines) → wrap each in `<p>...</p>`.

```python
import re

match_pattern = r'(.+?)(?:\n\n|\Z)'
replacement_pattern = r'<p>\1</p>'
```

`.+?` lazily matches paragraph content up to the next `\n\n` boundary or end-of-string `\Z`. each match’s group 1 gets wrapped in `<p>` tags. the `\n\n` separator is consumed and discarded.

### Part C

Convert markdown links `[text](url)` to HTML `<a href="url">text</a>`.

```python
import re

match_pattern = r'\[([^\]]+)\]\(([^)]+)\)'
replacement_pattern = r'<a href="\2">\1</a>'
```

group 1 captures the link text (everything inside `[...]`), group 2 captures the URL (everything inside `(...)`).

### Part D

Translate markdown `*italics*` and `**bold**` into HTML `<em>` and `<strong>`, with nesting.

```python
import re


def translate(input_string: str) -> str:
  result = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', input_string)
  result = re.sub(r'\*(.+?)\*', r'<em>\1</em>', result)
  return result
```

ordering matters: `**` gets replaced first so that `\*(.+?)\*` doesn’t greedily eat bold markers. after bold is resolved, the remaining single `*` pairs become `<em>`.

### Part E

Convert markdown headers (`# ...`, `## ...`, `### ...`) to HTML `<h1>`, `<h2>`, `<h3>` with auto-generated slug IDs (lowercase, spaces → hyphens).

```python
import re


def translate(input_string: str) -> str:
  def header_replace(m):
    level = len(m.group(1))
    text = m.group(2)
    slug = text.lower().replace(' ', '-')
    return f'<h{level} id="{slug}">{text}</h{level}>'

  return re.sub(
    r'^(#{1,6})\s+(.+)$', header_replace, input_string, flags=re.MULTILINE
  )
```

`(#{1,6})` captures the hash marks (determines heading level via `len`), `\s+` eats the whitespace gap, `(.+)` captures the header text. the replacement function computes the slug from the text.

<script type="application/json" data-notebook-runtime-data>{"id":"notebook-runtime-1mc2qh2","sourcePath":"thoughts/university/twenty-five-twenty-six/sfwr-4tb3/88 Assignment 4/results.md","language":"javascript","indexUrl":"","cells":[{"id":"code-cell-1","source":"function isValidCreditCard(sText) {\n  var reVisa = /^4\\d{12}(\\d{3})?$/\n  var reMasterCard = /^5[1-5]\\d{14}$/\n  var reAmericanExpr = /^3[47]\\d{13}$/\n  if (\n    (reMasterCard.test(sText) || reVisa.test(sText) || reAmericanExpr.test(sText)) \u0026\u0026\n    luhnCheckSum(sText) === 0\n  ) {\n    element.append('Valid!')\n  } else {\n    element.append('Invalid!')\n  }\n}\n\nfunction luhnCheckSum(sCardNum) {\n  var sum = 0\n  var numDigits = sCardNum.length\n  var parity = numDigits % 2\n  for (var i = 0; i \u003c numDigits; i++) {\n    var digit = parseInt(sCardNum[i], 10)\n    if (i % 2 === parity) {\n      digit *= 2\n      if (digit \u003e 9) digit -= 9\n    }\n    sum += digit\n  }\n  return sum % 10\n}\n\nisValidCreditCard('378282246310005') // American Express\nisValidCreditCard('37873449367100') // American Express\nisValidCreditCard('5555555555554444') // MasterCard\nisValidCreditCard('5105105105105100') // MasterCard\n\nisValidCreditCard('4111111111111111') // Visa\nisValidCreditCard('4222222222222') // Visa\nvar br = document.createElement('br')\nelement.appendChild(br)\n\nisValidCreditCard('378282246310003')\n\nisValidCreditCard('5555555555554445')\nisValidCreditCard('4111111111111114')\nisValidCreditCard('3787344936710007')\nisValidCreditCard('411111111111111')","language":"javascript","executionIndex":null}],"toolbar":false,"debug":true,"vimMode":true}</script>

