Skip to content

Commit

Permalink
Merge pull request #30 from Bradfield/14-language-switcher
Browse files Browse the repository at this point in the history
Add a basic language switcher
  • Loading branch information
Myles Byrne committed Jan 28, 2016
2 parents b8bc78d + 915a7d0 commit d8cf1df
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 15 deletions.
11 changes: 11 additions & 0 deletions book/book.css
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,17 @@ table tr:last-child td {
stroke: #eee;
}

#language-switcher {
float: right;
font-size: 1.5em;
margin: 15px 0 0 20px;
visibility: hidden;
}

[data-language-switcher-enabled] [data-language] {
visibility: hidden;
}

#svg-refs {
width: 0;
height: 0;
Expand Down
69 changes: 69 additions & 0 deletions book/language-switching.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// TODO: litjs

const languageName = {
javascript: 'JavaScript',
python: 'Python',
}

const languageAttribute = 'data-language'

const unique = (values) =>
Array.from(new Set(values).values())

const getLanguageValue = (namedNodeMap) =>
namedNodeMap.attributes[languageAttribute].nodeValue

const querySelectionArray = (selector) =>
Array.from(document.querySelectorAll(selector))

const getContentForAllLanguages = () =>
querySelectionArray(`[${languageAttribute}]`)

const getContentForLanguage = (language) =>
querySelectionArray(`[${languageAttribute}=${language}]`)

// An array of all languages defined on the page
const getUniqueLanguages = () =>
unique(getContentForAllLanguages().map(getLanguageValue)).sort()

// Hide content other than the specified language
const switchLanguageTo = (language) => {
getContentForAllLanguages().forEach(node => node.hidden = true)
getContentForLanguage(language).forEach(node => node.hidden = false)
}

// When the user changes the prefered language, reflect the change
const handleSwitchLanguage = (event) => {
const language = event.target.value
window.localStorage.setItem('preferredLanguage', language)
switchLanguageTo(language)
}

const renderLanguageSwitcher = () => {
document.body.attributes['data-language-switcher-enabled'] = true
const switcher = document.getElementById('language-switcher')
const languages = getUniqueLanguages()
const preferredLanguage = window.localStorage.getItem('preferredLanguage')

languages.forEach(language => {
const element = document.createElement('option')
element.setAttribute('value', language)
element.innerHTML = languageName[language] || language
switcher.appendChild(element)
if (language === preferredLanguage) {
element.setAttribute('selected', true)
}
})

if (languages.length > 0) {
switcher.style.visibility = 'visible'
getContentForAllLanguages().forEach(node => node.style.visibility = 'visible')
switcher.addEventListener('change', handleSwitchLanguage)
switchLanguageTo(preferredLanguage || languages[0])
}
}

// Populate the language switcher <select> element and bind change callback
window.addEventListener('load', () => {
renderLanguageSwitcher()
})
79 changes: 67 additions & 12 deletions book/recursion/dynamic-programming.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,23 @@ numbers” and recognizes the relationship $$f(n) = f(n-1) + f(n-2)$$.
With 0 and 1 as our base cases, this leads to an implementation in code that
looks very much like the mathematical definition of the sequence:

<div data-language="python">
```python
def fib(n):
if n <= 1:
return n # base cases: return 0 or 1 if n is 0 or 1, respectively
return fib(n - 1) + fib(n - 2)
```
</div>

<div data-language="javascript">
```javascript
const fib = (n) => {
if (n <= 1) return n
return fib(n - 1) + fib(n - 2)
}
```
</div>

This is a correct solution, but it poses a problem evident to those who run
`fib(50)` and wait for an answer. The running time of this implementation is
Expand Down Expand Up @@ -125,13 +136,29 @@ calculations, and we never obtain the same sum twice.

An implementation of this strategy might look like:

<div data-language="python">
```python
def fib(n):
a, b = 0, 1
for _ in range(n):
a, b = a + b, a
return a
```
</div>
<div data-language="javascript">
```javascript
const fib = (n) => {
let a = 0
let b = 1
for (let i = 0; i < n; i++) {
let temp = a
a = a + b
b = temp
}
return a
}
```
</div>

With this implementation, we sacrifice some of the elegance and
readability of our recursive solution, but gain a much better $$O(n)$$
Expand Down Expand Up @@ -451,7 +478,17 @@ is simply `1`.
Putting our base case and general case together, we obtain a succinct recursive
solution:

<div data-language="python">
<!-- litpy recursion/lattice_traversal_recursive.py -->
</div>
<div data-language="javascript">
```javascript
const numPaths = (height, width) => {
if (height === 0 || width === 0) return 1
return numPaths(height, width - 1) + numPaths(height - 1, width)
}
```
</div>

Unfortunately, we find ourselves with another $$O(2^n)$$ solution
(where $$n = max(H, W)$$) due
Expand All @@ -460,7 +497,7 @@ calculating `f(3, 2)` involves calculating `f(2, 2)` and `f(3, 1)`,
but then in calculating `f(2, 3)` we redundantly call `f(2, 2)` once
more.

Consider the call tree of `num_paths(2, 2)` to convince yourself
Consider the call tree of <span data-language="python">`num_paths(2, 2)`</span><span data-language="javascript">`numPaths(2, 2)`</span> to convince yourself
that the running time is $$O(2^n)$$:

<figure>
Expand All @@ -469,32 +506,32 @@ that the running time is $$O(2^n)$$:

<script>
drawTree('#lattice-2-2-call-tree', {
name: 'num_paths(2, 2)',
name: 'f(2, 2)',
children: [
{
name: 'num_paths(2, 1)',
name: 'f(2, 1)',
children: [
{ name: 'num_paths(2, 0)' },
{ name: 'f(2, 0)' },
{
name: 'num_paths(1, 1)',
name: 'f(1, 1)',
children: [
{ name: 'num_paths(1, 0)' },
{ name: 'num_paths(0, 1)' }
{ name: 'f(1, 0)' },
{ name: 'f(0, 1)' }
]
},
]
},
{
name: 'num_paths(1, 2)',
name: 'f(1, 2)',
children: [
{
name: 'num_paths(1, 1)',
name: 'f(1, 1)',
children: [
{ name: 'num_paths(1, 0)' },
{ name: 'num_paths(0, 1)' }
{ name: 'f(1, 0)' },
{ name: 'f(0, 1)' }
]
},
{ name: 'num_paths(0, 2)' }
{ name: 'f(0, 2)' }
]
},
]
Expand Down Expand Up @@ -562,7 +599,25 @@ This is what the memo looks like for `f(10, 10)`:
Below is a possible implementation of the dynamic programming strategy
we have discussed.

<div data-language="python">
<!-- litpy recursion/lattice_traversal_dp.py -->
</div>
<div data-language="javascript">
```javascript
const numPathsDp = (height, width) {
const memo = Array.from(Array(height + 1)).map(
() => Array.from(Array(width + 1)).map(() => 1)
)
for (let i = 1; i < memo.length; i++) {
const row = memo[i]
for (let j = 1; j < row.length; j++) {
memo[i][j] = memo[i - 1][j] + memo[i][j - 1]
}
}
return memo[height][width]
}
```
</div>

Both the time and space cost for this implementation are $$O(H \times W)$$,
compared to $$2^{max(H, W)}$$ previously, making a big difference as $$H$$
Expand Down
7 changes: 6 additions & 1 deletion layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
<link rel="stylesheet" href="<%= process.env.SITE_ROOT %>book.css">

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.10/d3.min.js"></script>
<script src="<%= process.env.SITE_ROOT %>language-switching.js"></script>
<script src="<%= process.env.SITE_ROOT %>figures.js"></script>

</head>
<body>

Expand All @@ -27,7 +29,10 @@

<div class="page">
<div class="content">
<h1><%= title %></h1>
<h1>
<%= title %>
<select id="language-switcher"></select>
</h1>
<%- contents %>

<div class="navigation">
Expand Down
13 changes: 11 additions & 2 deletions prism-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const prism = require('prismjs')

const pythonCode = /```python\n([\s\S]*?)```/g

const javascriptCode = /```javascript\n([\s\S]*?)```/g

const pythonGrammar = {
'triple-quoted-string': {
pattern: /"""[\s\S]+?"""|'''[\s\S]+?'''/,
Expand All @@ -29,15 +31,22 @@ const pythonGrammar = {
punctuation: /[{}[\];(),.:]/,
}

const highlight = (match, group) => `
const highlightPython = (match, group) => `
<pre><code class="language-python">${prism.highlight(group, pythonGrammar)}</code></pre>
`

const highlightJavaScript = (match, group) => `
<pre><code class="language-javascript">${prism.highlight(group, prism.languages.javascript)}</code></pre>
`

const highlightCode = files => {
for (let path in files) {
if (path.search('\.md$') !== -1) {
const file = files[path]
const replaced = file.contents.toString('utf8').replace(pythonCode, highlight)
const replaced = file.contents
.toString('utf8')
.replace(pythonCode, highlightPython)
.replace(javascriptCode, highlightJavaScript)
file.contents = new Buffer(replaced, 'utf8')
}
}
Expand Down
1 change: 1 addition & 0 deletions run
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function publish-from-travis {
}

function serve {
mkdir -p $BUILD_DIR
cd $BUILD_DIR && http-server -p $PORT
}

Expand Down

0 comments on commit d8cf1df

Please sign in to comment.