Compare commits

...

80 Commits

Author SHA1 Message Date
JamesFlare1212
1340ff2a0e add slides for reading-9787562498056 2025-05-06 10:03:45 -04:00
JamesFlare1212
e2398a8a39 add reading-9787562498056 2025-05-05 19:11:30 -04:00
JamesFlare1212
6a996f1c09 fix LaTeX passthrough 2025-05-04 15:35:08 -04:00
JamesFlare1212
68ffcf4956 pre-release of cards-sue-hbd-20 2025-05-04 15:24:15 -04:00
JamesFlare1212
a38c21b1a9 add common-terms 2025-04-25 18:41:06 -04:00
JamesFlare1212
54bf5ca168 update to FixIt 0.3.18 2025-04-15 14:54:07 -04:00
JamesFlare1212
df2c11ae67 add csci-1200-hw-3 2025-02-20 19:13:06 -05:00
JamesFlare1212
c501581f39 update csci-1200-hw-1 2025-02-16 14:06:19 -05:00
JamesFlare1212
dc9bda2b37 add csci-1200-hw-1 2025-02-16 14:05:36 -05:00
JamesFlare1212
2f3f75d3f2 improve engr-2350-quiz-02 2025-02-14 00:20:10 -05:00
aebff3d595 add engr-2350-quiz-02 2025-02-13 12:55:48 -05:00
JamesFlare1212
83fb593dd6 update engr-2350-lab-01 2025-02-10 23:31:39 -05:00
JamesFlare1212
764bb967f6 improve seo for engr-2350-lab-01 2025-02-10 21:58:40 -05:00
d558d5834e add engr-2350-lab-01 2025-02-10 21:51:57 -05:00
JamesFlare1212
21e87884c6 update ollama-deepseek-r1-distill 2025-02-10 00:43:43 -05:00
JamesFlare1212
c968a3ae00 add ollama-deepseek-r1-distill 2025-02-09 03:56:34 -05:00
JamesFlare1212
6726a156b3 add csci-1200-hw-2 2025-01-31 13:20:17 -05:00
a10a010701 update theme 2025-01-23 13:19:11 -05:00
JamesFlare1212
f4af875bb3 create csci-1200-hw-2 2025-01-22 10:35:21 -05:00
JamesFlare1212
4b57659747 improve studio-0-linux-2016-2 2025-01-13 19:40:09 -05:00
JamesFlare1212
c7f48c6fe9 add studio-0-linux-2016-2 2025-01-09 22:31:59 -05:00
594aa545da improve wording and update changes in x5 rom 2024-12-29 22:37:43 -05:00
286a3d2f32 update theme and minor fixes 2024-12-29 21:07:13 -05:00
df94c4721b update dsas-cca-api 2024-12-21 06:27:54 -05:00
JamesFlare1212
2fa41ea756 add ecse-1010-poc-lab03 2024-12-18 02:06:38 -05:00
JamesFlare1212
4eb7cd640b add csci-1100-crib-sheets 2024-12-08 22:42:38 -05:00
JamesFlare1212
5f52c8b518 improve ecse-1010-poc-lab02 2024-11-28 15:18:17 -05:00
JamesFlare1212
0e9f89d331 add ecse-1010-poc-lab02 2024-11-28 15:13:08 -05:00
JamesFlare1212
44447ec5b8 update umami tracker 2024-11-28 12:41:10 -05:00
JamesFlare1212
df21d69df4 fix katex and table style 2024-11-20 15:29:21 -05:00
JamesFlare1212
47358d25a8 improve katex style 2024-11-19 16:54:06 -05:00
JamesFlare1212
fc70b1776c resize pdf in posts 2024-11-19 08:15:05 -05:00
JamesFlare1212
e1e1e305bc optimize pdf in posts 2024-11-19 08:01:10 -05:00
JamesFlare1212
323c90005b add ecse-1010-poc-lab01 2024-11-19 07:46:14 -05:00
JamesFlare1212
ccb5641919 update theme 2024-11-18 01:27:38 -05:00
JamesFlare1212
2a881236cc fix url error 2024-11-07 21:15:07 -05:00
JamesFlare1212
218dcad925 update theme 2024-11-07 21:15:07 -05:00
JamesFlare1212
02cce87a93 update hw-7 solutions 2024-11-07 21:15:07 -05:00
637bdeb6c0 flarum-queue 2024-10-25 13:31:28 -04:00
JamesFlare1212
7222828595 improve translation and csci-1100-hw-7 2024-09-21 10:59:36 -04:00
JamesFlare1212
7878b7623b add install-lobechat-db 2024-09-16 04:18:31 -04:00
JamesFlare1212
d3c9fe3a83 update theme and csci-1100 hw8 2024-09-14 03:43:46 -04:00
71e64a0329 update picea-power-x5-qa 2024-08-01 11:50:27 +08:00
9240ca2d83 remove x5 firmware changelog 2024-07-19 23:45:06 +08:00
cedd0a3830 update picea-power-x5-qa 2024-07-07 00:52:54 +08:00
54019e743b improve picea-power-x5-qa 2024-07-05 22:59:22 +08:00
a148c972e1 improve picea-power-x5-qa 2024-07-05 22:57:37 +08:00
521ce492c0 improve picea-power-x5-qa 2024-07-05 21:24:27 +08:00
34011a8589 improve picea-power-x5-qa 2024-07-05 00:00:10 +08:00
dcf2763424 picea-power-x5-qa 2024-07-04 22:57:21 +08:00
021d9935d4 dsas-cca-api 2024-06-30 23:43:49 +08:00
f1677360fc improve sing-box config and update friend info 2024-06-26 01:03:41 +08:00
574ceec1ff fix bad tun name 2024-05-26 23:15:26 +08:00
bf4a723a7a add sing-box macos config 2024-05-12 07:32:35 +08:00
5de976f4b3 improve sing-box ipv6 config 2024-05-12 06:53:48 +08:00
a24aec9914 update sing-box config 2024-05-07 14:37:02 +08:00
c86e21521b get-my-proxy 2024-05-02 21:55:01 -04:00
JamesFlare1212
2a1ed5983f update theme 2024-04-24 13:04:12 -04:00
JamesFlare1212
06085a2b19 new git card 2024-04-17 17:36:26 -04:00
JamesFlare1212
a100b6794e fix estimated read time 2024-04-17 14:29:44 -04:00
JamesFlare1212
3b991a8135 csci-1100-hw-6 2024-04-13 23:18:26 -04:00
JamesFlare1212
320e5c296b cc-attack-on-index-php 2024-04-13 21:20:17 -04:00
JamesFlare1212
1a0f9f7bf1 disable rss fulltext 2024-04-11 10:16:23 -04:00
JamesFlare1212
8e07a57a6a improve css and translation 2024-04-05 00:02:15 -04:00
JamesFlare1212
3ea3cf0c1c new css and more 2024-04-04 12:04:12 -04:00
JamesFlare1212
ccb8ed859c add friend halob.oneln.top 2024-03-23 11:17:34 -04:00
JamesFlare1212
e076e5ba36 update mermaid to 10.x 2024-03-17 01:26:59 -04:00
JamesFlare1212
8bb682728e quantization-type-llama-cpp 2024-03-16 20:18:32 -04:00
JamesFlare1212
a2261bab32 csci-1100-exam-2-overview 2024-03-15 02:39:50 -04:00
JamesFlare1212
3b6a0249bb add hw-3 hw-4 hw-5 2024-03-13 15:55:10 -04:00
JamesFlare1212
c53391f4b6 update memu and few more improvement 2024-03-13 04:48:32 -04:00
JamesFlare1212
6e8ecc4dbc remove comment-visitors and comment-count 2024-03-12 05:35:51 -04:00
JamesFlare1212
861f4792ed fix artalk 2024-03-12 04:58:45 -04:00
JamesFlare1212
33584d0123 add artalk 2024-03-12 04:27:11 -04:00
JamesFlare1212
4ff4a125ff add feature collection 2024-03-12 03:53:28 -04:00
JamesFlare1212
03a1cf73d6 update .gitignore 2024-03-12 03:35:11 -04:00
JamesFlare1212
4980de8456 csci-1100-hw-2 2024-03-12 03:02:21 -04:00
JamesFlare1212
70b115b8d3 csci-1100-hw-1 2024-03-12 02:40:10 -04:00
JamesFlare1212
c54ab7bea0 fix: gravatar-cloudflare-workers english title 2024-03-12 02:01:27 -04:00
JamesFlare1212
5443c93d1e gravatar-cloudflare-workers 2024-03-12 01:56:07 -04:00
440 changed files with 43844 additions and 3100 deletions

5
.gitignore vendored
View File

@@ -1,3 +1,6 @@
*.lock
public/
resources/
resources/_gen/
isableFastRander/
.hugo_build.lock
*Zone.Identifier

7
.gitmodules vendored
View File

@@ -1,3 +1,10 @@
[submodule "themes/FixIt"]
path = themes/FixIt
url = https://github.com/hugo-fixit/FixIt.git
branch = dev
[submodule "themes/component-projects"]
path = themes/component-projects
url = https://github.com/hugo-fixit/component-projects.git
[submodule "themes/hugo-embed-pdf-shortcode"]
path = themes/hugo-embed-pdf-shortcode
url = https://github.com/anvithks/hugo-embed-pdf-shortcode.git

View File

@@ -7,16 +7,23 @@ This is a git repository for the FlareBlog. The Blog is based on [Hugo](https://
Clone the repository:
```bash
git clone https://github.com/JamesFlare1212/FlareBlog.git
git clone --recurse-submodules https://github.com/JamesFlare1212/FlareBlog.git
```
install hugo:
Then, install Hugo.
For Linux:
```bash
apt install hugo
wget https://github.com/gohugoio/hugo/releases/download/v0.146.0/hugo_extended_0.146.0_linux-amd64.deb
sudo dpkg -i hugo_extended_0.146.0_linux-amd64.deb
```
Run the server:
For MacOS:
```bash
brew install hugo
```
Server the blog locally:
```bash
cd FlareBlog

View File

@@ -1,5 +1,10 @@
+++
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
date = {{ .Date }}
draft = true
+++
---
title: {{ replace .TranslationBaseName "-" " " | title }}
subtitle:
date: {{ .Date }}
lastmod: {{ .Date }}
slug: {{ substr .File.UniqueID 0 7 }}
description:
keywords:
draft: true
---

37
archetypes/friends.md Normal file
View File

@@ -0,0 +1,37 @@
---
title: {{ replace .TranslationBaseName "-" " " | title }}
subtitle:
layout: friends
date: {{ .Date }}
description: "{{ .Site.Params.author.name }}'s friends"
keywords:
- 'Hugo FixIt'
- 'friends template'
- 友情链接
comment: false
---
<!-- When you set data `friends.yml` in `yourProject/data/` directory, it will be automatically loaded here. -->
---
<!-- You can define additional content below for this page. -->
## Base info
```yaml
- nickname: Lruihao
avatar: https://lruihao.cn/images/avatar.jpg
url: https://lruihao.cn
description: Lruihao's Note
```
## Friendly Reminder
{{< admonition info "Notice" true >}}
1. If you want to exchange link, please leave a comment in the above format. (personal non-commercial blogs / websites only)
2. :(fa-solid fa-exclamation-triangle): Website failure, stop maintenance and improper content may be unlinked!
3. Those websites that do not respect other people's labor achievements, reprint without source, or malicious acts, please do not come to exchange.
{{< /admonition >}}

View File

@@ -0,0 +1,45 @@
---
title: {{ replace .TranslationBaseName "-" " " | title }}
subtitle:
date: {{ .Date }}
slug: {{ substr .File.UniqueID 0 7 }}
draft: true
author:
name:
link:
email:
avatar:
description:
keywords:
license:
comment: true
weight: 0
tags:
- draft
categories:
- draft
collections:
- draft
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary:
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: false
lightgallery: false
password:
message:
repost:
enable: true
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->

46
archetypes/posts.md Normal file
View File

@@ -0,0 +1,46 @@
---
title: {{ replace .TranslationBaseName "-" " " | title }}
subtitle:
date: {{ .Date }}
lastmod: {{ .Date }}
slug: {{ substr .File.UniqueID 0 7 }}
draft: true
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description:
keywords:
license:
comment: true
weight: 0
tags:
- draft
categories:
- draft
collections:
- draft
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary:
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: false
lightgallery: false
password:
message:
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->

53
assets/css/_custom.scss Normal file
View File

@@ -0,0 +1,53 @@
// ==============================
// Custom style
// 自定义样式
// ==============================
ul, ol {
padding-inline-start: 0px;
margin-left: 1.33em;
}
li {
margin-left: 0.5em;
margin-bottom: 8px;
margin-top: 8px;
}
h3 .heading-mark{
padding-inline-end: 6px;
}
details summary strong {
margin-left: 6px;
}
.aside-collection {
margin-top: 64px;
}
// Short code - columns
.md-columns {
display: flex;
flex-wrap: wrap;
margin-left: -1rem;
margin-right: -1rem;
>div {
flex: 1 1;
margin: 1rem 0;
min-width: 100px;
max-width: 100%;
padding: 0 1rem;
}
.markdown-inner {
margin-top: 0;
margin-bottom: 0;
}
}
.katex-display {
overflow-y: clip;
}

View File

@@ -0,0 +1,19 @@
.md-columns {
display: flex;
flex-wrap: wrap;
margin-left: -1rem;
margin-right: -1rem;
>div {
flex: 1 1;
margin: 1rem 0;
min-width: 100px;
max-width: 100%;
padding: 0 1rem;
}
.markdown-inner {
margin-top: 0;
margin-bottom: 0;
}
}

View File

@@ -1,21 +1,32 @@
# =====================================================================================
# It's recommended to use Alternate Theme Config to configure FixIt
# Modifying this file may result in merge conflict
# There are currently some restrictions to what a theme component can configure:
# params, menu, outputformats and mediatypes
# =====================================================================================
# -------------------------------------------------------------------------------------
# Hugo Configuration
# See: https://gohugo.io/getting-started/configuration/
# -------------------------------------------------------------------------------------
#ignoreLogs = ['error-get-gh-repo', 'error-get-remote-json']
# website title
title = "FlareBlog"
# Hostname (and path) to the root
baseURL = "https://www.jamesflare.com/"
# theme list
theme = "FixIt" # enable in your site config file
theme = ["FixIt", "component-projects", "hugo-embed-pdf-shortcode"]
enableInlineShortcodes = true
defaultContentLanguage = "en"
# language code ["en", "zh-CN", "fr", "pl", ...]
languageCode = "en"
# language name ["English", "简体中文", "Français", "Polski", ...]
languageName = "English"
# whether to include Chinese/Japanese/Korean
hasCJKLanguage = true
# default amount of posts in each pages
paginate = 12
[pagination]
pagerSize = 12
# copyright description used only for seo schema
copyright = ""
# whether to use robots.txt
@@ -25,78 +36,6 @@ enableGitInfo = false
# whether to use emoji code
enableEmoji = true
# -------------------------------------------------------------------------------------
# Menu Configuration
# See: https://fixit.lruihao.cn/documentation/basics/#menu-configuration
# -------------------------------------------------------------------------------------
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = true
[languages]
[languages.en]
weight = 10
disabled = false
contentDir = "content/en"
languageCode = "en"
languageName = "English"
hasCJKLanguage = false
copyright = "This work is licensed under CC BY-NC-SA-4.0."
[languages.zh-CN]
weight = 20
disabled = false
contentDir = "content/zh-cn"
languageCode = "zh-CN"
languageName = "简体中文"
hasCJKLanguage = true
copyright = "本站内容采用 CC BY-NC-SA 4.0 国际许可协议。"
[menu]
[[menu.main]]
identifier = "archives"
parent = ""
# you can add extra information before the name (HTML format is supported), such as icons
pre = ""
# you can add extra information after the name (HTML format is supported), such as icons
post = ""
name = "Archives"
url = "/archives/"
# title will be shown when you hover on this menu link
title = ""
weight = 1
# FixIt 0.2.14 | NEW add user-defined content to menu items
[menu.main.params]
# add css class to a specific menu item
class = ""
# whether set as a draft menu item whose function is similar to a draft post/page
draft = false
# FixIt 0.2.16 | NEW add fontawesome icon to a specific menu item
icon = "fa-solid fa-archive"
# FixIt 0.2.16 | NEW set menu item type, optional values: ["mobile", "desktop"]
type = ""
[[menu.main]]
identifier = "categories"
parent = ""
pre = ""
post = ""
name = "Categories"
url = "/categories/"
title = ""
weight = 2
[menu.main.params]
icon = "fa-solid fa-folder-tree"
[[menu.main]]
identifier = "tags"
parent = ""
pre = ""
post = ""
name = "Tags"
url = "/tags/"
title = ""
weight = 3
[menu.main.params]
icon = "fa-solid fa-tags"
# -------------------------------------------------------------------------------------
# Related content Configuration
# See: https://gohugo.io/content-management/related/
@@ -163,18 +102,46 @@ defaultContentLanguageInSubdir = true
########## necessary configurations ##########
guessSyntax = true
# Goldmark is from Hugo 0.60 the default library used for Markdown
# https://gohugo.io/getting-started/configuration-markup/#goldmark
[markup.goldmark]
duplicateResourceFiles = false
[markup.goldmark.extensions]
definitionList = true
footnote = true
linkify = true
strikethrough = true
linkifyProtocol = 'https'
strikethrough = false
table = true
taskList = true
typographer = true
[markup.goldmark.extensions.passthrough]
enable = true
[markup.goldmark.extensions.passthrough.delimiters]
block = [['\[', '\]'], ['$$', '$$']]
inline = [['\(', '\)'], ['$', '$']]
# https://gohugo.io/getting-started/configuration-markup/#extras
[markup.goldmark.extensions.extras]
[markup.goldmark.extensions.extras.delete]
enable = true
[markup.goldmark.extensions.extras.insert]
enable = true
[markup.goldmark.extensions.extras.mark]
enable = true
[markup.goldmark.extensions.extras.subscript]
enable = true
[markup.goldmark.extensions.extras.superscript]
enable = true
# TODO passthrough refactor https://gohugo.io/getting-started/configuration-markup/#parserattributeblock
# TODO hugo 0.122.0 https://gohugo.io/content-management/mathematics/
[markup.goldmark.parser]
[markup.goldmark.parser.attribute]
block = true
title = true
[markup.goldmark.renderer]
hardWraps = false
# whether to use HTML tags directly in the document
unsafe = true
xhtml = false
# Table Of Contents settings
[markup.tableOfContents]
ordered = false
@@ -187,7 +154,7 @@ defaultContentLanguageInSubdir = true
# -------------------------------------------------------------------------------------
[sitemap]
changefreq = "weekly"
changefreq = "always"
filename = "sitemap.xml"
priority = 0.5
@@ -206,7 +173,7 @@ defaultContentLanguageInSubdir = true
# -------------------------------------------------------------------------------------
[privacy]
[privacy.twitter]
[privacy.x]
enableDNT = true
[privacy.youtube]
privacyEnhanced = true
@@ -230,11 +197,6 @@ defaultContentLanguageInSubdir = true
# -------------------------------------------------------------------------------------
[outputFormats]
# Options to make output .md files
[outputFormats.MarkDown]
mediaType = "text/markdown"
isPlainText = true
isHTML = false
# FixIt 0.3.0 | NEW Options to make output /archives/index.html file
[outputFormats.archives]
path = "archives"
@@ -243,6 +205,7 @@ defaultContentLanguageInSubdir = true
isPlainText = false
isHTML = true
permalinkable = true
notAlternative = true
# FixIt 0.3.0 | NEW Options to make output /offline/index.html file
[outputFormats.offline]
path = "offline"
@@ -251,18 +214,29 @@ defaultContentLanguageInSubdir = true
isPlainText = false
isHTML = true
permalinkable = true
notAlternative = true
# FixIt 0.3.0 | NEW Options to make output readme.md file
[outputFormats.README]
[outputFormats.readme]
baseName = "readme"
mediaType = "text/markdown"
isPlainText = true
isHTML = false
notAlternative = true
# FixIt 0.3.0 | CHANGED Options to make output baidu_urls.txt file
[outputFormats.baidu_urls]
baseName = "baidu_urls"
mediaType = "text/plain"
isPlainText = true
isHTML = false
notAlternative = true
# FixIt 0.3.10 | NEW Options to make output search.json file
[outputFormats.search]
baseName = "search"
mediaType = "application/json"
rel = "search"
isPlainText = true
isHTML = false
permalinkable = true
# -------------------------------------------------------------------------------------
# Customizing Output Formats
@@ -276,11 +250,11 @@ defaultContentLanguageInSubdir = true
# taxonomy: ["HTML", "RSS"]
# term: ["HTML", "RSS"]
[outputs]
home = ["HTML", "RSS", "JSON", "archives"]
page = ["HTML", "MarkDown"]
section = ["HTML", "RSS"]
taxonomy = ["HTML"]
term = ["HTML", "RSS"]
home = ["html", "rss", "archives", "offline", "search"]
page = ["html", "markdown"]
section = ["html", "rss"]
taxonomy = ["html"]
term = ["html", "rss"]
# -------------------------------------------------------------------------------------
# Taxonomies Configuration
@@ -317,8 +291,8 @@ defaultContentLanguageInSubdir = true
enablePWA = false
# FixIt 0.2.14 | NEW whether to add external Icon for external links automatically
externalIcon = false
# FixIt 0.3.0 | NEW whether to reverse the order of the navigation menu
navigationReverse = false
# FixIt 0.3.13 | NEW whether to capitalize titles
capitalizeTitles = true
# FixIt 0.3.0 | NEW whether to add site title to the title of every page
# remember to set up your site title in `hugo.toml` (e.g. title = "title")
withSiteTitle = true
@@ -327,6 +301,8 @@ defaultContentLanguageInSubdir = true
# FixIt 0.3.0 | NEW whether to add site subtitle to the title of index page
# remember to set up your site subtitle by `params.header.subtitle.name`
indexWithSubtitle = false
# FixIt 0.3.13 | NEW whether to show summary in plain text
summaryPlainify = false
# FixIt 0.2.14 | NEW FixIt will, by default, inject a theme meta tag in the HTML head on the home page only.
# You can turn it off, but we would really appreciate if you dont, as this is a good way to watch FixIt's popularity on the rise.
disableThemeInject = false
@@ -429,19 +405,26 @@ defaultContentLanguageInSubdir = true
enable = false
sticky = false
showHome = false
# FixIt 0.3.13 | NEW
separator = "/"
capitalize = true
# FixIt 0.3.10 | NEW Post navigation config
[params.navigation]
# whether to show the post navigation in section pages scope
inSection = false
# whether to reverse the next/previous post navigation order
reverse = false
# Footer config
[params.footer]
enable = true
# FixIt 0.2.17 | CHANGED Custom content (HTML format is supported)
# For advanced use, see parameter `params.customFilePath.footer`
custom = ""
# whether to show copyright info
copyright = true
# whether to show the author
author = true
# Site creation year
since = "2022"
since = ""
# FixIt 0.2.12 | NEW Public network security only in China (HTML format is supported)
gov = ""
# ICP info only in China (HTML format is supported)
@@ -450,9 +433,9 @@ defaultContentLanguageInSubdir = true
license = '<a rel="license external nofollow noopener noreferrer" href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank">CC BY-NC 4.0</a>'
# FixIt 0.3.0 | NEW whether to show Hugo and theme info
[params.footer.powered]
enable = false
hugoLogo = true
themeLogo = true
enable = true
hugoLogo = false
themeLogo = false
# FixIt 0.2.17 | CHANGED Site creation time
[params.footer.siteTime]
enable = false
@@ -481,8 +464,12 @@ defaultContentLanguageInSubdir = true
paginate = 20
# date format (month and day)
dateFormat = "01-02"
# amount of RSS pages
rss = 30
# FixIt 0.3.10 | NEW Section feed config for RSS, Atom and JSON feed.
[params.section.feed]
# The number of posts to include in the feed. If set to -1, all posts.
limit = -1
# whether to show the full text content in feed.
fullText = false
# FixIt 0.2.13 | NEW recently updated pages config
# TODO refactor to support archives, section, taxonomy and term
[params.section.recentlyUpdated]
@@ -491,14 +478,26 @@ defaultContentLanguageInSubdir = true
days = 30
maxCount = 10
# List (category or tag) page config
# Term list (category or tag) page config
[params.list]
# special amount of posts in each list page
paginate = 20
# date format (month and day)
dateFormat = "01-02"
# amount of RSS pages
rss = 10
# FixIt 0.3.10 | NEW Term list feed config for RSS, Atom and JSON feed.
[params.list.feed]
# The number of posts to include in the feed. If set to -1, all posts.
limit = -1
# whether to show the full text content in feed.
fullText = false
# FixIt 0.3.13 | NEW recently updated pages config for archives, section and term list
[params.recentlyUpdated]
archives = true
section = true
list = true
days = 30
maxCount = 10
# FixIt 0.2.17 | NEW TagCloud config for tags page
[params.tagcloud]
@@ -510,8 +509,6 @@ defaultContentLanguageInSubdir = true
# Home page config
[params.home]
# amount of RSS pages
rss = 10
# Home page profile
[params.home.profile]
enable = true
@@ -623,9 +620,18 @@ defaultContentLanguageInSubdir = true
TryHackMe = ""
Douyin = ""
TikTok = ""
Credly = ""
Phone = ""
Email = "jamesflare1212@gmail.com"
RSS = true
# custom social links like the following
# [params.social.twitter]
# id = "lruihao"
# weight = 3
# prefix = "https://twitter.com/"
# Title = "Twitter"
# [social.twitter.icon]
# class = "fa-brands fa-x-twitter fa-fw"
# Page config
[params.page]
@@ -643,7 +649,7 @@ defaultContentLanguageInSubdir = true
twemoji = false
# whether to enable lightgallery
# FixIt 0.2.18 | CHANGED if set to "force", images in the content will be forced to shown as the gallery.
lightgallery = false
lightgallery = true
# whether to enable the ruby extended syntax
ruby = true
# whether to enable the fraction extended syntax
@@ -655,13 +661,13 @@ defaultContentLanguageInSubdir = true
# whether to show link to Raw Markdown content of the post
linkToMarkdown = true
# FixIt 0.3.0 | NEW whether to show link to view source code of the post
linkToSource = true
linkToSource = false
# FixIt 0.3.0 | NEW whether to show link to edit the post
linkToEdit = true
linkToEdit = false
# FixIt 0.3.0 | NEW whether to show link to report issue for the post
linkToReport = true
# whether to show the full text content in RSS
rssFullText = true
rssFullText = false
# FixIt 0.2.13 | NEW Page style ["narrow", "normal", "wide", ...]
pageStyle = "wide"
# FixIt 0.2.17 | CHANGED Auto Bookmark Support
@@ -674,7 +680,7 @@ defaultContentLanguageInSubdir = true
# FixIt 0.2.17 | NEW end of post flag
endFlag = ""
# FixIt 0.2.18 | NEW whether to enable instant.page
instantPage = false
instantPage = true
# FixIt 0.3.0 | NEW whether to enable collection list at the sidebar
collectionList = true
# FixIt 0.3.0 | NEW whether to enable collection navigation at the end of the post
@@ -696,7 +702,7 @@ defaultContentLanguageInSubdir = true
position = "right"
# FixIt 0.2.13 | NEW Display a message at the beginning of an article to warn the reader that its content might be expired
[params.page.expirationReminder]
enable = false
enable = true
# Display the reminder if the last modified time is more than 90 days ago
reminder = 90
# Display warning if the last modified time is more than 180 days ago
@@ -705,10 +711,13 @@ defaultContentLanguageInSubdir = true
closeComment = false
# FixIt 0.3.0 | NEW page heading config
[params.page.heading]
# used with `markup.tableOfContents.ordered` parameter
# FixIt 0.3.3 | NEW whether to capitalize automatic text of headings
capitalize = false
[params.page.heading.number]
# whether to enable auto heading numbering
enable = false
# FixIt 0.3.3 | NEW only enable in main section pages (default is posts)
onlyMainSection = true
[params.page.heading.number.format]
h1 = "{title}"
h2 = "{h2} {title}"
@@ -731,10 +740,12 @@ defaultContentLanguageInSubdir = true
mhchem = true
# Code config
[params.page.code]
# FixIt 0.3.9 | NEW whether to enable the code wrapper
enable = true
# whether to show the copy button of the code block
copy = true
# FixIt 0.2.13 | NEW whether to show the edit button of the code block
edit = true
edit = false
# the maximum number of lines of displayed code by default
maxShownLines = 10
# Mapbox GL JS config (https://docs.mapbox.com/mapbox-gl-js)
@@ -805,26 +816,26 @@ defaultContentLanguageInSubdir = true
Mix = false
# FixIt 0.2.15 | CHANGED Comment config
[params.page.comment]
enable = false
enable = true
# FixIt 0.2.13 | NEW Artalk comment config (https://artalk.js.org/)
[params.page.comment.artalk]
enable = false
server = "https://yourdomain"
site = "默认站点"
enable = true
server = "https://artalk.jamesflare.com"
site = "FlareBlog"
placeholder = ""
noComment = ""
sendBtn = ""
editorTravel = true
flatMode = "auto"
# FixIt 0.2.17 | CHANGED enable lightgallery support
lightgallery = false
lightgallery = true
locale = "" # FixIt 0.2.15 | NEW
# FixIt 0.2.18 | NEW
emoticons = ""
nestMax = 2
nestSort = "DATE_ASC" # ["DATE_ASC", "DATE_DESC", "VOTE_UP_DESC"]
vote = true
voteDown = false
voteDown = true
uaBadge = true
listSort = true
imgUpload = true
@@ -849,8 +860,8 @@ defaultContentLanguageInSubdir = true
appKey = ""
placeholder = ""
avatar = "mp"
meta = ""
requiredFields = ""
meta = ['nick','mail','link']
requiredFields = []
pageSize = 10
lang = ""
visitor = true
@@ -882,6 +893,7 @@ defaultContentLanguageInSubdir = true
texRenderer = false # FixIt 0.2.16 | NEW
search = false # FixIt 0.2.16 | NEW
recaptchaV3Key = "" # FixIt 0.2.16 | NEW
turnstileKey = "" # FixIt 0.3.8 | NEW
reaction = false # FixIt 0.2.18 | NEW
# Facebook comment config (https://developers.facebook.com/docs/plugins/comments)
[params.page.comment.facebook]
@@ -979,6 +991,25 @@ defaultContentLanguageInSubdir = true
# For values, see https://mermaid.js.org/config/theming.html#available-themes
themes = ["default", "dark"]
# FixIt 0.3.13 | NEW Admonitions custom config
# See https://fixit.lruihao.cn/documentation/content-management/shortcodes/extended/admonition/#custom-admonitions
# syntax: <type> = <icon>
[params.admonition]
# ban = "fa-solid fa-ban"
# FixIt 0.3.14 | NEW Task lists custom config
# See https://fixit.lruihao.cn/documentation/content-management/advanced/#custom-task-lists
# syntax: <sign> = <icon>
[params.taskList]
# tip = "fa-regular fa-lightbulb"
# FixIt 0.3.15 | NEW version shortcode config
[params.repoVersion]
# url prefix for the release tag
url = "https://github.com/hugo-fixit/FixIt/releases/tag/v"
# project name
name = "FixIt"
# FixIt 0.2.12 | NEW PanguJS config
[params.pangu]
# For Chinese writing
@@ -986,7 +1017,7 @@ defaultContentLanguageInSubdir = true
selector = "article" # FixIt 0.2.17 | NEW
# FixIt 0.2.12 | NEW Watermark config
# Detail config see https://github.com/Lruihao/watermark#readme
# Detail config see https://github.com/Lruihao/watermark#readme
[params.watermark]
enable = false
# watermark's text (HTML format is supported)
@@ -1008,11 +1039,16 @@ defaultContentLanguageInSubdir = true
# FixIt 0.2.13 | NEW watermark's fontFamily
fontFamily = "inherit"
# FixIt 0.2.12 | NEW Busuanzi count
[params.ibruce]
# FixIt 0.3.10 | NEW Busuanzi count
[params.busuanzi]
# whether to enable busuanzi count
enable = false
# Enable in post meta
enablePost = false
# busuanzi count core script source. Default is https://vercount.one/js
source = "https://vercount.one/js"
# whether to show the site views
siteViews = true
# whether to show the page views
pageViews = true
# Site verification code config for Google/Bing/Yandex/Pinterest/Baidu/360/Sogou
[params.verification]
@@ -1033,7 +1069,7 @@ defaultContentLanguageInSubdir = true
# Analytics config
[params.analytics]
enable = false
enable = true
# Google Analytics
[params.analytics.google]
id = ""
@@ -1044,6 +1080,31 @@ defaultContentLanguageInSubdir = true
id = ""
# server url for your tracker if you're self hosting
server = ""
# FixIt 0.3.16 | NEW Baidu Analytics
[params.analytics.baidu]
id = ""
# FixIt 0.3.16 | NEW Umami Analytics
[params.analytics.umami]
data_website_id = "c687e659-a8de-4d17-a794-0fb82dd085f5"
src = "https://track.jamesflare.com/script.js"
data_host_url = "https://track.jamesflare.com"
data_domains = ""
# FixIt 0.3.16 | NEW Plausible Analytics
[params.analytics.plausible]
data_domain = ""
src = ""
# FixIt 0.3.16 | NEW Cloudflare Analytics
[params.analytics.cloudflare]
token = ""
# FixIt 0.3.16 | NEW Splitbee Analytics
[params.analytics.splitbee]
enable = false
# no cookie mode
no_cookie = true
# respect the do not track setting of the browser
do_not_track = true
# token(optional), more info on https://splitbee.io/docs/embed-the-script
data_token = ""
# Cookie consent config
[params.cookieconsent]
@@ -1080,7 +1141,7 @@ defaultContentLanguageInSubdir = true
# FixIt 0.2.18 | NEW Depends on the author's email, if the author's email is not set, the local avatar will be used
enable = false
# Gravatar host, default: "www.gravatar.com"
host = "www.gravatar.com" # ["cn.gravatar.com", "gravatar.loli.net", ...]
host = "gravatar.jamesflare.com" # ["cn.gravatar.com", "gravatar.loli.net", ...]
style = "" # ["", "mp", "identicon", "monsterid", "wavatar", "retro", "blank", "robohash"]
# FixIt 0.2.16 | NEW Back to top
@@ -1112,23 +1173,34 @@ defaultContentLanguageInSubdir = true
# ["barber-shop", "big-counter", "bounce", "center-atom", "center-circle", "center-radar", "center-simple",
# "corner-indicator", "fill-left", "flash", "flat-top", "loading-bar", "mac-osx", "material", "minimal"]
theme = "minimal"
# FixIt 0.2.17 | NEW Define custom file paths
# Create your custom files in site directory `layouts/partials/custom` and uncomment needed files below
[params.customFilePath]
# aside = "custom/aside.html"
# profile = "custom/profile.html"
# footer = "custom/footer.html"
# FixIt 0.3.10 | NEW Global Feed config for RSS, Atom and JSON feed.
[params.feed]
# The number of posts to include in the feed. If set to -1, all posts.
limit = 10
# whether to show the full text content in feed.
fullText = true
# FixIt 0.3.12 | NEW Custom partials config
# Custom partials must be stored in the /layouts/partials/ directory.
# Depends on open custom blocks https://fixit.lruihao.cn/references/blocks/
[params.customPartials]
head = []
menuDesktop = []
menuMobile = []
profile = []
aside = []
comment = []
footer = []
widgets = []
assets = []
postFooterBefore = []
postFooterAfter = []
# FixIt 0.2.15 | NEW Developer options
# Select the scope named `public_repo` to generate personal access token,
# Configure with environment variable `HUGO_PARAMS_GHTOKEN=xxx`, see https://gohugo.io/functions/os/getenv/#examples
[params.dev]
enable = false
# Check for updates
c4u = false
# Please do not expose to public!
githubToken = ""
# Mobile Devtools config
[params.dev.mDevtools]
enable = false
# "vConsole", "eruda" supported
type = "vConsole"
c4u = false

View File

@@ -0,0 +1,16 @@
[en]
weight = 10
disabled = false
contentDir = "content/en"
languageCode = "en"
languageName = "English"
hasCJKLanguage = false
copyright = "This work is licensed under CC BY-NC-SA-4.0."
[zh-CN]
weight = 20
disabled = false
contentDir = "content/zh-cn"
languageCode = "zh-CN"
languageName = "简体中文"
hasCJKLanguage = true
copyright = "本站内容采用 CC BY-NC-SA 4.0 国际许可协议。"

View File

@@ -0,0 +1,81 @@
# -------------------------------------------------------------------------------------
# Menu Configuration
# See: https://fixit.lruihao.cn/documentation/basics/#menu-configuration
# -------------------------------------------------------------------------------------
[[main]]
identifier = "archives"
parent = ""
# you can add extra information before the name (HTML format is supported), such as icons
pre = ""
# you can add extra information after the name (HTML format is supported), such as icons
post = ""
name = "Archives"
url = "/archives/"
# title will be shown when you hover on this menu link
title = ""
weight = 1
# FixIt 0.2.14 | NEW add user-defined content to menu items
[main.params]
# add css class to a specific menu item
class = ""
# whether set as a draft menu item whose function is similar to a draft post/page
draft = false
# FixIt 0.2.16 | NEW add fontawesome icon to a specific menu item
icon = "fa-solid fa-archive"
# FixIt 0.2.16 | NEW set menu item type, optional values: ["mobile", "desktop"]
type = ""
[[main]]
identifier = "categories"
parent = "archives"
pre = ""
post = ""
name = "Categories"
url = "/categories/"
title = ""
weight = 2
[main.params]
icon = "fa-solid fa-folder-tree"
[[main]]
identifier = "collections"
parent = "archives"
name = "Collections"
url = "collections/"
weight = 3
[main.params]
icon = "fa-solid fa-layer-group"
[[main]]
identifier = "tags"
parent = "archives"
pre = ""
post = ""
name = "Tags"
url = "/tags/"
title = ""
weight = 4
[main.params]
icon = "fa-solid fa-tags"
[[main]]
identifier = "about"
name = "About"
url = "about/"
weight = 20
[main.params]
icon = "fa-solid fa-circle-info"
[[main]]
identifier = "friends"
parent = "about"
name = "Friends"
url = "/friends/"
weight = 5
[main.params]
icon = "fa-solid fa-link"
[[main]]
identifier = "resume"
parent = "about"
name = "About Me"
url = "/me/"
weight = 10
[main.params]
icon = "fa-solid fa-clipboard-user"

View File

@@ -0,0 +1,81 @@
# -------------------------------------------------------------------------------------
# Menu Configuration
# See: https://fixit.lruihao.cn/documentation/basics/#menu-configuration
# -------------------------------------------------------------------------------------
[[main]]
identifier = "archives"
parent = ""
# you can add extra information before the name (HTML format is supported), such as icons
pre = ""
# you can add extra information after the name (HTML format is supported), such as icons
post = ""
name = "归档"
url = "/archives/"
# title will be shown when you hover on this menu link
title = ""
weight = 1
# FixIt 0.2.14 | NEW add user-defined content to menu items
[main.params]
# add css class to a specific menu item
class = ""
# whether set as a draft menu item whose function is similar to a draft post/page
draft = false
# FixIt 0.2.16 | NEW add fontawesome icon to a specific menu item
icon = "fa-solid fa-archive"
# FixIt 0.2.16 | NEW set menu item type, optional values: ["mobile", "desktop"]
type = ""
[[main]]
identifier = "categories"
parent = "archives"
pre = ""
post = ""
name = "分类"
url = "/categories/"
title = ""
weight = 2
[main.params]
icon = "fa-solid fa-folder-tree"
[[main]]
identifier = "collections"
parent = "archives"
name = "合集"
url = "collections/"
weight = 3
[main.params]
icon = "fa-solid fa-layer-group"
[[main]]
identifier = "tags"
parent = "archives"
pre = ""
post = ""
name = "标签"
url = "/tags/"
title = ""
weight = 4
[main.params]
icon = "fa-solid fa-tags"
[[main]]
identifier = "about"
name = "关于"
url = "about/"
weight = 20
[main.params]
icon = "fa-solid fa-circle-info"
[[main]]
identifier = "friends"
parent = "about"
name = "友链"
url = "/friends/"
weight = 5
[main.params]
icon = "fa-solid fa-link"
[[main]]
identifier = "resume"
parent = "about"
name = "关于我"
url = "/me/"
weight = 10
[main.params]
icon = "fa-solid fa-clipboard-user"

View File

@@ -0,0 +1,34 @@
---
title: Friends
subtitle:
layout: friends
date: 2024-03-12T17:16:13-04:00
description: "James's friends"
keywords: ["friends"]
comment: false
---
<!-- When you set data `friends.yml` in `yourProject/data/` directory, it will be automatically loaded here. -->
---
<!-- You can define additional content below for this page. -->
## Base info
```yaml
- nickname: James
avatar: https://www.jamesflare.com/site-logo.avif
url: https://jamesflare.com/
description: FlareBlog
```
## Friendly Reminder
{{< admonition info "Notice" true >}}
1. If you want to exchange link, please leave a comment in the above format.
2. Website failure, stop maintenance and improper content may be unlinked!
3. Those websites that do not respect other people's labor achievements, reprint without source, or malicious acts, please do not come to exchange.
{{< /admonition >}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

31
content/en/me/index.md Normal file
View File

@@ -0,0 +1,31 @@
---
title: About Me
subtitle:
date: 2024-03-12T17:40:26-04:00
slug: me
description:
keywords:
draft: false
comment: false
math: false
lightgallery: false
---
## Who am I?
My name is James, a student of Electrical Engineering. I like playing with some open source projects and interested in LLM and other AI related topics (no sound like a hardware guy). I love light weight and fast softwares.
I am a fan of technology, design, and innovation. Recently, I am watching Starship's Third Test Flight of SpaceX. That is so cool!
## What is this blog?
This is a personal blog where I write about my thoughts and experiences. I hope you find it interesting and useful.
## My Projects
{{< gh-repo-card-container >}}
{{< gh-repo-card repo="JamesFlare1212/FlareBlog" >}}
{{< gh-repo-card repo="JamesFlare1212/SCDocs" >}}
{{< gh-repo-card repo="JamesFlare1212/NancyPortfolio" >}}
{{< /gh-repo-card-container >}}

View File

@@ -40,7 +40,7 @@ lightgallery: false
password:
message:
repost:
enable: true
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter

View File

@@ -36,7 +36,7 @@ lightgallery: false
password:
message:
repost:
enable: true
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter

View File

@@ -0,0 +1,245 @@
---
slug: "gravatar-cloudflare-workers"
title: "Using CloudFlare Workers for Reverse Proxy"
subtitle: ""
date: 2023-01-15T21:31:42+08:00
draft: false
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description: "Gravatar's avatar service is unstable in mainland China. While we can use some public mirrors, we also have the option to set up our own reverse proxy. However, setting up a self-hosted reverse proxy requires a server, which may incur additional costs. More importantly, an individual's server is typically limited to a single data center, resulting in significant speed variations across different regions, unlike Gravatar's global CDN coverage."
keywords: ["Gravatar", "CloudFlare Workers"]
license: ""
comment: true
weight: 0
tags:
- CloudFlare
- JavaScript
categories:
- Tutorials
hiddenFromHomePage: false
hiddenFromSearch: false
summary: "Gravatar's avatar service is unstable in mainland China. While we can use some public mirrors, we also have the option to set up our own reverse proxy. However, setting up a self-hosted reverse proxy requires a server, which may incur additional costs. More importantly, an individual's server is typically limited to a single data center, resulting in significant speed variations across different regions, unlike Gravatar's global CDN coverage."
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc:
enable: true
math:
enable: false
lightgallery: true
seo:
images: []
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/theme-documentation-content/#front-matter
---
## Introduction
In mainland China, Gravatar's avatar service has always been unstable and unavailable. In addition to using some public mirror sites, we can also set up our own reverse proxy. However, if you want to set up your own reverse proxy, you need a server, which may incur additional costs. More importantly, a typical person's server can only be located in one data center, resulting in large speed differences between regions, unlike Gravatar which has a global CDN network.
I hope that users worldwide can enjoy fast loading speeds. At the very least, the proxies used by users may also be distributed globally, right?
{{< image src="network-map.svg" width="750px" caption="CloudFlare Network Map" >}}
[CloudFlare Workers](https://developers.cloudflare.com/workers/learning/how-workers-works/) can directly process requests in their nearby data centers, which is much faster than using a random server.
## Pricing
So, what is the [pricing](https://developers.cloudflare.com/workers/platform/pricing) for Workers?
| | Free Plan | Paid Plan - Bundled | Paid Plan - Unbound |
| -------- | -------------------------- | ---------------------------------- | ------------------------------------------------- |
| Requests | 100,000 / day | 10 million / month, +$0.50/million | 1 million / month, + $0.15/million |
| Duration | 10ms CPU time / invocation | 50 ms CPU time / invocation | 400,000 GB-s, + $12.50/million GB-s |
The free plan is generally sufficient for most use cases.
You get 100,000 free requests per day, which is basically inexhaustible. The 10ms CPU time per invocation is also adequate, as our code likely only takes around 1ms to execute.
Even if you do need to pay, since we don't require Workers KV, Queues, Durable Objects, or other products, and only need the number of requests, the Paid Plan - Unbound tier applies. 1 million requests cost a mere $0.15, equivalent to about one Chinese yuan, which is incredibly cheap.
### Cost Calculation
Assuming each image is around 30KB, 1 million images would consume approximately 28.6GB of traffic. Considering that VPS providers may calculate traffic in both directions, it would be about 57.2GB.
The price of 57GB per yuan is considered average in the VPS market, not particularly cheap, especially when compared to unlimited traffic plans or Russian VPS offerings. However, when taking into account the quality of the network and the global distribution of data centers, CloudFlare's offering is unbeatable.
CloudFlare's speed is not something that cheap VPS plans can match. If you were to use a premium network like CN2, the price would definitely be much higher.
## Workers JavaScript
The usage is very straightforward, essentially just JavaScript.
Let's construct a simple example:
```JavaScript
addEventListener(
"fetch", event => {
let url = new URL(event.request.url);
url.hostname = "www.gravatar.com";
url.protocol = "https";
let request = new Request(url, event.request);
event.respondWith(
fetch(request)
)
}
)
```
In essence, the logic is to return the requested URL received via HTTPS, but change the `hostname` sent at the time of the request to `www.gravatar.com`.
### Deployment
The deployment process is also very simple. Create a new Service in the CloudFlare Workers dashboard.
Copy the above code into it and click Deploy.
## Custom Domains
By default, you will be assigned a workers.dev subdomain, which is perfectly fine to use. However, I prefer to set up my own custom domain.
Go to the Service settings, then to Triggers, click Add Custom Domains, and enter your desired domain name.
For example, if I choose gravatar.jamesflare.com, I would enter `gravatar.jamesflare.com`.
## Testing
Let's test it out and see if it works. Here, I'll use my avatar URL for testing: `/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&d=`.
Constructing the URL:
![](https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&d=) https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&d=
As you can see, it works perfectly.
## Usage in Hugo
This part is somewhat derivative. The process can be quite convoluted and may vary between different themes, so I want to focus on the thought process rather than providing a direct solution, as it may not be universally applicable.
I am using the FixIt theme, which is roughly equivalent to LoveIt.
### Locating the Template File
After some searching, I found that the template responsible for rendering the author's avatar in articles is located at `/FixIt/layouts/partials/single/post-author.html`.
The code is as follows:
```go-html-template
{{- $params := .Scratch.Get "params" -}}
{{- $author := .Site.Author | merge (dict "name" "Anonymous" "link" (echoParam $params "authorlink") "email" (echoParam $params "authoremail")) -}}
{{- $avatar := .Site.Params.home.profile.avatarURL -}}
{{- if isset $params "author" | and (ne $params.author .Site.Author.name) -}}
{{- $author = dict "name" $params.author | merge $author -}}
{{- $author = dict "link" (echoParam $params "authorlink") | merge $author -}}
{{- $author = dict "email" (echoParam $params "authoremail") | merge $author -}}
{{- $avatar = "" -}}
{{- end -}}
{{- if (not $avatar | or $params.gravatarForce) | and $author.email -}}
{{- $gravatar := .Site.Params.gravatar -}}
{{- with $gravatar -}}
{{- $avatar = printf "https://%v/avatar/%v?s=32&d=%v"
(path.Clean .Host | default "www.gravatar.com")
(md5 $author.email)
(.Style | default "")
-}}
{{- end -}}
{{- end -}}
<span class="post-author">
{{- $content := $author.name -}}
{{- $icon := dict "Class" "fa-solid fa-user-circle" -}}
{{- if $avatar -}}
{{- $content = printf "%v&nbsp;%v" (dict "Src" $avatar "Class" "avatar" "Alt" $author.name | partial "plugin/image.html") $author.name -}}
{{- $icon = "" -}}
{{- end -}}
{{- if $author.link -}}
{{- $options := dict "Class" "author" "Destination" $author.link "Title" (T "single.author") "Rel" "author" "Icon" $icon "Content" $content -}}
{{- partial "plugin/link.html" $options -}}
{{- else -}}
<span class="author">
{{- with $icon -}}
{{ . | partial "plugin/icon.html" }}
{{ end -}}
{{- $content | safeHTML -}}
</span>
{{- end -}}
</span>
{{- /* EOF */ -}}
```
### Identifying the Relevant Code Section
The following code snippet is responsible for handling the avatar:
```go-html-template
{{- if (not $avatar | or $params.gravatarForce) | and $author.email -}}
{{- $gravatar := .Site.Params.gravatar -}}
{{- with $gravatar -}}
{{- $avatar = printf "https://%v/avatar/%v?s=32&d=%v"
(path.Clean .Host | default "www.gravatar.com")
(md5 $author.email)
(.Style | default "")
-}}
{{- end -}}
{{- end -}}
```
It checks the value of the `Host` item under the `gravatar` sub-item of the `params` section in the configuration file.
If the `Host` item is empty, it defaults to `www.gravatar.com`.
There are two possible approaches: modifying the HTML template itself or modifying the value in the configuration file.
### Configuring the .toml File
I opted for the second approach.
My configuration file is in the .toml format, so I'll construct it as follows:
```toml
[params]
[params.gravatar]
host = "gravatar.jamesflare.com"
```
### Previewing in the Browser
Regenerate the site. Here, I only need to preview it:
```bash
hugo server -D -e production --disableFastRender
```
Open the browser and navigate to `http://localhost:1313/`. Check the relevant part of the HTML source code:
```html
data-src="https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&amp;d="
data-srcset="https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&amp;d=, https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&amp;d= 1.5x, https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&amp;d= 2x"
```
As you can see, the change has taken effect, which can also be verified using the Sources tab in the browser's developer tools.
### Side Note
As a side note, it turns out that the FixIt theme's configuration file already included this option, and I was the clown for not noticing it earlier, despite searching extensively online. Here's the relevant section:
```toml
[params]
[params.gravatar]
# Gravatar host, default: "www.gravatar.com"
host = "www.gravatar.com" # ["cn.gravatar.com", "gravatar.loli.net", ...]
style = "" # ["", "mp", "identicon", "monsterid", "wavatar", "retro", "blank", "robohash"]
```

View File

@@ -0,0 +1,500 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1713 848" style="enable-background:new 0 0 1713 848;" xml:space="preserve">
<style type="text/css">
.st0{fill:#B1B1BA;}
.st1{fill:#F38020;stroke:#000000;stroke-miterlimit:10;}
.st2{fill:#F38020;stroke:#000000;stroke-width:1;stroke-miterlimit:10.0002;}
</style>
<path class="st0" d="M1669.1,756.9l-0.8,2.6l5.6-2.5l-0.5,2.6l-2.1,2.6l-4.2,2.9l-7,4.5l-4.6,2.5l-0.6,2.9l-4,0.1l-6.2,2.3l-4.7,4.1
l-8.1,6.2l-6.3,2.8l-4,1.8l-4.5-0.1l-1.5-2.1l-5.1-0.4l1.1-2.3l6.4-4.6l11.3-6.1l4.3-1.1l5.5-2.4l7-3.2l5.6-3.2l5.9-4.6l3.2-1.6
l3.4-3.4l5.8-2.9L1669.1,756.9L1669.1,756.9z M1697.9,726.4l-0.4,6.8l2.9-4.4l1.2,1.8l-2.3,4.9l2.9,2.1l3.2,0.5l4.6-2.5l2.1,0.8
l-5.1,5.7l-4.2,3.8l-3.8-0.2l-2.8,2l-1.5,2.7l-1.6,1.2l-4.5,3.4l-5.9,4.2l-5.9,2.5l0.4-1.6l-1.4-0.9l6.9-5.1l0.8-3.5l-3.7-2.5
l1.7-2.4l5.3-2.2l4.2-5l2.4-4.1l0.6-4.4l0.9-1.1l-0.9-2.7l-0.8-5.7l0.4-4.6l2.3-0.5l0.8,3.6l3.1,1.7L1697.9,726.4L1697.9,726.4z
M1577.3,583.8l0.5-2.3l0.1-3.6l-1.6-3.2l0.1-2.7l-1.3-0.8l0.1-3.9l-1.2-3.2l-2.3,2.4l-0.4,1.8l-1.5,3.5l-1.8,3.4l0.6,2.1l-1.2,1.3
l-1.5,4.8l0.1,3.7l-0.7,1.8l0.3,3.1l-2.6,5l-1.3,3.5l-1.7,2.9l-1.7,3.4l-4.1,2.1l-4.9-2.1l-0.5-2l-2.5-1.6h-1.6l-3.3-3.8l-2.5-2.2
l-3.9-2l-3.9-3.5l-0.1-1.8l2.5-3.1l2.1-3.2l-0.3-2.6l1.9-0.2l2.5-2.5l2-3.4l-2.2-3.2l-1.5,1.2l-2-0.5l-3.5,1.8l-3.2-2l-1.7,0.7
l-4.5-1.6l-2.7-2.7l-3.5-1.5l-3.1,0.9l3.9,2.1l-0.3,3.2l-4.8,1.2l-2.8-0.7l-3.6,2.2l-2.9,3.7l0.6,1.5l-2.7,1.7l-3.4,5.1l0.6,3.5
l-3.4-0.6h-3.5l-2.5-3.8l-3.7-2.9l-2.8,0.8l-2.6,0.9l-0.3,1.6l-2.4-0.7l-0.3,1.8l-3,1.1l-1.7,2.5l-3.5,3.1l-1.4,4.8l-2.3-1.3
l-2.2,3.1l1.5,3l-2.6,1.2l-1.4-5.5l-4.8,5.4l-0.8,3.5l-0.7,2.5l-3.8,3.3l-2,3.4l-3.5,2.8l-6.1,1.9l-3.1-0.2l-1.5,0.6l-1.1,1.4
l-3.5,0.7l-4.7,2.4l-1.4-0.8l-2.6,0.5l-4.6,2.3l-3.2,2.7l-4.8,2.1l-3.1,4.4l0.4-4.8l-3.1,4.6l-0.1,3.7l-1.3,3.2l-1.5,1.5l-1.3,3.7
l0.9,1.9l0.1,2l1.6,5l-0.7,3.3l-1-2.5l-2.3-1.8l0.4,5.9l-1.7-2.8l0.1,2.8l1.8,5l-0.6,5l1.7,2.5l-0.4,1.9l0.9,4.1l-1.3,3.6l-0.3,3.6
l0.7,6.5l-0.7,3.7l-2.2,4.4l-0.6,2.3l-1.5,1.5l-2.9,0.8l-1.5,3.7l2.4,1.2l4,4.1h3.6l3.8,0.3l3.3-2.1l3.4-1.8l1.4,0.3l4.5-3.4
l3.8-0.3l4.1-0.7l4.2,1.2l3.6-0.6l4.6-0.2l3-2.6l2.3-3.3l5.2-1.5l6.9-3.2l5,0.4l6.9-2.1l7.8-2.3l9.8-0.6l4,3.1l3.7,0.2l5.3,3.8
l-1.6,1.5l1.8,2.4l1.3,4.6l-1.6,3.4l2.9,2.6l4.3-5.1l4.3-2.1l6.7-5.5l-1.6,4.7l-3.4,3.2l-2.5,3.7l-4.4,3.5l5.2-1.2l4.7-4.4l-0.9,4.8
l-3.2,3.1l4.7,0.8l1.3,2.6l-0.4,3.3l-1.5,4.9l1.4,4l4,1.9l2.8,0.4l2.4,1l3.5,1.8l7.2-4.7l3.5-1.2l-2.7,3.4l2.6,1.1l2.7,2.8l4.7-2.7
l3.8-2.5l6.3-2.7l6-0.2l4.2-2.3l0.9-2l3-4.5l3.9-4.8l3.6-3.2l4.4-5.6l3.3-3.1l4.4-5l5.4-3.1l5-5.8l3.1-4.5l1.4-3.6l3.8-5.7l2.1-2.9
l2.5-5.7l-0.7-5.4l1.7-3.9l1.1-3.7V657l-2.8-5.1l-1.9-2.5l-2.9-3.9l0.7-6.7l-1.5,1l-1.6-2.8l-2.5,1.4l-0.6-6.9l-2.2-4l1-1.5
l-3.1-2.8l-3.2-3l-5.3-3.3l-0.9-4.3l1.3-3.3l-0.4-5.5l-1.3-0.7l-0.2-3.2l-0.2-5.5l1.1-2.8l-2.3-2.5l-1.4-2.7l-3.9,2.4L1577.3,583.8
L1577.3,583.8z M747.8,155.8l0.7,4.3l-3.9,5.4l-8.7,3.6l-6.7-0.9l4.2-6.4l-2.1-6.1l6.7-4.7l3.7-2.8l3.9-0.3l4.9,3.7L747.8,155.8
L747.8,155.8z M1383.4,380.5l-4.9-2l-1-5.5l2.2-2.8l5.8-1.8l3.2,0.1l1.6,2.5l-2,2.8l-0.6,3.6L1383.4,380.5L1383.4,380.5z
M1450,561.5l-7-4.4l5.3-1.3l2.8,2l1.8,1.9l-0.5,1.7L1450,561.5L1450,561.5z M1476.9,552.8l0.8-1.5l4.9-1.4l4-0.2l1.8-0.8l2,0.8
l-2.2,1.7l-6,2.8l-4.9,1.8l-4,4.8l-5,1.4l-0.6-0.8l0.7-2.1l2.8-3.9L1476.9,552.8L1476.9,552.8z M1439.6,549.5l3.5-0.6l1.2,2.8
l-6.6,1.2l-3.9,0.9l-3-0.1l2.2-3.6l3.1-0.1l1.6-2.2L1439.6,549.5L1439.6,549.5z M1465.8,547.8l-1.1,3.5l-8.6,1.8l-7.4-0.8l0.2-2.3
l4.5-1.3l3.4,1.9l3.8-0.5L1465.8,547.8L1465.8,547.8z M1397,540l1.3-2.6l10.3,3.1l1.8,4.1l8.3,1.1l6.6,3.8l-6.6,2.4l-5.9-2.5
l-5.1,0.2l-5.8-0.5l-5.2-1.2l-6.3-2.4l-4.1-0.6l-2.4,0.8l-10-2.6l-0.8-2.8l-5.1-0.4l4.1-6.1l6.8,0.4l4.4,2.4l2.3,0.5l0.7,2.3
L1397,540L1397,540z M1529.7,540.1l-0.3-4.8l1.2-2.2l1.3-2.2l1.2,1.9l-0.2,3L1529.7,540.1z M1491.9,518.3l-2.2,2.1l-3.9-1.2l-1-2.7
l5.7-0.3L1491.9,518.3L1491.9,518.3z M1511.9,520.8l-4.7-2.6l-4.6-0.5l-3.2,0.4l-3.9-0.2l1.5-3.5l6.9-0.3l6.1,1.8L1511.9,520.8
L1511.9,520.8z M1476.6,493.5l-4.3,1.3l-5.4-1.3l-9.3,0.3l-4.9,1l-0.9,4.8l5,5.6l3.1-2.9l10.5-2.1l-0.5,2.9l-2.4-0.9l-2.6,3.7
l-5,2.5l5.1,8.1l-1.1,2.2l4.7,7.4l-0.3,4.1l-3.1,1.9l-2.1-2.2l3-5.2l-5.6,2.4l-1.3-1.7l0.8-2.5l-3.9-3.7l0.7-6.2l-3.8,1.9l0.2,7.4
l-0.3,9.1l-3.6,1l-2.3-1.9l1.9-5.9l-0.6-6.1l-2.3-0.1l-1.6-4.3l2.4-4.2l0.9-5l2.9-9.6l1.2-2.6l4.7-4.8l4.4,1.9l7.1,0.9l6.5-0.3
l5.5-4.6l1,1.4L1476.6,493.5L1476.6,493.5z M1500.2,494.6l-2.9-0.6l-0.9,3.9l2.3,3.3l-1.6,0.8l-2.2-4l-1.7-8.2l1-5.1l1.8-2.3
l0.5,3.5l3.3,0.6l0.6,2.6L1500.2,494.6L1500.2,494.6z M1393.4,483.5l4.1,2.2l4.4-1.2l0.9-5.4l2.4-1.2l6.7-1.4l3.8-5l2.6-4l2.1-2.4
l4.6-3.5l4.1-4.4l2.5-5h2.3l3.1,3.2l0.4,2.8l3.8,1.7l4.8,2l-0.3,2.4l-3.8,0.4l1.2,3.1l-4,2.1l-3,5.8l4.3,6l-0.9,2.9l6.5,5.9
l-6.7,0.8l-1.8,4.3l0.2,5.8l-5.5,4.3l-0.3,6.4l-2.5,9.7l-0.8-2.3l-6.5,2.9l-2-3.9l-4-0.4l-2.8-2l-6.7,2.3l-2-3.1l-3.6,0.4l-4.6-0.8
l-0.7-8.5l-2.8-1.8l-2.6-5.4l-0.8-5.6l0.6-5.9L1393.4,483.5L1393.4,483.5z M1364.6,533.6l-4.5-5.3l-7-5.2l-2.2-3.8l-4.1-5.2
l-2.7-4.8l-4.1-8.9l-4.9-5.3l-1.7-5.5l-2.2-4.9l-5.1-4l-3.1-5.5l-4.4-3.6l-6.1-7l-0.6-3.2l3.6,0.3l8.8,1.2l5.2,6.2l4.5,4.3l3.2,2.7
l5.5,6.8l5.7,0.1l4.8,4.4l3.3,5.3l4.3,2.9l-2.3,5.2l3.2,2.2l2,0.2l0.9,4.4l1.9,3.6l4.1,0.5l2.6,4l-1.7,8l-0.7,9.8L1364.6,533.6
L1364.6,533.6z M1227.9,458.3l-2.8-5.1l-1.4-9.1l1.9-10.3l4.1,3.5l2.9,4.5l3,6.6l-0.6,6.6l-2.3,1.8L1227.9,458.3L1227.9,458.3z
M1005.4,60.1L992,59.4l-2.2-2.3l-7.9-1.4l-1.9-2.8l3.4-1.1l-1.6-2.8l5.4-4.3l-3.9-0.7l6.6-4.4l-2.5-2.2l6.8-2.7l10.1-3.1l11.7-0.9
l4.9-1.8l6.6-0.6l4.2,1.9l-1.3,1.5l-11.3,2.4l-9.8,2.4l-8.6,4.7l-2.8,5l-3.3,4.9l3.1,4.3l9.9,4.3L1005.4,60.1L1005.4,60.1z
M1487.8,185.7l-8.9-2l3.7,9l9.5,6.3l3,4.4l-6.5-3.8l0.1,4.8l-4.6-5.2l-3.8-6l-5.5-6.8l-2.4-4.6l-6.4-8.3l-7.8-6l-6.8-8.3l1.9-2.8
l-4.4-2.8l1.3-0.8l4.9,4l6.8,5.8l5.1,6l7.2,6.2L1487.8,185.7L1487.8,185.7z M834,150.2l-5.1-3.4l-0.9-2.5l6.7-2l1.7,3L834,150.2z
M758.5,133.7l4.7-0.8h5l-1.3,4.9l-4.2,5.3l4.8,0.4l0.4,0.6l4.1,7.1l3.2,1l2.9,6.9l1.3,2.3l5.8,1.2l-0.5,3.9l-2.4,1.8l1.9,3.1
l-4.3,3.3l-6.5-0.1l-8.3,1.7l-2.2-1.2l-3.3,2.9l-4.5-0.7l-3.5,2.3l-2.5-1.2l7.3-6.5l4.3-1.3l-7.5-1l-1.3-2.4l5.1-1.9l-2.6-3.3l1-4
l7.1,0.6l0.8-3.5l-3.1-3.8l-5.8-1.1l-1-1.6l1.7-2.7l-1.4-1.6l-2.6,2.8l-0.1-5.8l-2.2-3l1.9-6.1l3.7-4.8l3.6,0.4l5.5-0.5L758.5,133.7
L758.5,133.7z M827.9,238.2l-0.4,8.4l-2.4-0.4l-2,2.1l-2.1-1.7l-0.4-7.6l-1.3-3.6l2.8,0.3l2.5-1.9L827.9,238.2L827.9,238.2z
M866.7,23.9l-7.6,2l-6.7-1.1l2.2-1.2l-2.6-1.5l7.2-0.9l1.9,1.7L866.7,23.9L866.7,23.9z M853.8,18.9l-8.5,1.8l-1.2,3.4l-3,0.9
l-0.9,3.9l-4.4,0.2l-8.4-2.9l3-1.7l-5.7-1.4l-7.6-3.8l-3.2-3.5l9.3-1.6l2.2,1.5h5l1-1.5l5.1-0.2l4.8,1.6L853.8,18.9L853.8,18.9z
M872.8,14l-4.3,2.4l-10.1,0.5l-10.6-0.7l-0.9-1.3h-5.1l-4.3-2l10.4-1.2l5.3,1l3.2-1.2l9.1,1L872.8,14L872.8,14z M824.7,232.7
l-2.4-1.2l-1.4-4.2l1-2.3l3.2-2.3l1.1,5.2L824.7,232.7L824.7,232.7z M381.9,369.7l4.4,0.1l3.1,1.6l1.4-0.1l0.8,2.2l3-0.1l-0.3,1.9
l2.4,0.2l2.5,2.3l-2.3,2.6l-2.5-1.3l-2.6,0.2L390,379l-1.1,1.2l-2.2,0.4l-0.6-1.6l-2,0.9l-2.6,4.4l-1.4-1v-1.8l-3.6-1.1l-2.7,0.4
l-3.3-0.4l-2.7,1.1l-2.8-1.9l0.7-2.1l5,0.9l4.2,0.5l2.1-1.4l-2.2-2.8l0.3-2.4l-3.4-1l1.4-1.7l3.4,0.2l4.6,1L381.9,369.7L381.9,369.7
z M714.8,86.3l4.4,3.8l-6.1,4.2l-12.9,3.9l-3.9,1l-5.6-0.8L679,96.6l4.6-2.5l-8.8-2.7l7.8-1.1l0.1-1.6l-8.7-1.3l3.6-3.7l6.5-0.8
l5.9,3.8l6.9-3l5.1,1.5l7.3-2.9l6.9,0.4L714.8,86.3L714.8,86.3z M349.4,383.6l-3-1l-2.9-2.3l0.8-1.5l2.4-0.4l1.2,0.2l3.7,0.6
l2.7,1.5l0.8,1.8l-3.9,0.1L349.4,383.6L349.4,383.6z M1056.7,590.2l1.3,2.9l-0.8,3.1l-1.1,1.9l-1.6-3.8l-1.2,1.9l0.8,4.7l-0.7,2.7
l-1.6,1.5l-0.8,5.4l-2.7,7.4l-3.3,8.8l-4.3,12l-2.8,8.9l-3.1,7.4l-4.6,1.5l-5,2.7l-3-1.7l-4.2-2.3l-1.2-3.3l0.1-5.7l-1.6-5l-0.2-4.6
l1.3-4.6l2.6-1.1l0.2-2.1l2.9-4.8l0.8-4.1l-1.1-3l-0.8-4l-0.1-5.9l2.2-3.5l1-4.1l2.8-0.2l3.1-1.3l2.2-1.2h2.4l3.4-3.7l4.8-3.9
l1.8-3.2l-0.6-2.7l2.3,0.8l3.3-4.5l0.3-3.8l2-2.8l1.8,2.7l1.4,2.7l1.1,4.2L1056.7,590.2L1056.7,590.2z M341.4,353.8l5.2-0.8l1.8,1.5
l4.2,4l3,2.8h1.9l3.1,1.3l-0.6,1.7l4,0.3l3.9,2.6l-0.8,1.5l-3.8,0.8l-3.7,0.3l-3.8-0.5l-7.9,0.6l4.1-3.5l-2.1-1.7l-3.5-0.4l-1.7-1.8
l-0.8-3.6l-3.2,0.2l-4.9-1.7l-1.5-1.3l-7-1l-1.8-1.3l2.3-1.6l-5.4-0.3l-4.4,3.3l-2.3,0.1l-0.9,1.6l-2.9,0.7l-2.2-0.6l3.1-2l1.5-2.3
l2.7-1.4l3-1.3l4.2-0.6l1.4-0.7l4.7,0.5l4.3,0.1l4.9,2.1L341.4,353.8L341.4,353.8z M378.5,422.8l-3.3,0.8l1.7,2.9l-0.3,3.3l-2.7,3.7
l1.9,5l2.4-0.4l1.5-4.6l-1.6-2.2v-4.8l7.1-2.6l-0.6-3l2.1-2l1.7,4.5l3.9,0.1l3.5,3.5l0.1,2.1l5,0.1l6-0.7l3.1,2.9l4.2,0.7l3.2-1.9
l0.2-1.6l6.9-0.4l6.7-0.1l-4.8,1.9l1.8,3l4.4,0.4l4.1,3.2l0.7,5.1l2.9-0.2l2.2,1.5l3.6,2.4l3.3,4.1l0.1,3.3l2.1,0.1l2.9,3.1l2.1,2.3
l6.7,1.3l0.6-1.1l4.6-0.5l6,1.7l1.9,0.7l4.1,1.5l5.8,5.4l0.9,2.5l1.9-0.3l1.3,3.5l3,11.2l3,1l0.1,4.4l-4.2,5.2l1.7,2l9.9,1l0.3,6.3
l4.2-4.1l7.1,2.3l9.4,3.8l2.7,3.8l-0.8,3.5l6.5-2l10.9,3.4l8.4-0.3l8.4,5.3l7.3,7.1l4.3,1.9l4.8,0.2l2.1,2l2,8.1l1,3.9l-2,10.5
l-2.7,4.2l-7.7,8.8l-3.3,7.2l-3.9,5.5l-1.4,0.1l-1.4,4.7l1,11.9l-1.1,9.9l-0.3,4.1l-1.6,2.6l-0.5,8.5l-5.1,8.3l-0.6,6.5l-4.2,2.8
l-1.1,3.8h-5.9l-8.5,2.4l-3.6,2.9l-6,1.8l-6,5.1l-4.1,6.3l-0.3,4.8l1.3,3.5l-0.3,6.4l-0.8,3.1l-3.4,3.5l-4.5,11.2l-3.9,5l-3.2,3
l-1.5,6l-2.9,3.7l-2.6,4.2l-3.2,1.4l-2.4,1.3h-6.4l-8.8-2.7l-3.3-3.3l0.3,3.3l7.8,5.5l0.1,4.5l3.9,2.8l0.3,3.2l-3.3,8.3l-6.9,3.4
l-10.2,1.3l-5.8-0.6l2,3.8l0.1,4.8l1.8,3.1l-2.5,2.2l-5,0.9l-5.6-2.3l-1.5,1.6l2.5,6.2l4,1.9l2.2-2l2.5,3.2l-4.1,1.9l-2.9,3.9
l1.2,6.1l-0.2,3.3l-4.7,0.1l-2.9,3.1l0.1,4.7l6.4,4.5l5.2,1.3l0.2,5.6l-4.7,3.5l-0.5,7.2l-3.5,2.5l-0.9,2.9l4.1,6.3l4.7,3.6
l-2.2-0.4h-4.2l-1.7,1.5l-3.4,2.2l3.1,4.3l-0.2-4.1l3.3-2.5l3.7,0.7l2.3,2.8l4.3,4.6l7.7,3.6l7.3,1.5l-0.9,3l-4.3,0.3l-2.7-1.7
l-0.4,2l-3.2,1.8l-2.3-0.1l-3-0.5l-4.3-1.8l-5.3-0.9l-7.5-3.4l-6.3-3.2l-9.7-6.8l-2.9-3.5l-3.3-3.8l-0.2-3.6l-4.2-4.1l-5-10.6
l-0.2-6l3.4-4.8l-8.2-1.8l2.7-5.5l-2-10.2l-1.8-5.4l-1.5-8.8l-1.9-11.6l1.1-4.3l-3.3-6.3l-2.4-7.3l2.1-0.2l0.6-10.5l1.2-10.2
l0.1-9.5l-3.2-9.5l0.5-5.3l-2.1-7.8l1.9-7.8l-0.9-12.3v-13.2l0.1-14.2l-1.5-10.4l-2.1-9l-5.9-3.7l-0.7-2.6l-11.6-6.4l-10.7-6.9
l-4.6-3.9l-2.8-5.3l0.8-1.8l-5.4-8.4l-6.2-11.7l-6-12.7l-2.4-2.9l-2-4.7l-4.5-4.1l-4.1-2.6l1.7-2.8l-3-6.1l1.6-4.5l4.4-4l2.9-4.7
l-1.3-2.8l-2.1,2.9l-3.4-2.7l1.1-1.8l-1-5.8l1.9-1l1-3.9l2.2-4.1l-0.4-2.6l3.1-1.4l3.9-2.5l-0.8-2l2.1-0.5l-0.2-3.2l1.4-2.3l2.8-0.4
l2.5-4l2.2-3.3l-2-1.5l1.2-3.7l-1.1-5.9l1.2-1.6l-0.6-5.4l-2.2-3.4l0.9-3.1l1.8,0.5l1.1-1.9l-1.1-3.8l0.7-0.9l2.9,0.2l4.4-4.4
l2.4-0.7l0.1-2.1l1.4-5.4l3.3-3l3.6-0.1l0.5-1.3l4.3,0.5l4.6-3.2l2.3-1.4l2.9-3.1l2,0.4l1.3,1.7l-1.2,2.1L378.5,422.8L378.5,422.8z
M459.5,200.8l3.4,0.8l4.7-0.1l-3.3,2.5l-1.9,0.4l-5.6-2.6l-0.5-2.1l2.5-1.9L459.5,200.8L459.5,200.8z M473.9,184.9l-2.5,0.1
l-5.7-1.9l-3.5-2.9l1.9-0.6l5.9,1.6l4.1,2.6L473.9,184.9L473.9,184.9z M168.6,188.6l-3,0.9l-6.3-2.8v-2.2l-2.9-2.2l0.4-1.8l-4.3-1.1
l0.6-3.3l1.4-1.5l4.1,1.4l2.4,0.9l4.1,0.7l0.1,2.1l0.4,2.9l3.2,2.6L168.6,188.6L168.6,188.6z M504.7,175.1l-4.9,5.4l3.8-2l2.9,1.3
l-2.4,2.1l3.9,1.7l2.7-1.5l4.3,1.9l-2.8,4.6l3.7-1.1l-0.3,3.3l0.5,3.9l-3.5,5.5l-2.3,0.2l-3-1.2l2.5-5.1l-1.2-0.8l-7.3,5.4l-2.9-0.2
l4.4-2.9l-4.4-1.5l-5.4,0.3l-9.6-0.1l-0.2-1.9l3.8-2.2l-1.6-1.7l5.3-3.7l8.1-9.8l4.2-3.5l4.9-2.2l2.1,0.3l-1.4,1.7L504.7,175.1
L504.7,175.1z M147.9,153.9l1.6,0.8l4.9-0.5l-7.1,6.9l0.2,4.9h-1.9l-0.7-2.8l0.6-2.8l-0.9-1.9l1.4-2.6L147.9,153.9L147.9,153.9z
M428.8,106.9l-3.5,3l-1.7-0.5l-0.1-1.7l0.4-0.4l2.8-1.7l1.7,0.1L428.8,106.9L428.8,106.9z M419.2,103.7l-7.1,3.2l-3-0.2v-1.5
l4.9-2.7l6,0.1L419.2,103.7z M416.1,87.1l-0.9,2.5l2.8-0.9l1.5,1.5l3.4,1.9l3.7,1.7l-1.3,2.7l3.4-0.4l1.9,1.8l-4.9,1.8l-5.9-1.3
l-0.8-2.6l-6.2,3l-8.2,3l0.8-3.4l-6.3,0.6l5.7-2.8l3.6-4.4l5-5.2L416.1,87.1L416.1,87.1z M460.7,78.9l-4.9,0.2l0.7-2.6l3.8-3.1
l4.2-0.7l2.3,1.5l-1.4,2.3l-1,0.7L460.7,78.9z M386.9,68.2l-4.2,1.9l-4.1-1.6l-3.8,0.6l-3.5-2.4l5-1.7l4.9-2.3l3,1.5l1.6,1l0.3,1
L386.9,68.2L386.9,68.2z M333.8,47.2l-4.7,2.4l10.4-1.5l2.6,2.6l7.3-2.7l1.7,1.7l-2.2,5.1l4.3-2.1l2.8-5.3l4.2-0.8l3,0.9l2.2,2
l-2.7,5.1l-2.4,3.6l4.2,2.6l5,2.5l-2.6,2.4l-7,0.4l0.7,2.1l-3.1,1.9l-6.7-0.8l-5.7-1.5l-5,0.4l-9.4,1.8l-11.2,0.8l-7.8,0.5l0.3-2.5
l-4.1-1.5l-4.2,0.6l-0.6-4.2l3.3-0.6l7.1-0.9l5.5,0.3l6.2-1l-6.5-1.2l-9,0.4l-5.6-0.1v-1.9l11.5-2.1l-6.2,0.1l-5.4-1.4l7.8-3.9
l5.1-2l14.1-3.2L333.8,47.2L333.8,47.2z M372.9,45.7l-7,3.4l-2.2-3.6l2.1-0.7l5.4-0.2L372.9,45.7z M480.6,47.3l-0.7,1.4l-8.2-0.2
l-4.8,0.7l-0.9-0.3l-2-2.8l1.7-1.8l2.1-0.3l8.4,0.5C476.2,44.5,480.6,47.3,480.6,47.3z M441.2,47l0.3,3.2l7.1-4.1l11.6-2.1l2.3,5.3
l-3.3,3.4l9-1.5l5.2-2.1l6.8,2.6l3.6,2.5l-1.1,2.3l8.2-1.2l1.9,3.3l8.3,2.1l2.2,2.1l0.8,5l-9,2.5l7.7,3.5l5.9,1.2l3.2,5.1l6.4,0.3
l-3.3,3.9l-10.7,6.5l-4-2.4l-3.8-5.4l-5.8,0.7l-2.3,3.2l2.7,3.3l4.4,2.5l1,1.5v5.6l-3.6,4.1l-4.7-1.5l-8.5-4.6l3.5,4.9l2.8,3.5
l-0.3,2l-10.7-2.3l-7.6-3.3l-3.8-2.8l2.4-1.6l-4.7-2.9l-4.7-2.7l-0.9,1.6l-13,0.9l-2.4-1.9l5.4-4.2l8-0.1l9.2-0.7l-0.2-2l3.2-2.7
l8.6-5.4l0.4-2.4l-0.5-1.9l-4.6-2.6l-7.1-1.8l3.5-1.4l-2-3.3l-3.3-0.3l-2-1.8l-3.2,1.6l-7.9,0.6L434,63.6l-7.3-1.5l-5.9-0.8
l-1.8-1.9l6.1-2.3l-5.6-0.1l3.3-5.2l7.1-4.6l5.8-2.1l11.2-1.3L441.2,47L441.2,47z M391.2,43.6l3.5,1l7.5-0.6l-0.5,1.5l-6,2.4
l3.8,2.2l-5.1,4.7l-8.4,2l-3.4-0.5l-0.9-2l-5.8-3.9l1.7-1.7l7.4,0.7l-0.9-3.4L391.2,43.6L391.2,43.6z M413.5,49l-7.7,3.9l-4.3-0.2
l1.8-4.5l2.5-2.6l4.2-2.1l5.2-1.4l7.8,0.2l6.1,1.2l-10,4.5C419.1,48,413.5,49,413.5,49z M298.8,56.2l-13.4,2.5l0.6-2.3l-5.9-2.7
l4.3-2.2l7.4-3.8l7.6-3.3l0.3-3.1l14-0.8l4.1,1.1l9.4,0.2l1.9,1.5l1.6,2.1l-6.4,1.3l-13.8,3.6l-9.1,3.6
C301.4,53.9,298.8,56.2,298.8,56.2z M422.9,37.9l-4.1,1.9l-5.1-0.4l-3.2-1.3l4.4-2.2l6.9-1.3l1.4,1.7L422.9,37.9L422.9,37.9z
M414.2,29.3l0.2,2.3l-2.8,2.5l-5.8,3.7l-6.8,0.5l-3.2-0.8l3.4-2.9l-6.6,0.4l4.3-3.8l3.8,0.1l7.6-1.6l4.8,0.3L414.2,29.3z
M375.1,31.8l-0.7,1.8l4.3-0.8l3.7,0.2l-2.3,2.4l-5.1,2.3l-13.8,0.8L349,40.7l-5.9,0.1l1.5-1.6l10.6-2.2l-17.8,0.5l-4.1-0.8
l11.6-4.8l5.4-1.4l8.2,1.6l2.8,2.9l6.1,0.4l0.7-4.7l5.7-1.7l3.1,0.5L375.1,31.8L375.1,31.8z M430.3,27.5l2.3,1.6l7.3-0.1l1.4,1.6
l-2.8,1.9l3.1,1.1l1.2,1.1l4.8,0.3l5.1,0.4l7-1.1l8-0.4l5.8,0.3l2.3,1.9l-1,2l-3.5,1.4l-6.7,1l-4.3-0.6l-11.6,0.8l-8,0.1l-5.5-0.6
l-8.5-1.6l1.5-2.8l2.2-2.4l-1.4-2.1l-7-0.6l-2.6-1.5l3.8-2L430.3,27.5L430.3,27.5z M355.3,24.9l-5.8,3.7l-5.2,1.6l-3.8,0.3l-9.8,2.1
l-7,0.7l-3.6-1l11.6-3.7l12.1-3.1l5.5,0.1L355.3,24.9L355.3,24.9z M435.8,25.5l-1.9,0.1l-6.4-0.3l0.6-1.3l7.2,0.1l1.5,0.9
L435.8,25.5L435.8,25.5z M377.4,24.7l-8.6,1.3l-3.3-1.5l5.1-1.5l5.9-0.5l4.1,0.8L377.4,24.7z M385.2,20.5l-5.6,0.9h-6l1-0.7l5.7-1.3
l1.6,0.2L385.2,20.5z M431.6,23l-6.6,1l-1.6-1.1l0.7-1.8l2.1-1.8l4.4,0.1l1.7,0.3l2.3,1.6C434.6,21.3,431.6,23,431.6,23z
M417.9,21.8l-1.1,1.9l-5.2-0.5l-4-1.5l-7.7-0.2l5.3-1.4l-2.8-1.1l2.1-1.7l6.1,0.6l7.4,1.7L417.9,21.8L417.9,21.8z M471.6,15.7
l2.6,1.5l-6.4,1.3l-10.5,3.4l-6.8,0.4l-6.7-0.6l-1.6-1.9l2.1-1.6l4.3-1.2h-6.5l-2-1.5l0.3-1.9l4.9-1.9l4.1-1.3l3.9-0.3L453,9.2
l8.2-0.2l1.6,2.2l4.7,0.9l4.7,0.8L471.6,15.7L471.6,15.7z M550.1,1.9l8.5,0.3l6.7,0.4l5.2,1l-1.1,1.1l-9.7,1.6l-9,0.8L546.8,8h7.4
l-10.3,2.5l-6.8,1.1l-9.3,3.5l-7.9,0.8l-3.2,0.9l-11.1,0.4l4.3,0.6l-3.3,0.8l0.8,2.2l-5,1.6l-6.8,1.3l-3.6,1.9l-6.5,1.4l-0.5,1.1
l6.4-0.2l-1,1.1l-12.6,3l-8.4-1.4l-11.7,0.8l-4.9-0.6l-6.7-0.3l2-2.3l8-1.1l2-3.4l2.5-0.3l7.5,2l-1.7-3l-4.8-0.9l4.9-1.7l7.5-1.1
l2.8-1.6l-3-1.7l1.1-2.2l9.3,0.2l2.2,0.4l7.2-1.5l-7.2-0.5L474,12.1l-4.4-1.5L468.8,9l-2.5-1.2l1.1-1.3l6-0.8l4.1-0.1l7.4-0.6
l6.7-1.4l3.9,0.2l2.3,1l5-2l5.1-0.6l6.4-0.4l10.3-0.2L526,2l10.2-0.6l6.9,0.3L550.1,1.9L550.1,1.9z M687.9,0.4l19.7,2.9L701,4.7
l-12.9,0.2l-18.3,0.4l1.4,0.7l12.1-0.5l9.7,1.4l7-1.2l2.3,1.4l-4.5,2.3l9.2-1.5l16.9-1.5l10,0.8l1.6,1.7l-14.6,2.9l-2.2,1l-11.3,0.7
l8,0.2l-4.9,3.2l-3.6,2.9l-1.1,5.1l3.6,3.2l-5.8,0.1l-6.4,1.6l6.2,2.5l-0.1,4.2l-4.1,0.5l4,4.3l-8.6,0.3l4,2.1l-1.7,1.8l-5.6,0.8
h-5.4l4.1,3.4l-0.5,2.3l-7.2-2.1l-2.4,1.4l5,1.3l4.5,3.1L690,60l-7.3,1l-2.7-2.1l-4.2-3l0.5,3.6l-5.3,2.8l10.6,0.2l5.5,0.3
l-11.9,4.6l-12.1,4.3l-12.5,1.8l-4.6,0.1l-4.8,2.1l-7.4,5.7l-10.2,3.9l-3,0.2l-6.1,1.4l-6.5,1.3l-4.7,3.5l-1.4,3.9l-3.3,3.7
l-8.6,4.6l0.3,4.4l-3.5,4.8l-4.1,5.7l-6.5,0.3l-5-4.7h-8.9l-3.1-3.2l-0.8-5.6l-4.8-7.1l-0.7-3.7l1.5-5l-3.7-5.2l3.4-4l-2-2l7-6.3
l7.1-2l2.6-2.3l2.8-4.2l-5.5,1.9l-2.6,0.8l-4,0.8l-4.2-1.8l1.5-3.6l3-2.8h3.8l7.5,1.4l-5.3-3.4l-2.6-1.8l-4.4,0.8l-2.6-1.3l7-4.8
l-1.4-1.9l-1-3.6l-1.4-5.3l-3.6-1.9l1.4-2.1l-8-2.9l-7.6-0.3l-10.1,0.2l-9.4,0.3l-3-1.5l-3.8-3.1l10.9-1.5l7.5-0.2L519.2,25l-6.4-2
l2.2-1.7l15.5-2.2l14.9-2.2l2.8-1.6l-8.1-1.5l4.6-1.7l14.6-2.9l5.3-0.5l0.2-1.8l8.9-1l10.9-0.6H595l2.7,1.2l10.6-2.1l7,1.4l4.5,0.3
l6.2,1.3l-6.7-2.1l1.6-1.7l12.7-2.1l11.5,0.1l5-1.3L661.9,0L687.9,0.4L687.9,0.4z M1493,279l1.6,2.2l-1.3,3.9l-3.1-2.1l-2.1,1.5
l0.1,3.7l-4.2-1.8l-1.2-3l1.3-3.8l3.3,0.8l1.1-2.7L1493,279L1493,279z M1517.4,259.7l0.6,5.2l2.5,3.2l-0.6,4.6l-5.4,3l-9.1,0.4
l-4.4,7.3l-4.6-2.4l-2.4-4.8l-8.6,1.4l-5,3l-6.1,0.1l7.3,4.7l0.8,10.8l-2.5,2.7l-3.5-2.5l-0.9-5.7l-4.1-1.9l-4-4.3l4.3-2l1.1-4
l3.9-3.3l1.9-4.4l9.6-1.9l6.3,1.3v-11.5l5,3.1l4.5-6.5l1.7-2.5l-1-7.9l-5.1-7.1l-0.2-3.9l4.8-1.2l8.1,8.7l2.8,5.2l-1.3,6.4
L1517.4,259.7L1517.4,259.7z M1505.6,215.5l4.5,1.3l1.7-2.6l6,6.9l-6.4,1.7l-0.4,6.1l-10.8-4.2l1.6,6.8h-5.7l-4.6-6.1l-0.6-4.7
l5.2-0.4l-4.3-8.5l-1.9-4.8l10.4,6.4L1505.6,215.5L1505.6,215.5z M1485.1,442.7l0.8,4.3l0.6,3.5l-1.5,5.8l-2.5-6.4l-2.4,3.2l2.1,4.7
l-1.4,3l-6.8-3.7l-1.9-4.6l1.5-3.1l-3.8-3l-1.5,2.7l-2.7-0.3l-3.9,3.6l-1.1-1.9l1.9-5.4l3.4-1.7l2.8-2.4l2.2,2.8l4.2-1.7l0.7-2.8
l3.9-0.2l-0.8-4.9l4.8,3l0.8,3.2L1485.1,442.7L1485.1,442.7z M1470.6,430.9l-1.8,2.1l-1.4,4l-1.6,1.9l-3.8-4.4l1-1.7l1.2-1.8l0.3-4
l3-0.3l-0.5,4.2l3.6-6.1L1470.6,430.9L1470.6,430.9z M1440.5,437l-7,6l2.4-4.4l3.8-4l2.9-4.4l2.4-6.3l1.5,5.2l-3.4,3.5L1440.5,437
L1440.5,437z M1457.9,420.6l3.5,2h3.6l0.2,2.6l-2.4,2.7l-3.3,1.9l-0.5-2.9l0.1-3.3L1457.9,420.6L1457.9,420.6z M1477.9,418.9
l2.3,7.1l-4.5-1.7l0.4,2.1l1.7,4l-2.5,1.4l-0.7-4.5l-1.7-0.3l-1.2-3.9l3.3,0.5l-0.3-2.4l-4-4.8l5.4,0.1L1477.9,418.9L1477.9,418.9z
M1455,413.1l-0.8,5.5l-2.8-3.2l-3.4-4.8l4.8,0.2L1455,413.1L1455,413.1z M1448.6,378.5l3.7,1.8l1.5-1.6l0.7,1.6l-0.4,2.6l2.7,4.6
l-0.6,5.3l-3,2.1l-0.1,5.1l2,5.1l3.1,0.7l2.3-0.8l7.5,3.6l-0.1,3.4l2,1.6l-0.2,2.9l-4.8-3.1l-2.5-3.4l-1.1,2.4l-4-3.8l-5,0.9
l-2.9-1.4l-0.1-2.6l1.5-1.7l-1.8-1.4l-0.4,2.3l-3.3-3.7l-1.2-2.8l-1.2-6l2.6,2l-1.1-9.9l0.9-5.8L1448.6,378.5L1448.6,378.5z
M1651.2,539.7l-1.6,0.6l-2.2-2.5l-2.2-4l-0.9-4.8l0.8-0.7l0.5,1.9l1.6,1.5l2.5,4l2.4,2.2L1651.2,539.7L1651.2,539.7z M1630,531.1
l-3,0.5l-1,1.8l-6.2,3.1h-2.9l-4.5-1.9l-3.1-1.8l0.6-1.9l5,0.9l3.1-0.5l1-3.1l0.8-0.1l0.4,3.4l3.2-0.5l1.7-2.2l3.3-2.3l-0.4-3.7
l3.4-0.1l1.1,1l-0.4,3.5L1630,531.1L1630,531.1z M1636.9,524.9l-1.9,1.7l-0.8-3.8l-1.2-2.4l-2.4-2.1l-3.1-2.7l-3.9-1.9l1.6-1.5
l2.9,1.8l1.9,1.4l2.3,1.5l2.1,2.6l2,2.1L1636.9,524.9L1636.9,524.9z M1442.8,351.3l-1.3,5.2l-4-5.4l-1.5-4.7l1.9-6.3l3.3-4.8
l2.9,1.9l-0.1,3.9L1442.8,351.3L1442.8,351.3z M1569.3,512.8l9.6,4.4l10.2,3.6l3.7,3.3l2.9,3.2l0.6,3.7l9.1,4l1.1,3.3l-5.2,0.7
l0.9,4.3l4.7,4.1l3,6.8l3.2-0.2l-0.5,2.8l4.2,1.1l-1.8,1.2l5.6,2.7l-0.8,1.8l-3.8,0.4l-1.1-1.6l-4.8-0.7l-5.5-1l-3.9-4l-2.9-3.5
l-2.4-5.6l-7-2.8l-4.9,1.8l-3.6,2.1l0.3,4.7l-4.6,2.2l-3.1-1.1l-5.7-0.2l-4.6-5.2l-5.5-1.3l-1.6,1.8l-7.1,0.2l2.8-5.2l3.7-1.8
l-1-6.9l-2.3-5.3l-10.6-5.4l-4.6-0.5l-8.2-5.9l-1.8,3.1l-2.1,0.5l-1.2-2.3l0.1-2.8l-4.2-3.1l6.2-2.3l4,0.1l-0.4-1.6l-8.3-0.1
l-2.1-3.7l-5-1.2l-2.3-3.2l7.6-1.5l2.9-2.1l9,2.6l0.8,2.4l1.3,10.3l5.7,3.8l4.9-6.8l6.6-3.8h5l4.7,2.2l4.1,2.3L1569.3,512.8
L1569.3,512.8z M439.5,211.6l-6.3,3l-6,2.1l-5.9,1.8l-3.9,3.7l-1.4,1.3l-1.1,3.3l0.6,3.3l2.1,0.1l0.2-2.2l1.1,1.4l-1,1.7l-3.8,1
l-2.4-0.1l-4.1,1.1l-2.4,0.3l-3.1,0.3l-4.8,1.8l7.9-1.2l1.1,1.2l-7.8,1.9h-3.3l-1.7,1l-1.1,4.8l-5.2,4.9l0.1-1.6l-2.3-2l0.1,3.5
l0.9,1.1l-0.6,2.5l-2.4,2.5l-4.4,5.1l2.5-4.6l-2-2.5l1-5.3l-1.9,2.7v4.1l-3.2-1l3,2.1l-1.5,6.2l1.3,2.6l-0.9,6.5l-4.6,4.8l-6,1.9
l-4.4,3.8l-2.8,0.4l-3.3,2.3l-1.3,2.2l-6.9,4.1l-3.7,3l-3.5,3.8l-1.8,4.5l-0.1,4.4l0.7,5.4l1.5,4.5l-0.5,2.7l1.2,7.4l-0.9,4.3
l-0.7,2.5l-2,3.9l-1.7,0.8l-2.6-0.8l-0.4-2.8l-1.8-1.4l-2-5.5l-1.6-4.9l-0.4-2.5l2-4.2l-0.8-3.5l-3-5.3l-1.9-1l-6.1,2.9l-2.9-3.3
l-3-1.5l-6.2,0.8l-4.7-0.7l-4.2,0.4l-2.4,1l0.5,1.7l-0.7,2.5l0.9,1.3l-1.3,0.8l-1.7-0.9l-2.4,1.2l-3.8-0.2l-3.3-3.4l-4.9,0.8
l-3.5-1.4l-3.5,0.4l-4.9,1.5l-6.1,4.7l-6,2.7l-3.7,3.1l-1.9,2.8l-1,4.4l-0.4,3.1l0.6,2.2l-3.1,5.6l-1.8,4.5l-1.8,8.6l-1.1,3.1
l0.5,3.4l1.2,3.1l0.4,5l3,4.7l0.7,3.6l1.8,3.2l5.6,1.7l1.9,2.6l5.1-1.8l4.3-0.6l4.3-1.1l3.6-1.1l3.9-2.6l1.8-3.7l1.2-5.4l1.2-1.8
l4-1.7l6-1.5l4.8,0.2l3.4-0.5l1.2,1.3l-0.6,3.1l-3.5,3.8l-1.8,3.9l0.9,1.1l-1.2,2.7l-2,5l-1.4-0.6l-0.3,1.8l-1.3,2.7l-0.5,3.3
l-0.7,3.8l-1.3,1.7l-2.4,2.4l1.7,1.1l2.1-0.1l1.9-0.9l1.6-0.1l3.9,0.8l2.6-0.2l1.8-0.7l2.5-0.3l2.8,0.2l3.6,0.4l2.4,1.3l1.5,1.4
l1.9,1l1.3,1.8l-1,2l0.3,2.3l-1.4,2.2l-0.9,2.5l-0.4,2.8l0.2,1.7l-0.1,2.8l-1.6,3.4l0.2,1.7l-1.2,1.6l0.1,1.7l0.8,1.1l1.2,3.4l2,2.6
l2.4,2.7l1.9,2.3l-0.2,1.3l2.3,0.3l2,1.1l2.7-0.5l2.5-1.6l3.5-1.3l2-1.9l3.1,0.4l2.8,0.8l2.5,1.1l1.7,1.9l2,1.8l0.4,4.7l-1.1,1.9
l-1.8-0.5l-0.9,3.1l-1.8-1.8l-1-3.5l1.5-1.7l-2.4-2.5l-2.7-1.8l-2.5,0.4l-1.2,2.2l-2.3,1.6l-1.9,1.6l2.5,3.4l-1.5,0.9l-3.5,1.2
l-0.8-3.8l-0.8,1.1l-1.8-0.4l-1-2.5l-2.3-0.5l-1.4-0.7h-2.4l-0.3,1.4l-3.5-2.4l-1.1-1.3l0.7-1.1l-0.1-1.4l-1.5-1.5l-2.1-1.3
l-1.9-0.8l-0.3-1.9l-1.4-1.1l0.3,1.8l-1.2,1.6l-1.2-1.8l-1.7-0.6l-0.7-1.3l0.1-2l0.9-2l-1.5-0.9l1.4-1.2l-1.8-2l-2.5-2.6l-1-2.1
l-2.2-2l-2.6-2.9h1.6l-0.3-2.4l-1.7-0.6l-0.7,1.5l-3.3-0.1l-1.9-0.6l-2.2-1.3l-3.1-0.4l-1.4-1.3l-2.8-1.1l-3.4-0.1l-2.5-1.3
l-2.7-2.6l-5.5-6.9l-2.6-2.1l-4.4-1.6l-3.1,0.4l-4.8,2.4l-2.9,0.7l-3.7-1.7l-4-1.2l-4.8-2.9l-4-0.9l-5.9-3l-4.2-3l-1.2-1.7l-3-0.4
l-5.4-2l-1.9-2.9l-5.3-3.6l-2.2-4l-0.8-3.1l1.9-0.7l-0.3-1.8l1.6-1.6l0.3-2.2l-1.4-2.9v-2.5l-1.3-3.2L191,345l-4.5-4.9l-1.9-4
l-4.1-2.6l-0.7-1.5l1.8-4l-2.5-1.5l-2.4-3.1l-0.3-4.4l-2.7-0.5l-2.3-3.4l-1.7-3.1l0.4-2l-1.5-4.7l-0.4-4.9l0.9-2.4l-3.1-2.5
l-1.9,0.2l-2.4-1.7l-1.8,2.6l-0.1,3l-1,4.8l1.1,2.6l2.7,4.4l0.4,1.4l0.8,2.7l1,4l1.2,1.6l0.6,2.3l2.6,3.2l0.5,5.9l1,2.8l0.9,3
l-0.4,3.4l2.6,0.2l1.6,2.9l1.5,2.8l-0.4,1.2l-2.7,2.3l-1.7-3.9l-2.9-3.6l-3.3-3.1l-2.5-1.6l1.2-4.7l-0.1-3.4l-2.1-2l-3.1-2.9
l-1.9-0.8l-2.9-1.5l-2.3-3.7l2.7-0.2l2.7-2.3l1-2.9l-2.9-4.5l-2.6-1.8l-0.8-4l-0.7-4.1l-0.7-5.1l-0.2-5.7l0.3-3.2l-2.1-3.7l-2.2-0.7
l0.1-1.9l-2.8-0.3l-1.3-1.7l-4.6-0.7l-1-1l0.8-3.5l-2.5-6.5l-0.4-9l0.8-1.5l-1.3-2.2L133,248l1.9-5.2l-1-3.4l3.9-5.2l2.8-5.3
l1.1-4.7l5.4-5.8l8-11.2l4.2-8.3l1.8-5.4l0.4-2.9l1.4-1.2l5.8,2.1l-1.1,5.9l2.3-1.6l2.4-5.1l1.6-5.1l-6.1-6.1l-1.6-2.7l-6.9-2.5
l1.3-5.5l3.6-3.7l-4.1-2.6l3.1-4.9l-2.1-4.4l2.5-3.1l-0.6-2.2l-2.5-1.9l3.3-5.2l-1.6-4.9l2.7-5.5l-4.1-0.4l-7.1-0.2l-3.7-1.7
l-3.3-6.1l-3.2-1.1l-5.7-2l-6.7,0.5l-6-2.7l-2.6-2.5l-6.3,1.2l-3.5,4.1l-2.9,0.3l-6.5,1.3l-6.1,1.9l-6.4,1.3l3.2-3.5l8.3-5.7
l6.7-1.8l0.4-1.4l-9.2,3.2l-7.4,3.9l-11.1,4.1l0.2,2.9l-8.9,4.2l-7.6,2.5l-6.6,1.8l-3.9,2.6l-10.5,3.1l-4.5,2.8l-8.1,2.6l-2.8-0.5
l-6.1,1.7l-6.9,2l-6,2l-10,1.7l8.5-3.8l6.6-1.8l8.5-3.3l6.5-0.7l4.9-2.4l10.3-3.6l2.3-1.2l5.7-2.1l5.7-4.5l6.2-3.4l-7.2,1.8
l-5.2,1.1l0.4-3l-3.6,2.1l1.6-2.9l-7.2,2.3h-2.8l3.6-3.5l3.3-2.1l-0.4-2.1l-7.2,1.1l-0.5-2.7l-1.4-1.4l4-3.3l-0.3-2.4l5.8-3.4
l7.6-3.1l5.3-3l4.1-0.4l1.8,0.9l7-2.7l2.5,0.5l5.5-1.8l2.5-2.6l-1.1-1l5.9-2.1h-2.7l-6.1,1.2l-2.9,1.3l-1.8-1.3l-6.9,0.7l-4.5-1.4
l1.2-2.2l-1-3.3l9.1-2.3L86.7,82h3.4l-4.3,2.8l9.2-0.2l1.2-3.5L93.9,79l0.8-2.7L94,74l-3.2-1.7l6.3-2.9l7.6-0.1l8.6-2.5l4.8-2.6
l7.8-2.5l4.8-0.6l11-2.3l3.2,0.3l10.2-2.8l4.4,1.1l-0.5,2.4l3.3-1l6.3,0.3l-2,1.2l4.9,0.9l4.8-0.5l6.2,1.6l7.1,0.5l2.2,0.7l6.5-0.8
l4.1,1.6l3.5,0.7l5.9,1.3l3.3,2.6l3.7,0.5l6.2-2.3l7-1.6l5.2,0.6l8.8-2.3l8.2-1.4l0.2,2.2l4.5-1.2l3.8-2.5l2.1,0.6l1.4,4.7l9.4-3.6
l-3.9,4.1l5.9-0.9l3.3-1.6l4.5,0.3l3.8,2.3l7.5,1.9l4.7,1l4.4-0.4l2.8,2.8l-8.5,2.6l6.4,1.2l11.8-0.6l4.4-1l1.4,3.3l7-2.8l-2-2.3
l4.4-1.8l5.2-0.3l3.8-0.5l2.1,1.3l1.5,2.9l5-0.4l5.1,2.4l7.2-0.8l5.9,0.1l2.4-3.4l4.5-0.9l4.8,1.8l-4.2,5.2l6.2-4.4l3.2,0.2l6.3-5.5
l-1.6-3.3L388,63l5.5-5.8l8.1-3.8l4.5,0.8l1.9,2.3l0.4,6l-5.7,2.6l6.6,1l-4.3,5.5l8.7-4.2l2.2,3.5l-4.2,4l1.3,3.7l7.2-4l6.5-4.6
l4.6-5.9l5.5,0.4l5.4,0.8l3.5,2.6l-1.7,2.7l-5,2.9l0.8,2.9l-2.3,2.7l-10.8,3.8l-6.5,0.9l-3.2-1.7l-3.2,2.8l-7.4,4.7l-2.9,2.4
l-7.7,3.8l-6.4,0.4l-5.1,2.4l-2.9,3.7l-5.6,0.7l-8.6,4.6l-9.4,6.5l-4.9,4.6l-4.9,6.8l6,1l-1.5,5.5l-0.7,4.5l7.1-1.2l7,2.6l3.3,2.2
l1.7,2.9l4.8,1.6l3.6,2.5l7.5,0.4l4.8,0.6l-3.6,5.2l-1.7,6l0.1,6.8l4.4,5.8l4.6-1.9l5.6-6.3l2.2-9.6l-1.7-3.2l9-2.8l7.5-4.2l4.7-4.1
l1.7-4l-0.5-5l-3.1-4.5l8.8-6.1l1-5.3l3.8-9l3.9-1.3l6.6,1.6l4.2,0.5l4.5-1.5l3,2l3.6,3.3l0.1,2.2l7.7,0.5l-2.6,4.9l-2.2,7.3l3.7,1
l1.6,3.4l8.1-3.2l7.4-6.5l4.3-2.7l1,5.2l2.6,7.5l2,7.1l-3.4,3.7l4.8,3.4l2.8,3.4l6.9,1.6l2.3,1.9v5.1l3.4,0.8l1.1,2.3l-2,6.8
l-4.2,2.3l-4.2,2.1l-8.7,2.2l-7.8,5.1l-8.6,1l-10-1.3l-7.3-0.1l-5.2,0.5l-5.7,4.4l-7.2,2.7l-10.1,8.3l-7.8,5.7l4.6-1l10.9-8.2
l12.1-5.2l7.6-0.6l3.3,3.1l-6.1,4.1l-0.6,6.8l0.1,4.6l5.5,3.1l8.6-0.8l7.1-7l-1,4.5l2.6,2.2l-7.5,4.1l-12.2,3.7l-5.8,2.4l-7.1,4.5
l-3.7-0.5l1.5-5.2l10.4-5.1l-8.1,0.2l-5.9,0.8L439.5,211.6L439.5,211.6z M1443.2,229.5l0.6,3.1l0.8,4.6l-1.4,2.9l-4.7,2.7l0.9,3.8
l2.9,1l3.8,2.6l4.5,4l4.3,3.9l3,4.2l3.3,7v3.6l-4.9,1.6l-2.9,2.8h-4.6l-1.9-3.4l-0.8-4.6l-5.3-7l3-0.9l-6.1-6h-4.9l-3.6-2.1v-4.5
l-1.4-4.7l-3.7-0.6l-2.9-1.8l-6.4,2l-2.4,2.9l-4.8,1.8l1.4-3l-2.3-2.5l1.9-4.2l-4.4-3.3l-3.4,2.2l-3.6,4.4l-1.2,4.1l-5,0.3l-1.2,3
l4.7,4.4l4.7,1l1.5,2.9l4.9,1.9l3.9-4.6l5.8,2.5l3.4,0.2l2.4,3.4l-6.7,1.8l-1,3.5l-3.8,3.2l-0.9,4.6l7.1,3.5l4.6,6.3l5.4,5.8
l5.4,4.9l1.6,4.7l-2.8,1.7l2.5,3.4l3.8,2l0.8,5.1l0.1,5.1l-2.8,0.5l-2.1,6.9l-2.3,8.4l-3.3,7.6l-6.3,5.9l-6.6,5.3l-6.1,0.7l-2.8,2.9
l-2.3-2.1l-2.5,3.2l-7.2,3.2l-5.6,0.9l-0.8,6.7l-3,0.4l-2.2-4.6l0.9-2.5l-7.7-2l-2.4,1l-6.5,5.5l-3.7,6l-0.5,4.4l5.3,6.7l6.4,8.3
l5.6,4l4.1,5.1l4,11.7l0.4,11.2l-4.3,4.2l-6.1,4.1l-4.1,5.3l-6.6,5.9l-2.3-4l1.3-4.3l-4.5-3.7l-4.7-0.9l-2.6-3.3l-3.4-6.6l-5.3-2.9
l-4.7,0.1l0.3-5l-4.9,0.1l0.2,7l-2.2,9.3l-1.4,5.6l0.7,4.6l3.7,0.2l2.6,5.8l1.4,5.5l3.3,3.6l3.4,0.8l3.1,3.3l4.8,4.4l2.6,4.3
l0.5,4.3l-0.5,2.9l0.6,2.1l0.5,3.8l2.1,1.8l2.3,5.6l-0.1,2.1l-4,0.5l-5.4-4.8l-6.7-5l-0.8-3.2l-3.4-4.3l-1-5.3l-2.1-3.4l0.4-4.7
l-1.4-2.7l-2.4-2.4l-1.1-3.2l-3.2-3.5l-2.9-3.1l-0.7,3.8l-1.2-3.6l0.3-3.9l1.3-6.1l-0.9-4.8l1.3-4.8l-2.2-3.8l-0.2-6.9l-2.6-3.3
l-2.6-7.6l-2-8.1l-3.1-5.2l-3.2,3.1l-5.8,4.6l-3.2-0.6l-3.6-1.5l0.9-7.9l-1.9-5.9l-5.4-7.4l0.4-2.2l-3.4-0.9l-4.6-5.2l-1.1-5.1
l1.6-3.6l0.5,4.6l-1,4.1l-2-3.3l-0.8-3.2l-1.5-3.1l-2.8-3.7l-5.1-0.2l0.9,2.6l-1.3,3.5l-2.5-1.3l-4.5-0.1l-0.5,2.4l-3.7-0.1
l-6.6,1.3l0.9,4.8l-2.4,3.8l-7.4,4.2l-5.3,7.5l-3.7,4l-5,4.2l0.3,2.9l-2.6,1.6l-4.8,2.3l-3.7,5.2l1.9,8.2l0.7,5.3l-1.9,6.1l0.7,10.8
l-2.9,0.3l-2.2,4.8l1.8,2.1l-5,1.8l-1.7,4.4l-2.2,1.8l-5.5-6l-3.1-8.9l-2.5-6.4l-2.2-3l-3.4-6.1l-2-8l-1.3-3.9l-5.8-8.8l-3.6-12.3
l-2.5-8.1l-0.8-7.8l-1.7-5.9l-7.6,3.8l-4-0.8l-8-7.7l2.4-2.3l-1.9-2.5l-7.1-5.4l-4.2-1.6l-2.1-4.6l-4.9-4.8l-9.9,1.2l-8.9,0.1
l-7.6,0.9l-10.5-1.9l-6.1-1.5l-6.3-0.8l-3.2-7.8l-2.8-1.1l-4,1.1l-5.1,3.1l-6.9-2.1l-6.1-4.9l-5.5-1.8l-4.3-6.1l-5.1-8.4l-2.8,1
l-3.7-2.1l-1.7,2.5l-3.2-0.4l1.4,2.8l2.1,6.3l2.7,5.5l2.9,1.4l1.2,2.3l4,2.6l0.6,2.7l-0.4,2.1l1,2.1l1.7,1.8l0.9,2.1l0.2-3.1
l1.1-3.3l3.3,1.3l0.5,3.7l-0.8,3.8l1.2,2.4l5.5,0.4l4.6,0.2l3.3,0.2l3.3-4.3l3.7-4.1l3-3.9l1.3,2.2l1.3,5l2.9,4.3l3.3,2.3l4.1,0.9
l3.3,1.1l2.9,3.7l1.6,2.1l2.2,2.2l-1.6,3.8l-2.9,3.8l-1.7,4.4l-3.5,1.2l-0.6,3.2l1,4.2l-3,0.8l-3.3,2.4l-0.2,3.1l-4.6,1.3l-2.1,1.6
l0.2,2.5l-2.5,1.8l-3.1-0.6l-3.6,2.2l-6.4,2l-0.9,2.9v2.1l-5.3,2.7l-8.8,3l-4.7,4.5l-4.1-0.1l-3.1,2.7l-3.5,1.2l-4.7,0.3l-2.5,2.1
l-2.3,2.1l-2.7-0.2l-5.7,0.6l-1.6-3.8v-3.4l-2.3-6.6l-1.7-2.6l0.4-3.2l0.2-4l-0.9-2.8l-1.8-1.9l-0.6-2.5l-3-2.3l-3.2-5.3l-1.9-5.2
l-4.1-4.4l-2.5-1l-4-6.1l-0.9-4.4v-3.8l-3.6-7l-2.8-2.5l-3-1.3l-2.1-3.7l-1.5-4.7l-4.2-6.2l-3.7-5.1l-3.2-4.4h-2.7l0.6-3.5V312
l0.2-3.4l-1.3,2.5l-0.7,4.8l-1.2,3.3l-3.2-0.9l-2.7-2.9l-5.1-8.5l2.8,6.7l3.9,6.4l4.8,9.9l2.3,3.5l2,3.6l5.3,7l-0.6,5.3l6.8,5.7
l3.1,7.5v7.7l2.5,7.6l2.2,1.6l3.1,2.4l3.7,7.3l1.8,5.9l3.2,3.1l7.9,6l3.2,3.6l3.2,3.7l1.9,2.2l2.8,1.9l1.4,1.9l-0.1,2.7l-3.1,1.5
l2.5,1.7l-2,3.4l-1.1,2.3l2.2,3.5l2.1,3.1l2.2,2.2l18.5,7.6l4.8-0.1l-15.4,19.1l-7.4,0.3l-4.9,4.5l-3.6,0.1l-1.5,2l-4.8,7.2
l0.2,23.2l3.3,5.3l-4,2.5l-1.4,2.7l-3,4.9l-1.8,2.6l-1.2,4.2l-2.2,2.1l-2.8,7.9l0.3,3.6l3.5,2.3l-1.5,5.5l-0.1,5l1.8,3.9l2.2,6.3
l2,1.4l0.8,2.9l-0.4,6.3l0.4,5.6l-0.2,9.9l0.9,3.1l-1.9,4.6l-2.4,4.4l-3.7,3.9l-5.2,2.5l-6.4,3.1l-6.7,6.8l-6.3,5.7l-2.3,1.4
l-0.7,4.6l2.4,4.8l0.8,3.7l0.5,7.8l-1.1,3l0.3,3.7L969,652l-4.6,2.2l-6.8,3.4L955,660l0.3,2.6l0.7,3.8l-1.7,4.7l-1.1,5.2l-1.6,2.9
l-3.9,3.2l-3.7,4.2l-1.8,3.2l-3.4,4.6l-6.7,6.5l-4.1,3.8l-4.3,2.9l-5.8,2.5l-2.7,0.3l-4,0.8l-2.7,1.2l-5.7-1.2l-3.3,0.8l-7.8,2.2
l-4.6,1l-3.5,2.4l-4.5-2.1l-4.2-2.1l-0.4-5.5l-1.4-4.2l1.9-6l-3.2-6l-2.5-5.4l-3.5-8.2l-3.9-4.8l-1.9-4.7l-1-6.2l-1.2-4.6l-1.6-9.8
v-7.6l-0.6-3.5l-2.1-2.6l-2.7-5.2l-2.8-7.7l-1.1-4l-4.4-6.2l-0.2-4.9l-0.5-4l0.9-5.5l2-5.9l0.3-2.7l1.9-5.7l1.3-2.6l3.3-4.2l1.8-2.8
l0.7-4.8l-0.3-3.6l-1.6-2.2l-1.5-3.9l-1.3-3.8l2-3.9l-1.6-6.1l-1.1-4.3l-2.8-4l-0.3-3.2l-1.4-4.8l-4.6-6.7l-5.8-6.4l-3.7-5.3
l-3.4-6.6l0.2-2.1l1.3-2l1.3-4.6l1.1-4.8l0.9-8.1l0.8-5l-2.2-4.2l-2.6-1.1l-1.1-2.8l-1.4-2.7l-5.8,2.3l-4.3,1.1l-4.5-0.2l-3-3.9
l-1.9-4.6l-3.9-4.2h-9.2l-4.6,0.8l-4.6,1.3l-8.7,3.8l-3.2,2.1l-5,1.9l-5-1.8l-6.5-1.2l-3.6,0.1l-6.6,1.1l-3.9,1.8l-5.6,2.4l-8.3-3.2
l-5.1-4.8l-4.8-3.5l-3.7-4.1l-7.2-4.1l-2.9-9.4l-2.4-3.7l-2.1-2.5l-2.8-2.1l-0.7-2.8l-2.4-2.5l-2.9-2.6l-3.6-2.3l-1.7-2.3l-0.9-5.2
l0.2-4.5l-5.5-6.4l5.3-3.6l0.9-8.2l1.7-4.6l1.8-6.9l-1.9-7.1l1.9-4.8l-4.4-4.1V360l0.3-3l2.2-1.7l1.9-3.3l-0.3-2.2l2-4.5l3.1-4
l3.5-4.8l0.2-3.3l2.1-4l3.7-2.3l3.7-6.6l2.8-2.4l5.1-0.7l4.4-4.4l2.7-1.7l4.7-5.3l-1.1-7.9l2.1-5.4l0.9-3.4l3.5-4.3l5.4-2.9l4-2.6
l3.7-6.7l1.8-3.9h3.9l3.1,2.7l5-0.4l5.5,1.4l7.4-3.4l5.7-1.2l3.4-2.6l5-2l8.9-1.2l8.7-0.5l2.7,1l4.9-2.6h5.6l2.2,1.5l3.6-0.4
l5.6-2.6l3.7,0.7l-0.1,3.3l4.4-2.4l-2.1,4.5l0.1,3l1.9,1.6l-0.5,5.6l-3.4,3.2l1.1,3.5l2.8,0.1l1.5,3l8.4,3.2l6.7,0.5l7.2,2.8
l2.8,5.7l4.9,1.2l7.7,2.7l5.9,3.1l2.5-1.6l2.4-3l-1.5-4.8l1.5-3.1l3.7-3l3.6-0.8l7.3,1.3l2,2.8l3.8,1.1l5.4,0.8l1.5,2.1l7.1-0.2
l5.3,1.7l5.5,1.9l2.6,1l3.9-2l2.1-1.8l4.7-0.6l3.9,0.8l1.7,3.2l1.1-2.1l4.4,1.5l4.2,0.4l2.5-1.6l1-2.5l1.1-2.9l0.6-4.8l2-6.9
l2.2-4.7l-1.1-5l1-2.6l-2.3-3l1.7-2.4l-3.2,0.6l-4.6-1.5l-3.1,3.7l-8,0.7l-4.6-3.5l-5.7-0.2l-0.9,2.7l-3.6,0.8l-5.4-3.5l-5.8,0.1
l-3.7-6.4l-4.2-3.6l2-5l-3.6-3l5.1-6.1l7.9-0.2l1.6-4.8l9.9,0.8l5.6-4l5.7-1.7l8.5-0.2l9.7,4.4l7.8,2.4l5.8-1l4.5,0.6l5.5-3.2
l0.3-2.7l-2.1-4.2l-3.4-2.3l-3-0.7l-2.2-1.9l-7.4-5.1l-6.2-2.4l-5-3.6l3.4-1l3.2-5.2l-3.2-2.4l7.1-2.3l-12.6,0.9l-3.8,1.3l-3.9,2.3
l1,3.9l2.7,1.5l5.1-0.4l-0.6,2.2l-5.3,1.1l-6.3,3.6l-3-1.3l0.6-2.9l-5.8-1.8l5.2-3.3l-9.7-2.9l-0.7-2.4l-4.5,0.8l-1.3,3.4l-3.3,4.6
l-1.9,2.9l-2,6.9l-2.2,2.5l-1.3,4.4l2.1,3.5l0.9,2.4l4.7,2l-6.8,1.9l-2,1.9l-3.9,3.4l-1.9-2.9l-3.3-1.5l-2.7-0.6l-6.1,1.6l3.9,3.6
l-2.5,1h-2.9l-3-3.2v3.6l3.7,4.6l-2.1,1.8l3.3,2.5l2.7,1.8l0.4,3.7l-5-1.7l1.9,3.3l-3.3,0.7l2.4,5.7l-3.4,0.1l-4.6-2.9l-2.3-5.1
l-1.3-4.3l-2.3-3l-2.9-3.6l-1.5-2.3l-3.2-3.5l-0.7-3l0.1-4.3l-0.5-2.9l-2.7-2.5l-3.2-1.1l-4.1-2.4l-3.1-2.2l-4.8-1.8l-4.6-4.5
l-1.5-4.3l-3.6-1.8l-1.3,2.6l-1.7-2v-2.2l-2.8-1.5l-3.9,2.2l0.1,5l1.7,2.9l4.9,3.1l2.9,5.1l6,5l4-0.1v2.6l4.8,2.3l3.9,1.9l4.7,3.2
l-0.2,3.5l-3.1-3l-4.5-1l-1.9,4.1l3.9,2.3l-0.4,3.3l-4.6,6l-4.7,3.5l0.9,2l-0.9,3.3l-4.2-2.4l-2.7-0.7l-7.5-3.3l0.6-3.4l6.2,0.6
l5.2-0.7l3.3-0.4l2.4-3.5l-1.2-5l-1.8-3.2l-3.9-3.5l-3.4-1.2l-2.3-2.5l-3.9-0.4l-4.2-2.8l-4.9-4l-3.6-3.6l-1.9-6l-2.6-0.7l-4.2-2.1
L817,218l-6.6,3.9l-10-1.6l-7.4,2l-0.5,3.7l0.3,3.5l-4.8,4.2l-6.6,1.3l-0.5,2.1l-3.1,3.5l-2,5.1l2,3.7l-3,2.9l-1.2,4.2l-3.9,1.3
l-3.8,5h-6.7l-5-0.1l-3.3,2.3l-2.1,2.5l-4.5-2.8l-1.4-3.7l-4.8-1l-2.2,1.7l-2.7-0.9l-2.8,0.7l1-5.1l-0.4-4l-3.5-3l0.6-4.2l2.2-2.3
l0.4-2.5l1.3-3.8l-0.1-2.7l-0.9-2.2l-0.1-2.1l0.4-4.4l-2-2.6l7.4-4.5l6.2,1.1h6.8l5.4,1l4.2-0.3l8.2,0.2l2.7-3.7l1-12.3l-5.1-6.5
l-3.6-3.1l-7.5-2.4l-0.4-4.6l6.4-1.3l8.2,1.6l-1.4-7l4.6,2.6l11.3-4.8l1.4-5.1l4.3-1.2l3.4-1.3l6.8-10.5l6.5-2.6l-6.5,2.6l6.1-2.1
l4.3-0.3l4.8-1.6l4-1.7l-1.2-2.2l-0.4-3.4l-2.1-3.4l-0.4-6.1l1.9-3.3l4-0.4l5.1-3.3l0.1,3l-1.3,2l3.3,2.5l-1,2.3l-4.5,3.6l1.5,3
l0.2,2.3l4.9,1.4l0.1,2.1l4.7-1.1l2.5-1.7l5.5,2.4l2.4,1.9l3.1-1.7l7.1-2.8l5.7-2.1l4.8,1.1l5.1,1.5l0.7-2.6l6.3-2l-1.7-5l-0.5-4.5
l1.7-3.7l4-2.1l4.4,4.5l3.6-0.1l0.2-4.6l-0.1-3.5l-4.8-1.3l-0.9-3.4l5.4-1.6l5.5-0.9l5.1,1c3.4-1.8,5.1-3.1,7.7-4.2l-3.9-2l-8,0.5
l-7.5,2.1l-7.1,1.2l-3.1-3.1l-4.7-1.9l0.1-5.7l-2.9-5.2l1.5-3.3l3.3-3.6l8.7-6.1l2.6-1.2l-0.9-2.3l-6.4-2.6l-7,1.5l-3.4,3.9l1.2,3.5
l-6.1,4.5l-7.7,4.9l-2.1,8.1l3.6,4l4.8,3.3l-3.3,6.6l-4.6,1.3l-0.6,10l-2,5.5l-5.7-0.5l-2.2,4.7l-5.3,0.3l-2-5.7l-4.4-6.7l-4.2-8.4
l-3.3-3.6l-8.5,6.8l-6,1.4l-6.4-3l-1.9-6.3l-1.9-13.4l3.9-3.6l11.2-4.8l8.1-5.8l7.1-7.8l8.8-10.7l6.4-4l10.1-6.8l8.5-2.3l6.7,0.3
l5.1-4.4l7.3,0.3l6.9-1.1l13.6,3.9l-4.8,1.4l5.4,3.3l3.6-1.8l7.6,3.2l11.6,1.3l17.6,6.1l4,2.6l1.6,3.6l-3.7,2.9l-6.4,1.5L919.3,81
l-2.9,0.7l8,4l1,2.6l1.9,5.8l6,1.7l3.8,1.4l-0.2-2.7l-3.3-2.4l2.1-2.1l11.4,3.5l3.2-1.4l-4.2-4.1l8-5.4l4,0.3l4.6,1.9l1.1-3.8
l-4.7-3.3l0.8-3.2l-4.3-3.4l12.2,1.8l3.6,3l-5,0.7l1.2,3l4,1.9l6-1.2l-0.4-3.5l7.6-2.6l12.3-4.6l3.2,0.2l-2.5,3.3l5.3,0.6l2.1-1.9
l7.6-0.1l5-2.2l6.4,3.2l2.7-3.6L1021,66l14-0.1l6.6,1.7l18.5,6.1l1-2.8l-5.8-2.9l-6.1-1.6l-0.1-2.6l-4.7-4.1l3.1-6.4l-0.5-4.7
l13.4,0.4l2.9,2.9l-0.8,4.2l3.6,1.6l3.7,3.7l3.8,7.3l6.7,3.3l0.5,3.6l-3.5,7.8l5.3,0.8l4.2-3.4l-0.5-2.7l1.9-2.6l-4.4-3.1l-0.4-3.5
l-4.7-0.5l-2.9-2.9l-0.4-5.4l-8.1-4.2l4.6-3.5l-3.6-3.7l6,2.7l2.1,5l4.9,1l-4.5-3.8l5.1-2l7.9-0.3l9.6,3l-6.9-4.3l-4.8-5.4l5.8-1.1
l9.5,0.3l7.7-0.7l-5.5-2.6l1.2-3.3l4.2-0.2l4.9-2.4l9.2-0.7l9-1.8l4.3,1.1l5.3-2.6l6.9,0.1l-1.5-2.1l1-2l6.1-1.9l8.2,1.5l-3.4,1.2
l9.2,0.7l4.1,2.4l12.7-1.1l11.5,2.3l5.5,1.9l2.4,2.5l-2.3,1.5l-6.6,2.8l4.5,2.2l7.2,1.2l8.1,2.3l5.4-2.1l13.3,0.9l3.9,2.3l17.2,0.8
l-4.7-3.9l9.3,0.9h6.1l9.8,2.6l6,3.3l0.4,2.2l10,4.1l8.9,2.1l-3-5.5l9.3,2.4l5-1.4l9.7,1.6l8.5-0.8l-9.3-4.8l2.1-2.2l40.1,3.4l7.8,3
l16.1,4l14.7-1l9.2,0.9l6.5,2.2l5.1,3.8l7.2,1.6l3.9-1.1l7-0.2l9.2,1.1l6.8-0.6l14.2,4.8l2.5-1.8l-8.5-3.4l-1.8-2.3l15.2,1.4
l7.9-0.3l15.7,2.6l9.4,2.3l32.9,21.9l-2,2.5l-6.1-0.4l8.1,3l9.1,4.6l4.1,1.6l3.8,2.3l-8.9,0.3l-6.5,4.3l-3.1,0.7l-1.5,4l-2,3.6
l1.7,2.6l-11.4-4l-6.1,4.6l-4.7-2.2l-1.3,2.5l-7-0.8l3.1,3.9l1.1,5.6l2.9,2.4l6.7,1.4l9,8.6l-4.1,0.2l3.4,5l4.7,2.6l-5,3.1l5.4,6.9
l-5.7,1.5l4.5,6.2l-1.6,5.7l-5.7-4.3l-10.5-8.8l-16-13.4l-6.4-8.3l0.1-3.6l-2.8-2.7l5.7-1.3l-0.3-7.5l0.8-6l2.4-4.6l-6.5-8.1
l-4.7,0.5l3.2,4.7l-3.6,6.4l-12.1-7.1l-9.1,1.9v9.8l7.7,3.5l-8.4,1.6l-6.4,0.6l-4.3-4.3l-7.9-0.8l-2.5,2.8l-14.9-1l-13.1,1.8
l-3.5,11.5l-4.9,14.1l8,0.7l5.7,3.8l5.8,1.3l0.3-3l5.6,0.4l12.7,6.7l4.7,5.1l1.3,6.2l5.5,7.3l5.2,9.9l-1,9l1.3,4.3l-1.9,7.2
l-2.1,7.1l-1,3.7l-4.4,3.6l-3.2,0.1l-5.1-3l-4,4.5l-5.9-2.4l4.3,4.4L1443.2,229.5L1443.2,229.5z M1018.6,218.9v3.7l7.3,7.5l7.4,8.6
l-1.1,10.6l2.6,8.7l4.9,1l4.1,2.6l9.3,1.4l7.7-1.4l-2.2-5.6v-6.7l-4.4-3.5l-4.1-4.4v-4h5.5l3-2.8l-5.5-6.5l-4,1.1l-3.2-5.1l-6.2-1.5
l-3.3-6.1l-4.2-1.7v-1.7h4.2v-4l3.3-1.1h4.1l-2.1-9.3l-5.3,1.6l-4.2-3.1l-4.1,3.1l-6.6,1.2v4.2h-4.5l-4.4,7.4L1018.6,218.9
L1018.6,218.9z"/>
<g>
<circle class="st1" cx="1511.8" cy="716.5" r="8"/>
<circle class="st1" cx="1695.5" cy="729.8" r="8"/>
<circle class="st1" cx="970.1" cy="292" r="8"/>
<circle class="st1" cx="799.8" cy="162.8" r="8"/>
<circle class="st1" cx="387.4" cy="610.9" r="8"/>
<circle class="st1" cx="857.8" cy="118.9" r="8"/>
<circle class="st1" cx="462.4" cy="654.7" r="8"/>
<circle class="st1" cx="902.3" cy="252.2" r="8"/>
<circle class="st1" cx="327" cy="279.8" r="8"/>
<circle class="st1" cx="1053.2" cy="326.7" r="8"/>
<circle class="st1" cx="787.7" cy="230.9" r="8"/>
<circle class="st1" cx="879.5" cy="209.1" r="8"/>
<circle class="st1" cx="966" cy="278.6" r="8"/>
<circle class="st1" cx="1014.5" cy="282.1" r="8"/>
<circle class="st1" cx="1338.9" cy="406.9" r="8"/>
<circle class="st1" cx="1210.6" cy="410" r="8"/>
<circle class="st1" cx="319.7" cy="263.9" r="8"/>
<circle class="st1" cx="1609.6" cy="668.5" r="8"/>
<circle class="st1" cx="360.5" cy="464.1" r="8"/>
<circle class="st1" cx="1180.2" cy="372.4" r="8"/>
<circle class="st1" cx="413.6" cy="224.2" r="8"/>
<circle class="st1" cx="798.7" cy="171.4" r="8"/>
<circle class="st1" cx="872.6" cy="192.8" r="8"/>
<circle class="st1" cx="375.5" cy="220.8" r="8"/>
<circle class="st1" cx="1422.6" cy="462.5" r="8"/>
<circle class="st1" cx="1399" cy="345.1" r="8"/>
<circle class="st1" cx="1263.2" cy="349.8" r="8"/>
<circle class="st1" cx="789.6" cy="183.1" r="8"/>
<circle class="st1" cx="1470.9" cy="428.4" r="8"/>
<circle class="st1" cx="1375.7" cy="533.1" r="8"/>
<circle class="st1" cx="1381.7" cy="274.1" r="8"/>
<circle class="st1" cx="1282.2" cy="352.3" r="8"/>
<circle class="st1" cx="1353.2" cy="304.8" r="8"/>
<circle class="st1" cx="348.2" cy="269.7" r="8"/>
<circle class="st1" cx="1225.1" cy="448.4" r="8"/>
<circle class="st1" cx="347.4" cy="238.9" r="8"/>
<circle class="st1" cx="736.5" cy="281.5" r="8"/>
<circle class="st1" cx="836.1" cy="142.7" r="8"/>
<circle class="st1" cx="876" cy="710.3" r="8"/>
<circle class="st1" cx="1391.3" cy="314.5" r="8"/>
<circle class="st1" cx="1337.2" cy="299.3" r="8"/>
<circle class="st1" cx="391.8" cy="416.5" r="8"/>
<ellipse class="st2" cx="508.2" cy="656.6" rx="8" ry="8"/>
<circle class="st1" cx="1272.9" cy="342.2" r="8"/>
<circle class="st1" cx="996.9" cy="537.8" r="8"/>
<circle class="st1" cx="1195" cy="312.2" r="8"/>
<circle class="st1" cx="234" cy="239.9" r="8"/>
<circle class="st1" cx="258.5" cy="284.5" r="8"/>
<circle class="st1" cx="679.6" cy="400.2" r="8"/>
<circle class="st1" cx="954.4" cy="144" r="8"/>
<circle class="st1" cx="1059" cy="333.1" r="8"/>
<circle class="st1" cx="350.1" cy="225.2" r="8"/>
<circle class="st1" cx="747.3" cy="156" r="8"/>
<circle class="st1" cx="945.2" cy="682.6" r="8"/>
<circle class="st1" cx="809.7" cy="169.1" r="8"/>
<circle class="st1" cx="1079.7" cy="333.2" r="8"/>
<circle class="st1" cx="761.3" cy="140.8" r="8"/>
<circle class="st1" cx="1007" cy="238" r="8"/>
<circle class="st1" cx="393.9" cy="234.6" r="8"/>
<circle class="st1" cx="466.4" cy="715.7" r="8"/>
<circle class="st1" cx="839.9" cy="227.8" r="8"/>
<circle class="st1" cx="1430.3" cy="328.9" r="8"/>
<circle class="st1" cx="560.4" cy="518.1" r="8"/>
<circle class="st1" cx="818.8" cy="176.6" r="8"/>
<circle class="st1" cx="1399.2" cy="346.7" r="8"/>
<circle class="st1" cx="539.3" cy="639.3" r="8"/>
<circle class="st1" cx="431.8" cy="417.6" r="8"/>
<circle class="st1" cx="833.4" cy="130.6" r="8"/>
<circle class="st1" cx="521.9" cy="643.2" r="8"/>
<circle class="st1" cx="272.6" cy="401.1" r="8"/>
<circle class="st1" cx="807.6" cy="200.3" r="8"/>
<circle class="st1" cx="1035.8" cy="236.1" r="8"/>
<circle class="st1" cx="824.5" cy="154.8" r="8"/>
<circle class="st1" cx="1360.2" cy="359" r="8"/>
<circle class="st1" cx="888.8" cy="115" r="8"/>
<circle class="st1" cx="1426.8" cy="301.5" r="8"/>
<circle class="st1" cx="1403.8" cy="351.9" r="8"/>
<circle class="st1" cx="-93.3" cy="358.2" r="8"/>
<circle class="st1" cx="1389.7" cy="323.1" r="8"/>
<circle class="st1" cx="1212.2" cy="384.2" r="8"/>
<circle class="st1" cx="373.6" cy="245.7" r="8"/>
<circle class="st1" cx="262.1" cy="303.1" r="8"/>
<circle class="st1" cx="1440.2" cy="255.4" r="8"/>
<circle class="st1" cx="329" cy="240.8" r="8"/>
<circle class="st1" cx="1166.7" cy="280" r="8"/>
<circle class="st1" cx="925.6" cy="232.8" r="8"/>
<circle class="st1" cx="336.7" cy="299.9" r="8"/>
<circle class="st1" cx="1359.9" cy="483.6" r="8"/>
<ellipse transform="matrix(0.1602 -0.9871 0.9871 0.1602 440.0319 1358.1957)" class="st1" cx="1018.2" cy="420.5" rx="8" ry="8"/>
<circle class="st1" cx="931" cy="660.4" r="8"/>
<circle class="st1" cx="1266.7" cy="346.4" r="8"/>
<circle class="st1" cx="927.5" cy="174.8" r="8"/>
<circle class="st1" cx="679.3" cy="93.9" r="8"/>
<circle class="st1" cx="946.4" cy="506.5" r="8"/>
<circle class="st1" cx="1144.7" cy="335.5" r="8"/>
<circle class="st1" cx="921.1" cy="195.9" r="8"/>
<circle class="st1" cx="1495.6" cy="274.7" r="8"/>
<circle class="st1" cx="1240.8" cy="317.6" r="8"/>
<circle class="st1" cx="1348.7" cy="476.5" r="8"/>
<circle class="st1" cx="1036.6" cy="307.8" r="8"/>
<circle class="st1" cx="851.1" cy="550.4" r="8"/>
<circle class="st1" cx="169.4" cy="264.1" r="8"/>
<circle class="st1" cx="146.7" cy="277.9" r="8"/>
<circle class="st1" cx="955.4" cy="271.9" r="8"/>
<circle class="st1" cx="913.4" cy="117.9" r="8"/>
<circle class="st1" cx="1176.6" cy="293.3" r="8"/>
<circle class="st1" cx="774.8" cy="167.9" r="8"/>
<circle class="st1" cx="346.2" cy="570.6" r="8"/>
<circle class="st1" cx="729.4" cy="246.8" r="8"/>
<circle class="st1" cx="795.6" cy="452.1" r="8"/>
<circle class="st1" cx="807.4" cy="179.2" r="8"/>
<circle class="st1" cx="1373.5" cy="272.8" r="8"/>
<circle class="st1" cx="1224.4" cy="411.4" r="8"/>
<circle class="st1" cx="758.5" cy="236" r="8"/>
<circle class="st1" cx="766.2" cy="156.4" r="8"/>
<circle class="st1" cx="999.5" cy="519.7" r="8"/>
<circle class="st1" cx="284.4" cy="243.5" r="8"/>
<circle class="st1" cx="1096.9" cy="343.8" r="8"/>
<circle class="st1" cx="353.6" cy="454.7" r="8"/>
<circle class="st1" cx="1535.9" cy="734.1" r="8"/>
<circle class="st1" cx="299.9" cy="270.9" r="8"/>
<circle class="st1" cx="229.2" cy="370.2" r="8"/>
<circle class="st1" cx="241" cy="327.3" r="8"/>
<circle class="st1" cx="1401.9" cy="352.9" r="8"/>
<circle class="st1" cx="314.4" cy="288.3" r="8"/>
<circle class="st1" cx="338.6" cy="329.7" r="8"/>
<circle class="st1" cx="1190.1" cy="467.3" r="8"/>
<circle class="st1" cx="1451.2" cy="401.6" r="8"/>
<circle class="st1" cx="954.8" cy="659.1" r="8"/>
<circle class="st1" cx="803.5" cy="217.6" r="8"/>
<circle class="st1" cx="1095.4" cy="624.1" r="8"/>
<circle class="st1" cx="306.8" cy="208.7" r="8"/>
<circle class="st1" cx="835.2" cy="187.1" r="8"/>
<circle class="st1" cx="820.7" cy="204" r="8"/>
<circle class="st1" cx="1212.9" cy="359.7" r="8"/>
<circle class="st1" cx="1381" cy="240.3" r="8"/>
<circle class="st1" cx="984.7" cy="502.4" r="8"/>
<circle class="st1" cx="1433.2" cy="304.1" r="8"/>
<circle class="st1" cx="1371.7" cy="350.1" r="8"/>
<circle class="st1" cx="1691.9" cy="634.2" r="8"/>
<circle class="st1" cx="1518.9" cy="266.2" r="8"/>
<circle class="st1" cx="283.4" cy="230.9" r="8"/>
<circle class="st1" cx="326.3" cy="226.6" r="8"/>
<circle class="st1" cx="376.3" cy="258.9" r="8"/>
<circle class="st1" cx="736.1" cy="165.6" r="8"/>
<circle class="st1" cx="826.8" cy="115.7" r="8"/>
<circle class="st1" cx="908.8" cy="210.6" r="8"/>
<circle class="st1" cx="376.7" cy="375.7" r="8"/>
<circle class="st1" cx="1263.2" cy="319.4" r="8"/>
<circle class="st1" cx="467.1" cy="459.3" r="8"/>
<circle class="st1" cx="161.3" cy="204.3" r="8"/>
<circle class="st1" cx="1398.8" cy="697.3" r="8"/>
<circle class="st1" cx="386.7" cy="239.7" r="8"/>
<circle class="st1" cx="179.7" cy="281.1" r="8"/>
<circle class="st1" cx="362.1" cy="235.8" r="8"/>
<circle class="st1" cx="1363.1" cy="420.5" r="8"/>
<circle class="st1" cx="500.6" cy="685" r="8"/>
<circle class="st1" cx="846.6" cy="176.2" r="8"/>
<circle class="st1" cx="332.2" cy="436.3" r="8"/>
<circle class="st1" cx="223" cy="362.9" r="8"/>
<circle class="st1" cx="1310.7" cy="386.4" r="8"/>
<circle class="st1" cx="371.6" cy="255" r="8"/>
<circle class="st1" cx="887.7" cy="135" r="8"/>
<circle class="st1" cx="718.9" cy="454.3" r="8"/>
<circle class="st1" cx="1032.6" cy="335.1" r="8"/>
<circle class="st1" cx="1083.2" cy="627.1" r="8"/>
<circle class="st1" cx="150.3" cy="285.6" r="8"/>
<circle class="st1" cx="399.4" cy="706.7" r="8"/>
<circle class="st1" cx="170" cy="192.8" r="8"/>
<circle class="st1" cx="1373.7" cy="425.2" r="8"/>
<circle class="st1" cx="1429.4" cy="295.4" r="8"/>
<circle class="st1" cx="1410.3" cy="227.4" r="8"/>
<circle class="st1" cx="1361.7" cy="485.4" r="8"/>
<circle class="st1" cx="137.3" cy="255.9" r="8"/>
<circle class="st1" cx="305.6" cy="430.4" r="8"/>
<circle class="st1" cx="1376.3" cy="250.1" r="8"/>
<circle class="st1" cx="895.7" cy="235.7" r="8"/>
<circle class="st1" cx="199" cy="234" r="8"/>
<circle class="st1" cx="142.9" cy="247.4" r="8"/>
<circle class="st1" cx="896.6" cy="222.2" r="8"/>
<circle class="st1" cx="563" cy="576.2" r="8"/>
<circle class="st1" cx="305.8" cy="247" r="8"/>
<circle class="st1" cx="1581.8" cy="710.1" r="8"/>
<circle class="st1" cx="1425.3" cy="294.7" r="8"/>
<circle class="st1" cx="1402.7" cy="349.9" r="8"/>
<circle class="st1" cx="1411.7" cy="263" r="8"/>
<circle class="st1" cx="1007.9" cy="228.6" r="8"/>
<circle class="st1" cx="290.8" cy="404.5" r="8"/>
<circle class="st1" cx="322.2" cy="300.4" r="8"/>
<circle class="st1" cx="889" cy="120.2" r="8"/>
<circle class="st1" cx="963.9" cy="290.3" r="8"/>
<circle class="st1" cx="1393.4" cy="259.2" r="8"/>
<circle class="st1" cx="1039.8" cy="613.7" r="8"/>
<circle class="st1" cx="328.8" cy="315.9" r="8"/>
<circle class="st1" cx="1440.3" cy="334.3" r="8"/>
<circle class="st1" cx="1387.7" cy="244.6" r="8"/>
<circle class="st1" cx="840.6" cy="161.3" r="8"/>
<circle class="st1" cx="336.2" cy="494.8" r="8"/>
<circle class="st1" cx="1305.5" cy="190.2" r="8"/>
<circle class="st1" cx="858.8" cy="188.6" r="8"/>
<circle class="st1" cx="896.1" cy="148.6" r="8"/>
<circle class="st1" cx="1345.5" cy="379.5" r="8"/>
<circle class="st1" cx="877.7" cy="163.7" r="8"/>
<circle class="st1" cx="1392" cy="298" r="8"/>
<circle class="st1" cx="1423.8" cy="293.5" r="8"/>
<circle class="st1" cx="1354.9" cy="274.7" r="8"/>
<circle class="st1" cx="406.3" cy="205.1" r="8"/>
<circle class="st1" cx="172.5" cy="181.9" r="8"/>
<circle class="st1" cx="302" cy="177.5" r="8"/>
<circle class="st1" cx="264.7" cy="163.7" r="8"/>
<circle class="st1" cx="225.2" cy="170" r="8"/>
<circle class="st1" cx="372.7" cy="216.1" r="8"/>
<circle class="st1" cx="857.6" cy="203.4" r="8"/>
<circle class="st1" cx="964.6" cy="288.5" r="8"/>
<circle class="st1" cx="819.5" cy="192.7" r="8"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,84 @@
---
title: CSCI 1100 - Test Crib Sheets
subtitle:
date: 2024-12-08T22:17:36-05:00
lastmod: 2024-12-08T22:17:36-05:00
slug: csci-1100-crib-sheets
draft: false
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description: This post shares the crib sheets I have been used in Test 2, Test 3 and Final of CSCI 1100.
keywords:
license:
comment: true
weight: 0
tags:
- CSCI 1100
- Exam
- RPI
- Python
- Programming
categories:
- Programming
collections:
- CSCI 1100
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary: This post shares the crib sheets I have been used in Test 2, Test 3 and Final of CSCI 1100.
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: false
lightgallery: false
password:
message:
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->
> [!TIP]
> You can edit this PDF with Adobe Photoshop. Yes, you are correct! I made this PDF by Photoshop. The font have been used in these crib sheets is [Intel One Mono](https://github.com/intel/intel-one-mono).
## Test 1 Crib Sheet
> [!NOTE]
> I didn't use a crib sheet in test 1. So, not crib sheets here.
## Test 2 Crib Sheet
<div style="width: 100%; max-width: 600px; margin: 0 auto; display: block;">
<embed src="Test 2 Crib Sheet A.pdf" type="application/pdf" width="100%" height="500px">
</div>
<div style="width: 100%; max-width: 600px; margin: 0 auto; display: block;">
<embed src="Test 2 Crib Sheet B.pdf" type="application/pdf" width="100%" height="500px">
</div>
## Test 3 Crib Sheet
<div style="width: 100%; max-width: 600px; margin: 0 auto; display: block;">
<embed src="Test 3 Crib Sheet A.pdf" type="application/pdf" width="100%" height="500px">
</div>
<div style="width: 100%; max-width: 600px; margin: 0 auto; display: block;">
<embed src="Test 3 Crib Sheet B.pdf" type="application/pdf" width="100%" height="500px">
</div>
## Final Crib Sheet
<div style="width: 100%; max-width: 600px; margin: 0 auto; display: block;">
<embed src="Final Crib Sheet A.pdf" type="application/pdf" width="100%" height="500px">
</div>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,139 @@
---
title: CSCI 1100 - Test 4 Overview and Practice Questions
subtitle:
date: 2024-04-26T03:54:07-04:00
slug: csci-1100-exam-4-overview
draft: true
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description:
keywords: ["CSCI 1100","Computer Science","Test 4","Practice Questions", "Python"]
license:
comment: true
weight: 0
tags:
- CSCI 1100
- Exam
- RPI
- Python
- Programming
categories:
- Programming
collections:
- CSCI 1100
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary:
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: false
lightgallery: false
password:
message:
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->
## Overview
- The final exam will be held Monday, April 29, 2024 from 6:30 pm - 8:30 pm. Note that this will be a two-hour exam.
- Most students will take the exam from 6:30 pm - 8:30 pm (120 minutes). The exam will be given in 308 DCC for most students.
- Students who provided an accommodation letter indicating the need for extra time or a quiet location will be given extra time beyond the 2 hour base. Shianne Hulbert will send you a reminder for your time and location. Use whatever information she sends you. It overrides any assignments given you on Submitty. If you show up at your Submitty location or time, you will be allowed to take the exam, but you will lose the accommodations.
- Students MUST:
- Go to their assigned rooms.
- Bring their IDs to the exam.
- Sit in the correct room/section.
- Put away all calculators, phones, etc. and take off/out all headphones and earbuds
Failing to do one of these may result in a 20 point penalty on the exam score. Failure to do all can cost up to 80 points.
- During the exam, if you are doubtful/confused about a problem, simply state your assumptions and/or interpretation as comments right before your code and write your solution accordingly.
- Exam coverage is the entire semester, except for the following:
- JSON data format
- Images
You do not need to know the intricacies of tkinter GUI formatting, but you should understand the GUI code structure we outlined (Lecture Notes and Class Code), be able to trace through event driven code and write small methods that are invoked by the GUI. Consider the lecture exercises for Lecture 22 and the modifications you made to the BallDraw class during Lab 11 for practice.
- Please review lecture notes, class exercises, labs, homework, practice programs, and tests, working through problems on your own before looking at the solutions.
- You are expected to abide by the following Honor code when appearing for this exam:
"On my honor, I have neither given nor received any aid on this exam."
- As part of our regular class time on Monday April 22, we will answer questions about the course material, so bring your questions!
- There are often study events held on campus, for example UPE often holds tutoring sessions. I do not know of any specific events right now, but we will post anything we learn to the Submitty discussion forum. Please monitor the channel if you are looking for help.
- What follows are a few additional practice problems. These are by no means comprehensive, so rework problems from earlier in the semester. All the material from tests 1, 2, and 3 are also fair game. This is a comprehensive final exam.
- We have separately provided Spring 2017's final exam.
## Questions
### Merge Without Extend
> Write a version of `merge` that does all of the work inside the `while` loop and does not use the `extend`.
### Three Way Merge
> Using what you learned from writing the solution to the previous problem, write a function to merge three sorted lists. For example:
>
> ```python
> print(three_way_merge([2, 3, 4, 4, 4, 5], [1, 5, 6, 9], [6, 9, 13]))
> ```
>
> Should output:
>
> ```
> [1, 2, 3, 4, 4, 4, 5, 5, 6, 6, 9, 9, 13]
> ```
### Score Range Counts
> Given a list of test scores, where the maximum score is 100, write code that prints the number of scores that are in the range 0-9, 10-19, 20-29, ... 80-89, 90-100. Try to think of several ways to do this. Outline test cases you should add.
>
> For example, given the list of scores:
>
> ```python
> scores = [12, 90, 100, 52, 56, 76, 92, 83, 39, 77, 73, 70, 80]
> ```
>
> The output should be something like:
>
> ```
> [0,9]: 0
> [10,19]: 1
> [20,29]: 0
> [30,39]: 1
> [40,49]: 0
> [50,59]: 2
> [60,69]: 0
> [70,79]: 4
> [80,89]: 2
> [90,100]: 3
> ```
### Closest 10 Values
> Given a list of floating point values containing at least 10 values, how do you find the 10 values that are closest to each other? In other words, find the smallest interval that contains 10 values. By definition the minimum and maximum of this interval will be values in the original list. These two values and the 8 in between constitute the desired answer. This is a bit of a challenging variation on earlier problems from the semester. Start by outlining your approach. Outline the test cases. For example, given the list:
>
> ```python
> values = [1.2, 5.3, 1.1, 8.7, 9.5, 11.1, 2.5, 3, 12.2, 8.8, 6.9, 7.4,
> 0.1, 7.7, 9.3, 10.1, 17, 1.1]
> ```
>
> The list of the closest 10 should be:
>
> ```
> [6.9, 7.4, 7.7, 8.7, 8.8, 9.3, 9.5, 10.1, 11.1, 12.2]
> ```

Binary file not shown.

View File

@@ -0,0 +1,314 @@
---
title: CSCI 1100 - Homework 1 - Calculations and Strings
subtitle:
date: 2024-03-12T02:12:11-04:00
slug: csci-1100-hw-1
draft: false
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description: This blog post provides a detailed overview of a Python programming homework assignment, which includes creating a Mad Libs game, calculating speed and pace, and generating a framed box with user-specified dimensions.
keywords: ["Python", "Programming", "Homework", "Mad Libs", "Speed calculation", "Framed box"]
license:
comment: true
weight: 0
tags:
- CSCI 1100
- Homework
- RPI
- Python
- Programming
categories:
- Programming
collections:
- CSCI 1100
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary: This blog post provides a detailed overview of a Python programming homework assignment, which includes creating a Mad Libs game, calculating speed and pace, and generating a framed box with user-specified dimensions.
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: false
lightgallery: false
password:
message:
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->
## Overview
This homework, worth 100 points total toward your overall homework grade, is due Thursday, September 14, 2023 at 11:59:59 pm. The three parts should each be submitted separately. All parts should be submitted by the deadline or your program will be considered late.
Please refer to the Submission Guidelines document before starting this assignment. It will give you details on what we expect and will answer some of the more common questions, including that you need to submit your program through Submitty and that Submitty will open by Monday, September 11th, 2023.
Remember that your output must match EXACTLY the format shown in example runs from the `hw01_files.zip` file. The purpose of this is to make testing easier and at the same time teach you how to be precise in your programming using the tools we gave you. We love creativity, but not in HW output formatting!
## Part 1: Mad Libs (40 pts)
In this part, you will write a Python program to construct the Mad Lib given below:
```text
Good morning <proper name>!
This will be a/an <adjective1> <noun1>! Are you <verb1> forward to it?
You will <verb2> a lot of <noun2> and feel <emotion1> when you do.
If you do not, you will <verb3> this <noun3>.
This <season> was <adjective2>. Were you <emotion2> when <team_name> won
the <noun4>?
Have a/an <adjective3> day!
```
You will ask the user of the program for the missing words | those enclosed in `< >` | using the input function. You will then take all the user-specified inputs and construct the above Mad Lib. Make sure your output looks like the above paragraph, except that the missing information is filled in with the user input.
An example of the program run is provided in file `hw1 part1 output.txt` (You will need to download file `hw01_files.zip` from the Course Materials section of Submitty and unzip it into your directory for HW 1).
We've provided reasonable inputs, but the idea of Mad Libs is to input random words and see how silly the result looks. Try it!
Of course, the program you write will only work for the specific Mad Lib we've written above. A more challenging problem, which you will be capable of solving by the end of the semester, is to write a program that reads in any Mad Lib, figures out what to ask the user, asks the user, reads the input, and generates the final Mad Lib.
Test your code well and when you are sure that it works, please submit it as a file named `hw1 part1.py` to Submitty for Part 1 of the homework.
## Part 2: Speed Calculations (40 pts)
Many exercise apps record both the time and the distance a user covers while walking, running, biking, or swimming. Some users of the apps want to know their average pace in minutes and seconds per mile, while others want to know their average speed in miles per hour. In many cases, we are interested in projected time over a specific distance. For example, if I run 6.3 miles in 53 minutes and 30 seconds, my average pace is 8 minutes and 29 seconds per mile, my average speed is 7.07 miles per hour, and my projected time for 2.7 miles is 22 minutes and 55 seconds.
Your job in Part 2 of this homework is to write a program that asks the user for the minutes, seconds, miles run, and miles to target from an exercise event and outputs both the average pace and the average speed.
An example of the program run is provided in file `hw1 part2 output.txt` (Can be found inside the `hw01_files.zip` file).
You can expect minutes and seconds to both be integers, but miles and miles to target will be floats. All minutes and seconds must be maintained as integers so please use integer division and modulo operations. For example:
```python
>>> x = 29.52
>>> y = int(x)
>>> print(y)
29
```
The output for the speed will be a float and should be printed to 2 decimal places. Notice also that our solution generates a blank line before the output of calculations.
We will test your code for the values used in our examples as well as a range of different values. Test your code well and when you are sure that it works, please submit it as a file named `hw1 part2.py` to Submitty for Part 2 of the homework.
## Part 3: Framed Box (20 pts)
Write a program that asks the user for a frame character, and then the height and width of a framed box. Then output a box of the given size, framed by the given character. Also, output the dimensions of the box centered horizontally and vertically inside the box. In case perfect vertical centering is not possible, dimensions should be output such that there is one less row above the text than below it. In case perfect horizontal centering is not possible, dimensions should be output such that there is one less space character to the left of the text than to the right.
Assume that the user inputs valid values for each input: width is a positive integer (7 or higher) and height is a positive integer (4 or higher), and a single character is given for the frame.
Two examples of the program run are provided in files `hw1 part3 output 01.txt` and `hw1 part3 output 02.txt` (Can be found inside the `hw01_files.zip` file).
You will need to put the box dimensions in a string first, and then use its length to figure out how long the line containing the dimensions should be. If you have prior programming experience you might be tempted to look for how Python implements "loops" in order to generate the full frame, but Python provides string manipulation tools (Lecture 3) that make this unnecessary. You must not use any if statements or loops in your program. We have not learned them yet, they are not needed, and they will not make your solution better or more elegant.
We will test your code for the values used in our examples as well as a range of different values. Test your code well and when you are sure that it works, please submit it as a file named `hw1 part3.py` to Submitty for Part 3 of the homework.
## Supporting Files
{{< link href="HW1.zip" content="HW1.zip" title="Download HW1.zip" download="HW1.zip" card=true >}}
## Solution
### hw1_part1.py
```python
#Prepare variables
proper_name = ""
adjective1 = ""
noun1 = ""
verb1 = ""
verb2 = ""
noun2 = ""
emotion1 = ""
verb3 = ""
noun3 = ""
season = ""
adjective2 = ""
emotion2 = ""
team_name = ""
noun4 = ""
adjective3 = ""
template= """
Good morning <proper name>!
This will be a/an <adjective1> <noun1>! Are you <verb1> forward to it?
You will <verb2> a lot of <noun2> and feel <emotion1> when you do.
If you do not, you will <verb3> this <noun3>.
This <season> was <adjective2>. Were you <emotion2> when <team_name> won
the <noun4>?
Have a/an <adjective3> day!"""
output = ""
#Get user's input
print("Let's play Mad Libs for Homework 1")
print("Type one word responses to the following:\n")
proper_name = input("proper_name ==> ").strip()
print(proper_name)
adjective1 = input("adjective ==> ").strip()
print(adjective1)
noun1 = input("noun ==> ").strip()
print(noun1)
verb1 = input("verb ==> ").strip()
print(verb1)
verb2 = input("verb ==> ").strip()
print(verb2)
noun2 = input("noun ==> ").strip()
print(noun2)
emotion1 = input("emotion ==> ").strip()
print(emotion1)
verb3 = input("verb ==> ").strip()
print(verb3)
noun3 = input("noun ==> ").strip()
print(noun3)
season = input("season ==> ").strip()
print(season)
adjective2 = input("adjective ==> ").strip()
print(adjective2)
emotion2 = input("emotion ==> ").strip()
print(emotion2)
team_name = input("team-name ==> ").strip()
print(team_name)
noun4 = input("noun ==> ").strip()
print(noun4)
adjective3 = input("adjective ==> ").strip()
print(adjective3)
#Construct the Mad Lib
output = template.replace("<proper name>", proper_name)
output = output.replace("<adjective1>", adjective1)
output = output.replace("<noun1>", noun1)
output = output.replace("<verb1>", verb1)
output = output.replace("<verb2>", verb2)
output = output.replace("<noun2>", noun2)
output = output.replace("<emotion1>", emotion1)
output = output.replace("<verb3>", verb3)
output = output.replace("<noun3>", noun3)
output = output.replace("<season>", season)
output = output.replace("<adjective2>", adjective2)
output = output.replace("<emotion2>", emotion2)
output = output.replace("<team_name>", team_name)
output = output.replace("<noun4>", noun4)
output = output.replace("<adjective3>", adjective3)
#Print the Mad Lib
print("\nHere is your Mad Lib...")
print(output, end="")
```
### hw1_part2.py
```python
#Perpare Variables
minutes = 00
seconds = 00
miles = 00.00
target_miles = 00.00
pace_seconds_per_mile = 00.00
pace_seconds = 00
pace_minutes = 00
speed_mph = 00.00
target_time_total_seconds = 00.00
target_time_seconds = 00
target_time_minutes = 00
#Get User Input
minutes = str(input("Minutes ==> "))
print(minutes)
seconds = str(input("Seconds ==> "))
print(seconds)
miles = str(input("Miles ==> "))
print(miles)
target_miles = str(input("Target Miles ==> "))
print(target_miles)
#Calculate Pace
pace_seconds_per_mile = (int(minutes) * 60 + int(seconds)) / float(miles)
pace_seconds = int(pace_seconds_per_mile % 60)
pace_minutes = int(pace_seconds_per_mile // 60)
#Calculate Speed
speed_mph = float(miles) / (int(minutes) / 60 + int(seconds) / 3600)
#Calculate Target Time
target_time_total_seconds = float(target_miles) * pace_seconds_per_mile
target_time_seconds = int(target_time_total_seconds % 60)
target_time_minutes = int(target_time_total_seconds // 60)
#Print Results
print("\nPace is " + str(pace_minutes) + " minutes and " + str(pace_seconds) + " seconds per mile.")
print("Speed is {0:.2f} miles per hour.".format(float(speed_mph)))
print("Time to run the target distance of {0:.2f} miles is {1} minutes and {2} seconds.".format(float(target_miles), int(target_time_minutes), int(target_time_seconds)), end="")
```
### hw1_part3.py
```python
#Prepare Variables
frame_character = ""
height = 0
width = 0
free_space = 0.0
#Get user input
frame_character = input("Enter frame character ==> ").strip()
print(frame_character)
height = int(input("Height of box ==> ").strip())
print(height)
width = int(input("Width of box ==> ").strip())
print(width, "\n")
#Calculate dimensions line
dimensions = str(width) + "x" + str(height)
free_space = width - 2 - len(dimensions)
#Calculate the left and right padding considering odd/even width
left_space = free_space // 2
right_space = free_space // 2 + (free_space % 2)
#Prepare rows
top_bottom_row = frame_character * width
empty_row = frame_character + " " * (width - 2) + frame_character
dimension_row = frame_character + " " * left_space + dimensions + " " * right_space + frame_character
#Calculate the number of rows before and after the dimensions row
before_rows = ((height - 2) // 2) - ((height - 1) % 2)
after_rows = height - 3 - before_rows
#Output box
print("Box:")
print(top_bottom_row)
print((empty_row + '\n') * before_rows, end="")
print(dimension_row)
print((empty_row + '\n') * after_rows, end="")
print(top_bottom_row)
```

Binary file not shown.

View File

@@ -0,0 +1,325 @@
---
title: CSCI 1100 - Homework 2 - Strings and Functions
subtitle:
date: 2024-03-12T02:41:25-04:00
slug: csci-1100-hw-2
draft: false
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description: This homework assignment consists of three parts, focusing on sizing a gum ball machine, implementing a simple substitution cipher, and performing basic sentiment analysis on sentences using Python functions and string manipulation.
keywords: ["Python","functions","string manipulation","sentiment analysis"]
license:
comment: true
weight: 0
tags:
- CSCI 1100
- Homework
- RPI
- Python
- Programming
categories:
- Programming
collections:
- CSCI 1100
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary: This homework assignment consists of three parts, focusing on sizing a gum ball machine, implementing a simple substitution cipher, and performing basic sentiment analysis on sentences using Python functions and string manipulation.
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: false
lightgallery: false
password:
message:
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->
## Overview
This homework, worth 100 points total toward your overall homework grade, is due Thursday, February 1, 2024 at 11:59:59 pm. Three parts should each be submitted separately. All parts should be submitted by the deadline or your program will be considered late.
**Note on grading**: Make sure you read the Submission Guidelines document. It applies to this and all future homework assignments and will be of increasing importance. In all parts of the homework, we will specify which functions you must provide. Make sure you write these functions, even if they are very simple. Otherwise, you will lose points. We will write more complex functions as the semester goes on. In addition, we will also look at program structure (see Lecture 4), and at the names of variables and functions in grading this homework.
You are not allowed to use any loops anywhere in this assignment.
## Fair Warning About Excess Collaboration
For all homework assignments this semester we will be using software that compares all submitted programs, looking for inappropriate similarities. This handles a wide variety of differences between programs, so that if you either (a) took someone elses program, modified it (or not), and submitted it as your own, (b) wrote a single program with one or more colleagues and submitted modified versions separately as your own work, or (c) submitted (perhaps slightly modified) software submitted in a previous year as your software, this software will mark these submissions as very similar.
All of (a), (b), and (c) are beyond what is acceptable in this course — they are violations of the academic integrity policy. Furthermore, this type of copying will prevent you from learning how to solve problems and will hurt you in the long run. The more you write your own code, the more you learn.
Please read the Collaboration Policy document for acceptable levels of collaboration and how you can protect yourself. The document can be found on the Course Materials page on Submitty. Penalties for excess collaboration can be as high as:
- 0 on the homework, and
- an additional overall 5% reduction on the semester grade.
Penalized students will also be prevented from dropping the course. More severe violations, such as stealing someone elses code, will lead to an automatic F in the course. A student caught in a second academic integrity violation will receive an automatic F.
By submitting your homework you are asserting that you both (a) understand the academic integrity policy and (b) have not violated it.
Finally, please note that this policy is in place for the small percentage of problems that will arise in this course. Students who follow the strategies outlined above and use common sense in doing so will not have any trouble with academic integrity.
## Part 1: A Penny for a Gum Ball Mickey (40 pts)
We are going to conduct an experiment in selling gum balls, but we are going to make a few assumptions. Assume that you are selling gum balls from a vending machine. The machine is a cube, and the gum balls are spheres. You check the machine once a week. The goal is to size the machine so that it is completely full at the start of the week, you never run out of gum balls before you get back to check the machine, and you do not have too many gum balls left at the end of the week to go stale. We will make the assumption that all the gum balls line up so that the number of gum balls along any dimension of the cube is simply the side length divided by the diameter of the spheres. So if the side length is 9.0 and the gum balls have a radius of 0.5 exactly 9 gum balls would fit along each dimension, or 729 gum balls in total. This is known as a cubic lattice.
Do the following:
1. First write two functions, `find_volume_sphere(radius)` and `find_volume_cube(side)` that calculate the volume of a sphere given a radius, and that calculate the volume of a cube given a side, respectively.
2. Now ask the user for the radius of the gum balls and the weekly sales.
3. Calculate the total number of gum balls that need to fit in the machine as 1.25 times the weekly sales and use this to calculate the side length of the machine in terms of an integer number of gum balls. Hint: You know the total number of gum balls and in a cubic lattice, you can fit the same number along each dimension, so if you can fit N gum balls along each dimension, then the machine holds N³ gum balls. The math module function `ceil` will always round up and may be of use here. (We wont be cutting gum balls to fit them in the machine.)
4. Calculate a few more values: how many gum balls will actually fit given the dimension you chose (remember that there must be an integer number of gum balls along each dimension of the cube); the volume of the cube; the volume of each gum ball, and the wasted space if we put in both the number of gum balls we need to hold and how many we can hold.
5. Print these values out using the `.2f` format for all floating point values.
Two examples of the program run (how it will look when you run it using Spyder IDE) are provided in files `hw2 part1 output 01.txt` and `hw2 part1 output 02.txt` (You will need to download file `hw02_files.zip` from the Course Materials section of Submitty and unzip it into your directory for HW 2).
We will test your code for the above values as well as a range of different values. Test your code well and when you are sure that it works, please submit it as a file named `hw2 part1.py` to Submitty for Part 1 of the homework.
## Part 2: Find the Hidden Message (40 pts)
Write a program to determine if a simple substitution code is reversible for a given string. The program should ask the user for a sentence using `input`. The program should then encrypt the string into a cipher, decode the cipher, and compare the result of the decode operation to the original sentence. If the decoded cipher matches the original, then the operation is reversible on the string. Otherwise, it is not reversible.
Along the way, the program should print out the cipher, the difference in length between the cipher and the original sentence, the decoded cipher, and a brief message saying whether the operation was reversible. Note that the difference in length should always be printed as a positive number.
Two examples of the program run (how it will look when you run it using Spyder IDE) are provided in files `hw2 part2 output 01.txt` and `hw2 part2 output 02.txt` (can be found inside the `hw02_files.zip` file).
The encryption rules are based on a set of string replacements, they should be applied in this order exactly:
| Original | Replacement | Note |
|:--------:|:-----------:|:-----|
| ' a' | '%4%' | Replace any 'a' after a space with '%4%' |
| 'he' | '7!' | Replace all occurrences of string 'he' with '7!' |
| 'e' | '9(*9(' | Replace any remaining 'e' with '9(*9(' |
| 'y' | '*%' | Replace all occurrences of string 'y' with '*%' |
| 'u' | '@@@' | Replace all occurrences of string 'u' with '@@@' |
| 'an' | '-?' | Replace all occurrences of string 'an' with '-?' |
| 'th' | '!@+3' | Replace all occurrences of string 'th' with '!@+3' |
| 'o' | '7654' | Replace all occurrences of string 'o' with '7654' |
| '9' | '2' | Replace all occurrences of string '9' with '2' |
| 'ck' | '%4' | Replace all occurrences of string 'ck' with '%4' |
For example the cipher for methane is `m2(*2(!@+3-?2(*2(`. Here is how we get this:
```python
>>> 'methane'.replace('e','9(*9(')
'm9(*9(than9(*9('
>>> 'm9(*9(than9(*9('.replace('an','-?')
'm9(*9(th-?9(*9('
>>> 'm9(*9(th-?9(*9('.replace('th','!@+3')
'm9(*9(!@+3-?9(*9('
>>> 'm9(*9(!@+3-?9(*9('.replace('9', '2')
'm2(*2(!@+3-?2(*2('
```
Decrypting will involve using the rules in reverse order.
Your program must use two functions:
- Write one function `encrypt(word)` that takes as an argument a string in plain English, and returns a ciphered version of it as a string.
- Write a second function `decrypt(word)` that does the reverse: takes a string in cipher and returns the plain English version of it.
Both functions will be very similar in structure, but they will use the string replacement rules in different order. You can now test whether your functions are correct by first encrypting a string, and then decrypting. The result should be identical to the original string (assuming the replacement rules are not ambiguous).
Use these functions to implement the above program. We will test your code for the above values as well as a range of different values.
Test your code well and when you are sure that it works, please submit it as a file named `hw2 part2.py` to Submitty for Part 2 of the homework.
## Part 3: How Do You Feel about Homework? (20 pts)
In this part of the homework, you will implement a very rough sentiment analysis tool. While the real tools use natural language processing, they all use word counts similar to the one we use here. Understanding the sentiment in messages is a crucial part of a lot of artificial intelligence tools.
Write a program that will ask the user for a string containing a sentence. The program will then compute the happiness and sadness level of the sentence using the two functions described below. If the happiness level is higher than sadness level, then the tone of the sentence is happy. If the sadness level is higher, then the tone of the sentence is sad. Otherwise, it is neutral. Find and print the tone of the sentence by first printing a sentiment line with a number of + equal to the number of happy words followed by the number of - equal to the number of sad words, followed by a simple statement of the analysis.
Two examples of the program run (how it will look when you run it using Spyder IDE 101) are provided in files `hw2 part3 output 01.txt` and `hw2 part3 output 02.txt` (Can be found inside the `hw02_files.zip` file).
To accomplish this you will write a function called `number_happy(sentence)` which returns the number of words in a given string called sentence that are happy. To do this, find the total count of the following 6 words: laugh happiness love excellent good smile. Here is an example run of this function:
```python
>>> number_happy("I laughed and laughed at her excellent joke.")
3
```
This is because the count of happy words is 3 (laugh is repeated twice). Your code should work even if there are upper and lower case words and extra spaces in the beginning and end of the sentence.
```python
>>> number_happy(" Happiness is the state of a student who started homework early. ")
1
```
Next, write a second function called `number_sad(sentence)` that works the same way but instead counts the number of the following 6 sad words in English: bad sad terrible horrible problem hate
```python
>>> number_sad("Dr. Horrible's Sing-Along Blog is an excellent show.")
1
>>> number_sad("Alexander and the Terrible, Horrible, No Good, Very Bad Day")
3
```
Of courses, there are more than 6 words of each category. We will see how to feed them using a file and use lists to process them in future classes.
Test your code well and when you are sure that it works, please submit it as a file named `hw2 part3.py` to Submitty for Part 3 of the homework.
## Supporting Files
{{< link href="HW2.zip" content="HW2.zip" title="Download HW2.zip" download="HW2.zip" card=true >}}
## Solution
### hw2_part1.py
```python
import math
#Functions
def find_volume_sphere(radius):
"""Calculates the volume of a sphere with a given radius"""
return (4/3) * math.pi * radius**3
def find_volume_cube(side):
"""Calculates the volume of a cube with a given side length"""
return side**3
#Input
radius = str(input("Enter the gum ball radius (in.) => ").strip())
print(radius)
weekly_sales = str(input("Enter the weekly sales => ").strip())
print(weekly_sales, "\n")
#Calculations
target_sales = math.ceil(float(weekly_sales) * 1.25)
edge_gumballs = math.ceil(target_sales**(1/3))
edge_length = edge_gumballs * float(radius)*2
edge_gumballs_max = edge_length / (float(radius)*2 + 0.0000000000000001)
#Aviod ZeroDivisionError by adding a small number to the radius
number_extra_gumballs = math.ceil(edge_gumballs_max**3 - target_sales)
volume_gumballs = find_volume_sphere(float(radius))
volume_cube = find_volume_cube(edge_length)
volume_wasted_target = volume_cube - volume_gumballs * target_sales
volume_wasted_full = volume_cube - volume_gumballs * (edge_gumballs_max) ** 3
#Print
print("The machine needs to hold", str(edge_gumballs), "gum balls along each edge.")
print("Total edge length is", "{:.2f}".format(edge_length), "inches.")
print("Target sales were", str(target_sales) + ", but the machine will hold", str(int(number_extra_gumballs)), "extra gum balls.")
print("Wasted space is", "{:.2f}".format(volume_wasted_target), "cubic inches with the target number of gum balls,")
print("or", "{:.2f}".format(volume_wasted_full), "cubic inches if you fill up the machine.")
```
### hw2_part2.py
```python
user_input = input("Enter a string to encode ==> ").strip()
print(user_input, "\n")
#Replacing
def encrypt(word):
word = word.replace(" a", "%4%")
word = word.replace("he", "7!")
word = word.replace("e", "9(*9(")
word = word.replace("y", "*%$")
word = word.replace("u", "@@@")
word = word.replace("an", "-?")
word = word.replace("th", "!@+3")
word = word.replace("o", "7654")
word = word.replace("9", "2")
word = word.replace("ck", "%4")
return word
#Calculation
length_difference = abs(len(user_input) - len(encrypt(user_input)))
#Decoding
def decrypt(word):
word = word.replace("%4", "ck")
word = word.replace("2", "9")
word = word.replace("7654", "o")
word = word.replace("!@+3", "th")
word = word.replace("-?", "an")
word = word.replace("@@@", "u")
word = word.replace("*%$", "y")
word = word.replace("9(*9(", "e")
word = word.replace("7!", "he")
word = word.replace("%4%", " a")
return word
#Printing
print("Encrypted as ==>", encrypt(user_input))
print("Difference in length ==>", str(length_difference))
print("Deciphered as ==>", decrypt(encrypt(user_input)))
if user_input == decrypt(encrypt(user_input)):
print("Operation is reversible on the string.")
else:
print("Operation is not reversible on the string.")
```
### hw2_part3.py
```python
def number_happy(sentence):
happy_words = ["laugh", "happiness", "love", "excellent", "good", "smile"]
sentence = sentence.lower()
#sentence = sentence.strip()
#sentence = sentence.split()
count = 0
for word in happy_words:
count += sentence.count(word)
return count
def number_sad(sentence):
sad_words = ["bad", "sad", "terrible", "horrible", "problem", "hate"]
sentence = sentence.lower()
#sentence = sentence.strip()
#sentence = sentence.split()
count = 0
for word in sad_words:
count += sentence.count(word)
return count
#Get user input
#sentence = "I laughed and laughed at her excellent joke."
sentence = input("Enter a sentence => ").strip()
#Print
#print(number_happy(sentence))
print(sentence)
print("Sentiment: " + ("+" * number_happy(sentence)) + ("-" * number_sad(sentence)))
if number_happy(sentence) > number_sad(sentence):
print("This is a happy sentence.")
elif number_happy(sentence) == number_sad(sentence):
print("This is a neutral sentence.")
else:
print("This is a sad sentence.")
```

Binary file not shown.

View File

@@ -0,0 +1,435 @@
---
title: CSCI 1100 - Homework 3 - Loops, Tuples, Lists, and Ifs
subtitle:
date: 2024-03-13T14:28:32-04:00
slug: csci-1100-hw-3
draft: false
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description: This homework assignment focuses on working with lists, loops, tuples, and if statements in Python. It includes three parts - analyzing text complexity, simulating Pikachu's movement, and modeling population changes of bears, berries, and tourists.
keywords: ["Python","loops","tuples","lists","if statements"]
license:
comment: true
weight: 0
tags:
- CSCI 1100
- Homework
- RPI
- Python
- Programming
categories:
- Programming
collections:
- CSCI 1100
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary: This homework assignment focuses on working with lists, loops, tuples, and if statements in Python. It includes three parts - analyzing text complexity, simulating Pikachu's movement, and modeling population changes of bears, berries, and tourists.
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: false
lightgallery: false
password:
message:
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->
## Overview
This homework is worth 100 points toward your overall homework grade, and is due Thursday, February 15th, 2024 at 11:59:59 pm. You have 1 week to finish this assignment.
The goal of this assignment is to work with lists, loops, tuples and use if statements. As your programs get longer, you will need to develop some strategies for testing your code. Here are a few simple ones: start testing early, and test small parts of your program by writing a little bit and testing. We will walk you through program construction in the homework description and provide some ideas for testing.
As always, make sure you follow the program structure guidelines. You will be graded on program correctness as well as good program structure. This includes comments. Minimally, we expect a brief docstring comment block at the start of the submission detailing the purpose and a brief summary (you may also include additional information like your name and date); and docstring comments for each function you define detailing the purpose, inputs, and expected return values. Additional comments have to accompany any complicated sections of your code.
## Fair Warning About Excess Collaboration
Please remember to abide by the Collaboration Policy you were given last assignment. It remains in force for this and all assignments this semester. We will be using software that compares all submitted programs, looking for inappropriate similarities. This handles a wide variety of differences between programs, so that if you either (a) took someone elses program, modified it (or not), and submitted it as your own, (b) wrote a single program with one or more colleagues and submitted modified versions separately as your own work, or (c) submitted (perhaps slightly modified) software submitted in a previous year as your software, this software will mark these submissions as very similar. All of (a), (b), and (c) are beyond what is acceptable in this course — they are violations of the academic integrity policy. Furthermore, this type of copying will prevent you from learning how to solve problems and will hurt you in the long run. The more you write your own code, the more you learn.
Make sure that you have read the Collaboration Policy for acceptable levels of collaboration and so you know how you can protect yourself. The document can be found on the Course Materials page on Submitty. Penalties for excess collaboration can be as high as:
- 0 on the homework, and
- an additional overall 5% reduction on the semester grade.
Penalized students will also be prevented from dropping the course. More severe violations, such as stealing someone elses code, will lead to an automatic F in the course. A student caught in a second academic integrity violation will receive an automatic F.
By submitting your homework you are asserting that you both (a) understand the academic integrity policy and (b) have not violated it.
Finally, please note that this policy is in place for the small percentage of problems that will arise in this course. Students who follow the strategies outlined above and use common sense in doing so will not have any trouble with academic integrity.
## Part 1: How complex is the language used in the text? (40 pts)
Create a folder for HW 3. Download the zip file `hw3_files.zip` from the Course Materials on Submitty. Put it in this folder and unzip it. You should see a file named `syllables.py` which will be a helper module for this homework. Write your program in the same folder as this file and name it `hw3_part1.py`.
A few things to get familiar with before solving this part.
In this part, you must get familiar with a function called `.split()` that takes a piece of text, and converts it to a list of strings. Here is an example run:
```python
>>> line = "Citadel Morning News. News about the Citadel in the morning, pretty self explanatory."
>>> m = line.split()
>>> m
['Citadel', 'Morning', 'News.', 'News', 'about', 'the', 'Citadel', 'in', 'the', 'morning,', 'pretty', 'self', 'explanatory.']
```
You will also need to use the function `find_num_syllables()` from the file `syllables.py` which takes as input an English word as a string and that returns the total number of syllables in that word as an integer. The module works even if the word has punctuation symbols, so you do not need to remove those explicitly. Make sure you import this module appropriately into your program.
```python
>>> find_num_syllables('computer')
3
>>> find_num_syllables('science')
1
>>> find_num_syllables('introduction')
4
```
Clearly, the second result is incorrect. The module we provided is not a perfect implementation of syllable counting, so you may find errors. It is not your job to fix them, use the module as it is, with errors and all. Do not worry about the mistakes it makes. To properly compute this, we would need to use a Natural Language Processing (NLP) module like NLTK, which we have not installed in this course.
### Problem specification
In this part, you will read a paragraph containing multiple English sentences as text from the user. Assume a period marks the end of a sentence. Read the paragraph as a single (long) line of text. Compute and print the following measures corresponding to the overall readability of this text.
- ASL (average sentence length) is given by the number of words per sentence. Print ASL.
- PHW (percent hard words): To compute this first count the number of words of three or more syllables that do not contain a hyphen (-) and three-syllable words that do not end with 'es' or 'ed'. Divide this count by the total number of words in the text and multiply the result by 100 to get a percentage. Print PHW.
- Collect all words that are used in the PHW computation in a list exactly as they appear in the input, and print this list.
- ASYL (average number of syllables) is given by the total number of syllables divided by the total number of words. Print ASYL.
- GFRI is given by the formula 0.4*(ASL + PHW). Print GFRI.
- FKRI is given by the formula 206.835-1.015*ASL-86.4*ASYL. Print FKRI.
Note that the measures GFRI and FKRI are slightly modified versions of well-known readability measures named Gunning-Fog and Flesch Kincaid. In Gunning-fog, the higher the value calculated, the more difficult it is to read a text. For Flesch Kincaid it is the opposite with higher values indicating more easily read text.
You can find example runs of the program in `hw3_part1_01.txt` and `hw3_part1_02.txt` from `hw3_files.zip`.
When you are finished, submit your program to Submitty as `hw3_part1.py`. You must use this filename, or your submission will not work in Submitty. You do not have to submit any of the files we have provided.
## Part 2: Pikachu in the Wild! (40 pts)
Suppose you have a Pikachu that is standing in the middle of an image, at coordinates (75, 75). Assume the top left corner of the board is (0,0) like in an image.
We are going to walk a Pikachu around the image looking for other Pokemon. This is a type of simple simulation. First, we will set the parameters of the simulation by asking the user for the number of turns, to run the simulation (starting at turn 0), the name, of your Pikachu and how often, we run into another Pokemon. At this point we enter a simulation loop (while). Your Pikachu walks 5 steps per turn in one of (N)orth, (S)outh, (E)ast or (W)est. Every turn, ask the user for a direction for your Pikachu to walk and move your Pikachu in that direction. You should ignore directions other than N, S, E, W. Every often turns, you meet another Pokemon. Ask the user for a type ((G)round or (W)ater). If it is a ground type, 'G', your Pikachu loses. It turns and runs 10 steps in the direction opposite to the direction in which it was moving before it saw another Pokemon. (If the last direction was not a valid direction, your Pikachu doesnt move.) If it is a water type, 'W', your Pikachu wins and takes 1 step forward. Anything else means you did not actually see another Pokemon. Keep track of wins, losses, and "No Pokemon" in a list.
At the end of turn turns report on where your Pikachu ended up and print out its record.
You must implement at least one function for this program:
```python
move_pokemon((row, column), direction, steps)
```
That returns the next location of the Pikachu as a (row, column) tuple. There is a fence along the boundary of the image. No coordinate can be less than 0 or greater than 150. 0 and 150 are allowed. Make sure your `move_pokemon()` function does not return positions outside of this range.
You can use the following code to test your `move_pokemon()` function. Feel free to write other functions if you want, but be sure to test them to make sure they work as expected!
```python
from hw3_part2 import move_pokemon
row = 15
column = 10
print(move_pokemon((row, column), 'n', 20)) # should print (0, 10)
print(move_pokemon((row, column), 'e', 20)) # should print (15, 30)
print(move_pokemon((row, column), 's', 20)) # should print (35, 10)
print(move_pokemon((row, column), 'w', 20)) # should print (15, 0)
row = 135
column = 140
print(move_pokemon((row, column), 'N', 20)) # should print (115, 140)
print(move_pokemon((row, column), 'E', 20)) # should print (135, 150)
print(move_pokemon((row, column), 'S', 20)) # should print (150, 140)
print(move_pokemon((row, column), 'W', 20)) # should print (135, 120)
```
Now, write some code that will call these functions for each command entered and update the location of the Pikachu accordingly.
Two examples of the program run (how it will look when you run it using Spyder IDE) are provided in files `hw3_part2_01.txt` and `hw3_part2_02.txt` (can be found inside the `hw03_files.zip` file). In `hw3_part2_01.txt`, note that `f` is an invalid direction, so it has no effect on the Pikachus state, and `r` is an invalid Pokemon type which gets flagged as a "No Pokemon" in the results list.
We will test your code with the values from the example files as well as a range of other values. Test your code well and when you are sure that it works, please submit it as a file named `hw3_part2.py` to Submitty for Part 2 of the homework.
## Part 3: Population Change — with Bears (20 pts)
You are going to write a program to compute a type of population balance problem similar to the bunnies and foxes you computed in Lab 3. This problem will have bears, berry fields, and tourists. We will just use the word berries to mean the area of the berry fields. We will count the number of bears and tourists, as well.
Bears need a lot of berries to survive and get ready for winter. So the area of berry fields is a very important part for their population. Berry fields in general spread over time, but if they are trampled too heavily by bears, then they may stop growing and may reduce in size. Tourists are the worst enemy of bears, often habituating them to humans and causing aggressive behavior. Sadly, this can lead to bears being killed to avoid risk to human life.
Here is how the population of each group is linked to one another from one year to the next. Suppose the variable `bears` stores the number of bears in a given year and `berries` stores the area of the berry fields.
- The number of tourists in a given year is determined as follows. If there are less than 4 or more than 15 bears, there are no tourists. It is either not interesting enough or too dangerous for them. In other cases, there are 10,000 tourists for each bear up to and including 10 and then 20,000 tourists for each additional bear. It is a great idea to write a function for computing tourists and test it separately.
- The number of bears and berries in the next year is determined by the following formulas given the population of bears, berries, and tourists in the given year:
```python
bears_next = berries/(50*(bears+1)) + bears*0.60 - (math.log(1+tourists,10)*0.1)
berries_next = (berries*1.5) - (bears+1)*(berries/14) - (math.log(1+tourists,10)*0.05)
```
Remember none of these values can end up being negative. Negative values should be clipped to zero. Also, bears and tourists are integers. The `log` function is in the `math` module.
You must write a function that takes as input the number of bears, berries, and tourists in a given year and returns the next years bears population and berry field area as a tuple.
```python
>>> find_next(5, 1000, 40000)
(5, 1071.1984678861438)
```
Then write the main program that reads two values, the current population of bears, and the area of berry fields. Your program then finds and prints the population of all three groups (bears, berries, and tourists) for the first year and another 9 years (10 years total). You must use a loop to do this. The output is formatted such that all values are printed in columns and are aligned to the left within each column. The width of each column is exactly 10 characters (padded with spaces, if necessary). All floating point values need to be printed with exactly one decimal place.
Once completed, your program should output: the smallest and largest values of the population of bears, berries, and tourists reached in your computation. These values should be output using the same formatting rules as for the population values for each of the years.
An example of the program run how it will look when you run it using the Spyder IDE is provided in file `hw3_part3_01.txt` (can be found inside the `hw03_files.zip` file). Note that the number of bears may go down to zero and then come back up. Why? Bears from neighboring areas can move in. The min and max values for each of bears, berries, and tourists may come from different years.
We will test your code with the values from the example file as well as a range of other values. Test your code well and when you are sure that it works, please submit it as a file named `hw3_part3.py` to Submitty for Part 3 of the homework.
## Supporting Files
{{< link href="HW3.zip" content="HW3.zip" title="Download HW3.zip" download="HW3.zip" card=true >}}
## Solutions
### hw3_part1.py
```python
import syllables
#user_input = "We hold these truths to be self-evident, that all men are created equal, that they are endowed by their Creator with certain unalienable Rights, that among these are Life, Liberty and the pursuit of Happiness."
#user_input = "There is a theory which states that if ever anyone discovers exactly what the Universe is for and why it is here, it will instantly disappear and be replaced by something even more bizarre and inexplicable. There is another theory which states that this has already happened."
user_input = str(input("Enter a paragraph => ")).strip()
print(user_input)
# Calculate ASL
sentences = user_input.split(". ")
words_per_sentence = []
#print(sentences)
for sentence in sentences:
words = sentence.split()
words_per_sentence.append(len(words))
asl = sum(words_per_sentence) / len(words_per_sentence)
#print(asl)
# Calculate PHW
words = user_input.split()
#print(len(words))
#find hard words
hard_words = []
for word in words:
num_syllables = syllables.find_num_syllables(word)
#print(num_syllables)
if num_syllables >= 3 and "-" not in word and word[-2:] != "es" and word[-2:] != "ed":
#print(words)
hard_words.append(word)
#print(hard_words)
#print(hard_words)
#print(len(hard_words))
#print(words)
#print(len(words))
phw = len(hard_words) / len(words) * 100
# Calculate ASYL
total_syllables = 0
for word in words:
total_syllables += syllables.find_num_syllables(word)
asyl = total_syllables / len(words)
# Caclulate GFRI
gfri = 0.4 * (asl + phw)
# Calculate FKRI
fkri = 206.835 - 1.015 * asl - 86.4 * asyl
print("Here are the hard words in this paragraph:\n" + str(hard_words))
print("Statistics: ASL:{:.2f} PHW:{:.2f}% ASYL:{:.2f}".format(asl, phw, asyl))
print("Readability index (GFRI): {:.2f}".format(gfri))
print("Readability index (FKRI): {:.2f}".format(fkri))
```
### hw3_part2.py
```python
# Get user input
num_turn = int(input("How many turns? => ").strip())
print(num_turn)
pikachu_name = str(input("What is the name of your pikachu? => ").strip())
print(pikachu_name)
often_turn = int(input("How often do we see a Pokemon (turns)? => ").strip())
print(str(often_turn) + "\n")
position = (75, 75)
wins, losses, no_pokemon = 0, 0, 0
turn_counter = 0
records = []
last_direction = ""
# Debugging
#num_turn = 5
#print(num_turn)
#pikachu_name = "Piki"
#print(pikachu_name)
#often_turn = 1
#print(often_turn)
def move_pokemon(coords, direction, steps):
global position
x,y = coords
direction = direction.lower()
if direction in ['n', 's', 'e', 'w']:
if direction == 'n':
x = max(0, x - steps)
elif direction == 'e':
y = min(150, y + steps)
elif direction == 's':
x = min(150, x + steps)
elif direction == 'w':
y = max(0, y - steps)
position = (x, y)
def walk(repeat):
global turn_counter, last_direction
for _ in range(repeat):
direction = input("What direction does " + pikachu_name + " walk? => ").strip()
print(direction)
direction = direction.lower()
if direction in ['n', 's', 'e', 'w']:
move_pokemon(position, direction, 5)
last_direction = direction
turn_counter += 1
def turn_message(turn_counter, position):
print("Turn " + str(turn_counter) + ", " + pikachu_name + " at " + str(position))
def battle(pokemon_type):
global records, position, last_direction
if pokemon_type.lower() == 'g':
records.append('Lose')
opposite_directions = {'n': 's', 's': 'n', 'e': 'w', 'w': 'e'}
move_pokemon(position, opposite_directions[last_direction], 10)
print(pikachu_name + " runs away to " + str(position))
elif pokemon_type.lower() == 'w':
records.append('Win')
move_pokemon(position, last_direction, 1)
print(pikachu_name + " wins and moves to " + str(position))
else:
records.append('No Pokemon')
print("Starting simulation, turn 0 " + pikachu_name + " at (75, 75)")
for i in range(num_turn // often_turn):
walk(often_turn)
turn_message(turn_counter, position)
pokemon_type = input("What type of pokemon do you meet (W)ater, (G)round? => ").strip()
print(pokemon_type)
battle(pokemon_type)
if num_turn < often_turn:
for i in range(num_turn):
direction = input("What direction does " + pikachu_name + " walk? => ").strip()
print(direction)
direction = direction.lower()
if direction in ['n', 's', 'e', 'w']:
move_pokemon(position, direction, 5)
last_direction = direction
turn_counter += 1
print(pikachu_name + " ends up at " + "(" + str(position[0]) + ", " + str(position[1]) + ")" + ", Record: " + str(records))
```
### hw3_part3.py
```python
import math
n_bear = input("Number of bears => ").strip()
print(n_bear)
n_size = input("Size of berry area => ").strip()
print(n_size)
n_bear = int(n_bear)
n_size = float(n_size)
year = 10
block_size = 10
data = []
title = ["Year", "Bears", "Berry", "Tourists"]
def print_line(b1, b2, b3, b4):
b1 = b1 + " " * int(10 - len(b1))
b2 = b2 + " " * int(10 - len(b2))
b3 = b3 + " " * int(10 - len(b3))
b4 = b4 + " " * int(10 - len(b4))
print(b1 + b2 + b3 + b4)
def show_data(data):
data_copy = data.copy()
for line in data_copy:
line[2] = "{:.1f}".format(float(line[2]))
line = [str(i) for i in line]
print_line(line[0], line[1], line[2], line[3])
def conclusion(data):
#print(data)
initial_line = data[0]
max_bears, max_berry, max_tourists = initial_line[1], float(initial_line[2]), initial_line[3]
min_bears, min_berry, min_tourists = initial_line[1], float(initial_line[2]), initial_line[3]
for i in data:
max_bears = max(max_bears, i[1])
max_berry = max(max_berry, float(i[2]))
max_tourists = max(max_tourists, i[3])
min_bears = min(min_bears, i[1])
min_berry = min(min_berry, float(i[2]))
min_tourists = min(min_tourists, i[3])
message = []
message.append(["Min:", str(min_bears), str(min_berry), str(min_tourists)])
message.append(["Max:", str(max_bears), str(max_berry), str(max_tourists)])
show_data(message)
def find_next(bears, berry, tourists):
bears_next = berry / (50*(bears+1)) + bears*0.60 - (math.log(1+tourists,10)*0.1)
berries_next = (berry*1.5) - (bears+1)*(berry/14) - (math.log(1+tourists,10)*0.05)
if berries_next < 0:
berries_next = 0
return (int(bears_next), berries_next)
def find_tourists(bears):
bears = int(bears)
if bears < 4 or bears > 15:
tourists = 0
elif bears <= 10:
tourists = 10000*bears
elif bears > 10:
tourists = 100000 + 20000*(bears-10)
return tourists
for i in range(year):
tourists = find_tourists(n_bear)
data.append([i+1, n_bear, n_size, tourists])
n_bear, n_size = find_next(n_bear, n_size, tourists)
print_line(title[0], title[1], title[2], title[3])
show_data(data)
print()
conclusion(data)
```

Binary file not shown.

View File

@@ -0,0 +1,385 @@
---
title: CSCI 1100 - Homework 4 - Loops and Lists; Passwords and Quarantine
subtitle:
date: 2024-03-13T15:15:44-04:00
slug: csci-1100-hw-4
draft: false
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description: This blog post outlines the requirements and guidelines for completing Homework 4 in the CSCI 1100 - Computer Science 1 course, which consists of two parts focusing on password strength evaluation and analyzing COVID-19 quarantine states using Python programming.
keywords: ["CSCI 1100","Computer Science","Python","Password Strength","COVID-19"]
license:
comment: true
weight: 0
tags:
- CSCI 1100
- Homework
- RPI
- Python
- Programming
categories:
- Programming
collections:
- CSCI 1100
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary: This blog post outlines the requirements and guidelines for completing Homework 4 in the CSCI 1100 - Computer Science 1 course, which consists of two parts focusing on password strength evaluation and analyzing COVID-19 quarantine states using Python programming.
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: true
lightgallery: false
password:
message:
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->
## Overview
This homework is worth 100 points total toward your overall homework grade. It is due in 1 week i.e., on Thursday, February 22, 2024 at 11:59:59 pm. As usual, there will be a mix of autograded points, instructor test case points, and TA graded points. There are two parts in the homework, each to be submitted separately. All parts should be submitted by the deadline or your program will be considered late.
See the handout for Submission Guidelines and Collaboration Policy for a discussion on grading and on what is considered excessive collaboration. These rules will be in force for the rest of the semester.
You will need the utilities and data files we provide in `hw4_files.zip`, so be sure to download this file from the Course Materials section of Submitty and unzip it into your directory for HW 4.
Module `hw4_util.py` is written to help you read information from the files. You do not need to know how functions provided in `hw4_util.py` are implemented (but feel free to examine the code, if you are interested), you can simply use them.
Final note, you will need to use loops in this assignment. We will leave the choice of the loop type up to you. Please feel free to use while loops or for loops depending on the task and your personal preference.
## Part 1: Password Strength
Often when you create a password it is judged for its strength. The estimate of strength is computed by applying several rules — about the length of the password, the presence of certain types of characters, its match against common passwords, and even its match against license plates. In this part of the homework you will implement a few simple strength judgment rules and then determine if a password is to be rejected, or rated poor, fair, good or excellent.
Your program should start by asking for, and reading in, a password. The program should then evaluate the password based on the following rules. Each rule contributes to a numerical score (which starts at 0):
1. **Length**: If the password is 6 or 7 characters long then add 1 to the score; if it is 8, 9 or 10 characters long then add 2; and longer than 10 add 3.
2. **Case**: If it contains at least two upper case letters and two lower case letters add 2 to the score, while if it contains at least one of each, add 1 to the score.
3. **Digits**: If it contains at least two digits add 2 to the score and if it contains at least one digit then add 1.
4. **Punctuation**: If it contains at least one of !@#$ add 1 and if it contains at least one of %^&* then add 1 (total possible of 2).
5. **NY License**: If it contains three letters (upper or lower case) followed by four digits, then it potentially matches a NY state license plate. In this case, subtract 2 from the score.
6. **Common Password**: If the lower case version of the password exactly matches a password found in a list of common passwords, then subtract 3 from the score.
Whenever a rule is applied that creates a change in the score, generate an explanatory line of output. After applying all the rules, output the score and then convert it to a final strength rating of the password:
- **Rejected**: the score is less than or equal to 0.
- **Poor**: the score is 1 or 2.
- **Fair**: the score is 3 or 4
- **Good**: the score is 5 or 6
- **Excellent**: the score is 7 or above.
### Notes
1. For this part and for part 2 you should write functions to keep the code clean, clear and easy to manage.
2. We have provided you with a number of examples that show output values and formatting. Please follow these examples closely.
3. The common passwords are extracted from a file. One of the utility functions we have provided reads this file and return a list of these passwords. To use this function, start by making sure that `hw4_util.py` and `password_list_top_100.txt` are in the same folder as your code. Then add the line
```python
import hw4_util
```
into your program. Finally, call function `hw4_util.part1_get_top` with no arguments. It will return a list of strings containing 100 passwords for you to compare against.
5. Submit only your program file `hw4_part1.py`. Do not submit `hw4_util.py`.
## Part 2: COVID-19 Quarantine States
The NY State COVID-19 Travel Advisory at [COVID-19 Travel Advisory](https://coronavirus.health.ny.gov/covid-19-travel-advisory) requires that individuals who travel to New York from states that have significant community spread of COVID-19 must self-quarantine for 14 days. “Significant spread” in a state is measured as either:
- a daily average of more than 10 people per 100,000 residents tested positive in the previous seven days, or
- a daily average of more than 10% of tests were positive in the previous seven days.
We will refer to states having a significant spread as quarantine states. In this part of HW 4 you will use per state data downloaded from [COVID Tracking Project](https://covidtracking.com/) to answer queries about which states were quarantine states and when.
The data we obtained was downloaded (on October 5, 2023) in the form of a large “comma-separated value” file. This file contains one line per day per state, and there are many fields per line. We have condensed it to a form suitable for a CS 1 homework. The data is shared under the Creative Commons BY 4.0 license which means we can:
- Share: copy and redistribute the material in any medium or format and
- Adapt: remix, transform, and build upon the material for any purpose, even commercially.
We have provided a simple utility to give you access to the condensed data. To use this (similar to Part 1) you must have the files `hw4_util.py` and `prob2_data.csv` in the same folder as your own code. You must then
```python
import hw4_util
```
into your program. `hw4_util` has a function called `part2_get_week` that accepts a single integer argument, `w`, and returns a list of lists. Argument `w` is the index of a previous week, with `w==1` being the most recent week, `w==2` being the week before that, etc., up to `w==29` which corresponds to 29 weeks ago, all the way back to March 15. The returned list contains one list per state, plus the District of Columbia (DC) and Puerto Rico (PR) — 52 in all. Each state list has 16 items:
- Item 0 is a string giving the two letter (upper case) state abbreviation. These are correct.
- Item 1 is an integer giving the states population estimate — from the 2019 Census Bureau estimate [Census Bureau estimate](https://www.census.gov/newsroom/press-kits/2019/national-state-estimates.html).
- Items 2-8 are the number of positive tests for that state in each of the seven days of the specified week — most recent day first.
- Items 9-15 are the number of negative tests for the state in each of the seven days of
- the specified week — most recent day first.
For example, the list for Alaska for week 1 is
```text
['AK',\
731545,\
189,147,128,132,106,125,118,\
3373,3819,6839,4984,6045,6140,1688]
```
Here is what you need to do for this assignment. Your program, in a loop, must start by asking the user to specify the index for a week, as described above. (You may assume an integer is input as the week number.) A negative number for the week indicates that the program should end. For non-negative numbers, if the data are not available for that week the function will return an empty list; in this case, skip the rest of loop body. Otherwise, after getting the list of lists back, the program should answer one of four different requests for information about that week. Answering the request starts by the user typing in a keyword. The keywords are 'daily', 'pct', 'quar', 'high'. Heres what the program must do for each request:
- **'daily'**: Ask the user for the state abbreviation and then output the average daily positive cases per 100,000 people for that state for the given week, accurate to the nearest tenth.
- **'pct'**: Ask the user for the state abbreviation and then output the average daily percentage of tests that are positive over the week, accurate to the nearest tenth of a percent.
- **'quar'**: Output the list of state abbreviations, alphabetically by two-letter abbreviation, of travel quarantine states for the given week as discussed above. There should be ten state abbreviations per line—call `hw4_util.print_abbreviations` with your list of abbreviations to print the output as required. (Note: every week has at least one quarantine state.)
- **'high'**: Output the two-letter abbreviation of the state that had the highest daily average number of positive cases per 100,000 people over the given week, and output this average number, accurate to the nearest tenth.
Input key words and state abbreviations may be typed in upper or lower case and still match correctly. If a key word is not one of these four or if a state is not found (because its abbreviation is incorrectly typed), output a simple error message and do nothing more in the current loop iteration.
## Notes
1. As always, look at the example output we provide and follow it accurately.
2. All reported values for numbers of positive and negative test results will be at least 0; however, some may be 0. You may assume, however, that there will never be a week where all days have 0 negative tests.
3. Compute the daily percentage of tests that are positives by summing the positive cases for the week, and summing negative cases for the week. If these sums are P and N respectively, then the percentage positive is $P/(P +N) * 100$. This is not exactly the same as the average of daily percentages for a week, but it is easier to compute.
4. Submit only your program file `hw4_part2.py`. Do not submit `hw4_util.py`.
## Supporting Files
{{< link href="HW4.zip" content="HW4.zip" title="Download HW4.zip" download="HW4.zip" card=true >}}
## Solution
### hw4_part1.py
```python
import hw4_util
if __name__ == "__main__":
# initialize variables
strength = 0
report = ""
# Debugging
#user_password = "AdmIn123%^%*(&"
#user_password = "jaX1234"
# get user input
user_password = str(input("Enter a password => ").strip())
# print the password for testing purposes
print(user_password)
# get the length of the password
length = len(user_password)
# check the length of the password and update strength and report accordingly
if length <= 7 and length >= 6:
strength += 1
report += "Length: +1\n"
elif length >= 8 and length <= 10:
strength += 2
report += "Length: +2\n"
elif length > 10:
strength += 3
report += "Length: +3\n"
# count the number of uppercase and lowercase letters in the password
num_upper = sum(1 for c in user_password if c.isupper())
num_lower = sum(1 for c in user_password if c.islower())
# check the number of uppercase and lowercase letters and update strength and report accordingly
if num_upper >= 2 and num_lower >= 2:
strength += 2
report += "Cases: +2\n"
elif num_upper >= 1 and num_lower >= 1:
strength += 1
report += "Cases: +1\n"
# count the number of digits in the password
num_digits = sum(1 for c in user_password if c.isdigit())
# check the number of digits and update strength and report accordingly
if num_digits >= 2:
strength += 2
report += "Digits: +2\n"
elif num_digits >= 1:
strength += 1
report += "Digits: +1\n"
# check for special characters and update strength and report accordingly
if any(c in "!@#$" for c in user_password):
strength += 1
report += "!@#$: +1\n"
if any(c in "%^&*" for c in user_password):
strength += 1
report += "%^&*: +1\n"
# check for a specific pattern and update strength and report accordingly
if (num_upper + num_lower) == 3 and num_digits == 4 and len(user_password) > 3:
check = user_password.replace(user_password[0:3], "")
if sum(1 for c in check if c.isdigit()) == 4:
strength -= 2
report += "License: -2\n"
# check if the password is in the top 10,000 most common passwords and update strength and report accordingly
if user_password.lower() in hw4_util.part1_get_top():
strength -= 3
report += "Common: -3\n"
# add the combined score to the report
report += "Combined score: " + str(strength) + "\n"
# check the strength and add the appropriate message to the report
if strength <= 0:
report += "Password is rejected"
elif strength >= 1 and strength <= 2:
report += "Password is poor"
elif strength >= 3 and strength <= 4:
report += "Password is fair"
elif strength >= 5 and strength <= 6:
report += "Password is good"
elif strength >= 7:
report += "Password is excellent"
# print the report
print(report)
```
### hw4_part2.py
```python
import hw4_util
"""
hw4_util.part2_get_week(1)[0] ==> ['AK',\
731545, 189, 147, 128, 132, 106, 125,\
118, 3373, 3819, 6839, 4984, 6045,\
6140, 1688]
"""
def find_state(states, state):
state = state.upper()
found_status = False
for i in states:
if i[0].upper() == state:
found_status = True
return i
if not found_status:
return []
def get_postive_per_100k(status):
population = status[1]
total_postive = 0
for i in range (2, 9):
total_postive += status[i]
postive_per_100k = ((total_postive / 7) / population) * 100000
return postive_per_100k
def get_pct_postive_tests(status):
num_tested = 0
num_postive = 0
for i in range (2, 16):
num_tested += status[i]
for i in range (2, 9):
num_postive += status[i]
pct_postive_tests = num_postive / num_tested
return pct_postive_tests
def action_valid(request_code):
request_code = request_code.lower()
allowed_action = ["daily", "pct", "quar", "high"]
if request_code in allowed_action:
return True
else:
return False
def quar(week):
states = []
for i in week:
if get_postive_per_100k(i) >= 10 or get_pct_postive_tests(i) >= 0.1:
states.append(i[0])
states.sort()
return states
def high(week):
highest = ""
highest_value = 0
for i in week:
if get_postive_per_100k(i) > highest_value:
highest = i[0]
highest_value = get_postive_per_100k(i)
return highest.upper()
def show_high(week):
highest = high(week)
highest_postive_per_100k = get_postive_per_100k(find_state(week, highest))
print("State with highest infection rate is", highest)
print("Rate is {:.1f} per 100,000 people".format(highest_postive_per_100k))
if __name__ == "__main__":
index_week = 0 # Initialize index_week
print("...")
while index_week != -1:
# Get index of week
index_week = input("Please enter the index for a week: ").strip()
print(index_week)
index_week = int(index_week)
# Stop if the index is -1
if index_week < 0:
break
# Get the week, check if the week is valid
week = hw4_util.part2_get_week(index_week).copy()
if week == []:
print("No data for that week")
print("...")
continue
# Get the Action
request_code = input("Request (daily, pct, quar, high): ").strip()
print(request_code)
request_code = request_code.lower()
# Check if the action is valid
if not action_valid(request_code):
print("Unrecognized request")
print("...")
continue
# Perform the action
if request_code == "daily":
state = input("Enter the state: ").strip()
print(state)
state = state.upper()
if find_state(week,state) == []:
print("State {} not found".format(state))
print("...")
else:
state_data = find_state(week,state)
print("Average daily positives per 100K population: {:.1f}".format(get_postive_per_100k(state_data)))
print("...")
elif request_code == "pct":
state = input("Enter the state: ").strip()
print(state)
state = state.upper()
if find_state(week,state) == []:
print("State {} not found".format(state))
print("...")
else:
state_data = find_state(week,state)
print("Average daily positive percent: {:.1f}".format(get_pct_postive_tests(state_data)*100))
print("...")
elif request_code == "quar":
print("Quarantine states:")
hw4_util.print_abbreviations(quar(week))
print("...")
elif request_code == "high":
show_high(week)
print("...")
```

Binary file not shown.

View File

@@ -0,0 +1,473 @@
---
title: CSCI 1100 - Homework 5 - Lists of Lists; Grids; Path Planning
subtitle:
date: 2024-03-13T15:36:47-04:00
slug: csci-1100-hw-5
draft: false
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description: This blog post provides an overview of a homework assignment for CSCI 1100 - Computer Science 1, focusing on lists of lists, grids, and path planning using Python. It includes problem descriptions, guidelines, and example outputs for each part of the assignment.
keywords: ["Python","Computer Science","Lists","Grids","Path Planning"]
license:
comment: true
weight: 0
tags:
- CSCI 1100
- Homework
- RPI
- Python
- Programming
categories:
- Programming
collections:
- CSCI 1100
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary: This blog post provides an overview of a homework assignment for CSCI 1100 - Computer Science 1, focusing on lists of lists, grids, and path planning using Python. It includes problem descriptions, guidelines, and example outputs for each part of the assignment.
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: false
lightgallery: false
password:
message:
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->
## Overview
This homework is worth 100 points total toward your overall homework grade. It is due Thursday, February 29, 2024 at 11:59:59 pm. As usual, there will be a mix of autograded points, instructor test case points, and TA graded points. There are two parts to the homework, each to be submitted separately. Both parts should be submitted by the deadline or your program will be considered late.
See the handout for Submission Guidelines and Collaboration Policy for a discussion on grading and on what is considered excessive collaboration. These rules will be in force for the rest of the semester.
You will need the data files we provide in `hw5_files.zip`, so be sure to download this file from the Course Materials section of Submitty and unzip it into your directory for HW 5. The zip file contains utility code, data files and example input / output for your program.
## Problem Description
Many problems in computer science and in engineering are solved on a two-dimensional numerical grid using techniques that are variously called “gradient ascent” (or “descent”), greedy search, or hill-climbing. We are going to study a simplified version of this using hand-generated elevation data.
The main representation we need is a list of lists of “heights” (also called “elevations”, but we will use the simpler term “heights” here). For example:
```python
grid = [[15, 16, 18, 19, 12, 11],
[13, 19, 23, 21, 16, 12],
[12, 15, 17, 19, 22, 10],
[10, 14, 16, 13, 9, 6]]
```
This grid has four lists of six integer entries each. Each entry represents a height — e.g., meters above sea level — and the heights are measured at regularly-spaced intervals, which could be as small as centimeters or as large as kilometers. The USGS, United States Geological Survey, maintains and distributes elevation data like this, but private companies do so as well. Such data are important for evaluating water run-off, determining placement of wind turbines, and planning roads and construction, just to name a few uses. We are going to use the analogy of planning hiking paths on a plot of land.
Questions we might ask about this data include:
1. What is the highest point (greatest height)? This is also called the “global maximum” because it is the greatest value in the data. In our example, this height value is 23 and it occurs in list 1, entry 2. We will refer to this as row 1, column 2 (or “col 2”), and write these values as a tuple, (1, 2), where we assume the first value is the row and the second is the column. We refer to (1, 2) as a “location”.
2. Are there “local maxima” in the data? These are entries whose value is greater than their immediately surrounding values, but smaller than the global maximum. In our example there is a local maxima of 22 at location (2, 4).
3. Starting at a given location, what is the best path to the global maxima? This is a tricky question because we need to define “best path”. Here is a simple one: can we start at a given location and only take the steepest route and get to the global maximum (can we hike up to the “peak”)? For example, if we start at (3, 0) then the path through locations (3, 0), (3, 1), (3, 2), (2, 2), (1, 2) follows the steepest route and reaches the top. This is a “gradient ascent” method. But, if we start at location (3, 5) then we will generate the route (3, 5), (3, 4), (2, 4), but then there is no way to continue up to reach the global maximum.
There are many more questions that we can ask and answer. Some of them can be solved easily, while others require sophisticated algorithms and expensive computations.
Before getting started on the actual assignment, it is important to define the notion of a “neighbor” location in the grid — one that we are allowed to step to from a given location. For our purposes, from location (r, c), the neighbor locations are (r-1, c), (r, c-1), (r, c+1), and (r+1, c).
In other words, a neighbor location must be in the same row or the same column as the current location. Finally, neighbors cannot be outside the grid, so for example at location (r, 0) only (r-1, 0), (r, 1), (r+1, 0) are allowed as neighbors.
## Getting Started
Please download `hw5_files.zip` and place all the files in the same folder that you are going to write your solutions. Files `hw5_util.py` and `hw5_grids.txt` are quite important: `hw5_util.py` contains utility functions to read grids and starting locations from `hw5_grids.txt`. In particular:
- `hw5_util.num_grids()` returns the number of different grids in the data.
- `hw5_util.get_grid(n)` returns grid n, where n==1 is the first grid and n == `hw5_util.num_grids()` is the last.
- `hw5_util.get_start_locations(n)` returns a list of tuples giving one or more starting locations to consider for grid n.
- `hw5_util.get_path(n)` returns a list of tuples giving a possible path for grid n.
We suggest you start by playing around with these functions and printing out what you get so that you are sure you understand.
You may assume the following about the data:
1. The grid has at least two rows.
2. Each row has at least two entries (columns) and each row has the same number of columns.
3. All heights are positive integers.
4. The start locations are all within the range of rows and columns of the grid.
5. The locations on the path are all within the range of rows and columns of the grid.
## Part 1
Write a python program, `hw5_part1.py` that does the following:
1. Asks the user for a grid number and loops until one in the proper range is provided. Denote the grid number as n.
2. Gets grid n.
3. Asks the user if they want to print the grid. A single character response of 'Y' or 'y' should cause the grid to be printed. For anything else the grid should not be printed. When printing, you may assume that the elevations are less than 1,000 meters. See the example output.
4. Gets the start locations associated with grid n and for each it prints the set of neighbor locations that are within the boundaries of the grid. For example if grid n has 8 rows and 10 columns, and the list of start locations is `[(4, 6), (0, 3), (7, 9)]` then the output should be:
```plaintext
Neighbors of (4, 6): (3, 6) (4, 5) (4, 7) (5, 6)
Neighbors of (0, 3): (0, 2) (0, 4) (1, 3)
Neighbors of (7, 9): (6, 9) (7, 8)
```
Very important: we strongly urge you to write a function called `get_nbrs` that takes as parameters a row, col location, together with the number of rows and columns in the grid, and returns a list of tuples containing the locations that are neighbors of the row, col location and are within the bounds of the grid. You will make use of this function frequently.
5. Gets the suggested path, decides if it is a valid path (each location is a neighbor of the next), and then calculates the total downward elevation change and the total upward elevation change. For example using the grid above, if the path is `(3, 1), (3, 0), (2, 0), (1, 0), (1, 1), (0, 1), (0, 2), (1, 2)` the downward elevation changes are from (3, 1) to (3, 0) (change of 4) and from (1, 1) to (0, 1) (change of 3) for a total of 7, and the upward elevation changes are from (3, 0) to (2, 0), from (2, 0) to (1, 0), from (1, 0) to (1, 1), from (0, 1) to (0, 2) and from (0, 2) to (1, 2) for a total of (2 + 1 + 6 + 2 + 5) = 16. The output should be:
```plaintext
Valid path
Downward 7
Upward 16
```
If the path is invalid, the code should print `Path: invalid step from point1 to point2.` Here point1 and point2 are the tuples representing the start and end of an invalid step.
Submit just the file `hw5_part1.py` and nothing else.
## Part 2
Revise your solution to Part 1 and submit it as `hw5_part2.py`. The program should again ask the user for the grid number, but it should not print the grid. Next, it should find and output the location and height of the global maximum height. For example for the simple example grid, the output should be `global max: (1, 2) 23`. You may assume without checking that the global maximum is unique.
The main task of Part 2 is to find and output two paths from each start location for the grid. The first is the steepest path up, and the second is the most gradual path up. The steps on each path must be between neighboring locations as in Part 1. Also, on each path no steps to a location at the same height or lower are allowed, and the step size (the change in height) can be no more than a maximum step height (difference between heights at the new location and at the current location). Your code must ask the user for the value of this maximum step height.
Next, determine for each path if it reaches the location of the global maximum height in the grid, a local maximum, or neither. The latter can happen at a location where the only upward steps are too high relative to the height at the current location. Of course, true hiking paths can go both up and down, but finding an “optimal path” in this more complicated situation requires much more sophisticated algorithms than we are ready to develop here.
As an example of the required results, here is the same grid as above:
```python
grid = [[15, 16, 18, 19, 12, 11],
[13, 19, 23, 21, 16, 12],
[12, 15, 17, 19, 20, 10],
[10, 14, 16, 13, 9, 6]]
```
Starting at location (3, 0) with a maximum height change of 4, the steepest path is (3, 0), (3, 1), (3, 2), (2, 2), (2, 3), (1, 3), (1, 2), while the most gradual path is (3, 0), (2, 0), (1, 0), (0, 0), (0, 1), (0, 2), (0, 3), (1, 3), (1, 2). Both reach the global maximum, and both avoid stepping to the global maximum the first time they are close because the step height is too large. Note that both the steepest and most gradual paths from location (3, 5) would end at the local maximum (2, 4). The steepest path would end after four steps (five locations on the path) and the most gradual would end after six steps (seven locations on the path). If the max step height were only 3, then both paths from (3, 5) would stop at location(3, 4) before any maximum is reached.
Paths should be output with 5 locations per line, for example:
```plaintext
steepest path
(3, 0) (2, 0) (1, 0) (0, 0) (0, 1)
(0, 2) (0, 3) (1, 3) (1, 2)
global maximum
```
See the example output for further details.
Finally, if requested by the user, output a grid — well call it the “path grid” — giving at each location the number of paths that include that location. This can be handled by forming a new list of lists, where each entry represents a count — initialized to 0. For each path and for each location (i, j) on the path, the appropriate count in the list of lists should be incremented. At the end, after all paths have been generated and added to the counts, output the grid. In this output, rather than printing a 0 for a locations that are not on any path, please output a '.'; this will make the output clearer. See the example output.
## Notes
1. In deciding the choice of next locations on a path, if there is a tie, then pick the one that is earlier in the list produced by your `get_nbrs` function. For example starting at (0, 5) with elevation 11 in the above example grid, both (0, 4) and (1, 5) have elevation 12. In this case (0, 4) would be earlier in the `get_nbrs` list and therefore chosen as the next location on the path.
2. Please do not work on the path grid output — the last step — until you are sure you have everything else working.
3. Both the most gradual and steepest paths are examples of greedy algorithms where the best choice available is made at every step and never reconsidered. More sophisticated algorithms would consider some form of backtracking where decisions are undone and alternatives reconsidered.
## Supporting Files
{{< link href="HW5.zip" content="HW5.zip" title="Download HW5.zip" download="HW5.zip" card=true >}}
## Solution
### hw5_part1.py
```python
import hw5_util
def show_grid(grid):
""" Display the grid in a human-readable format. """
display = ""
for row in grid:
for cell in row:
display += " {:2d}".format(int(cell))
display += "\n"
print(display, end="")
def find_grid_size(grid):
""" Find the size of the grid. """
return (len(grid), len(grid[0]))
def find_neighbors(point, grid):
""" Find the neighbors of a point in the grid. """
neighbors = []
y, x = point
max_y, max_x = find_grid_size(grid)
neighbors.append((y-1, x)) if y-1 >= 0 else None
neighbors.append((y, x-1)) if x-1 >= 0 else None
neighbors.append((y, x+1)) if x+1 < max_x else None
neighbors.append((y+1, x)) if y+1 < max_y else None
neighbors.append((y-1, x-1)) if y-1 >= 0 and x-1 >= 0 else None
neighbors.append((y-1, x+1)) if y-1 >= 0 and x+1 < max_x else None
neighbors.append((y+1, x-1)) if y+1 < max_y and x-1 >= 0 else None
neighbors.append((y+1, x+1)) if y+1 < max_y and x+1 < max_x else None
return neighbors
def show_neighbors(start_locations, grid):
""" Display the neighbors of the start locations."""
for i in start_locations:
neighbors = ""
for j in find_neighbors(i, grid):
neighbors += str(j) + " "
neighbors = neighbors.strip()
print("Neighbors of {}: {}".format(i, neighbors))
def path_validator(path, grid):
""" Validate the path. """
result = (True, "Valid path")
for i in range(1, len(path)):
if path[i] not in find_neighbors(path[i-1], grid):
result = (False, "Path: invalid step from {} to {}".format(path[i-1], path[i]))
break
return result
def movement_status(path, grid):
""" Determine the movement status of the path. """
downward = 0
upward = 0
for i in range(1, len(path)):
change = grid[path[i][0]][path[i][1]] - grid[path[i-1][0]][path[i-1][1]]
if change > 0:
upward += change
elif change < 0:
downward += change
return (downward, upward)
if __name__ == "__main__":
grid_index = input("Enter a grid index less than or equal to 3 (0 to end): ").strip()
#grid_index = 1 # Debugging
print(grid_index)
grid_index = int(grid_index)
grid = hw5_util.get_grid(int(grid_index))
#print(grid) # Debugging
"""
grid = [[15, 16, 18, 19, 12, 11],\
[13, 19, 23, 21, 16, 12],\
[12, 15, 17, 19, 20, 10],\
[10, 14, 16, 13, 9, 6]]
"""
print_gride = input("Should the grid be printed (Y or N): ").strip()
#print_gride = "Y" # Debugging
print(print_gride)
if print_gride.upper() == "Y":
print("Grid {}".format(grid_index))
show_grid(grid)
print("Grid has {} rows and {} columns".format(find_grid_size(grid)[0], find_grid_size(grid)[1]))
start_locations = hw5_util.get_start_locations(grid_index)
"""start_locations = [(3, 3), (3, 0), (3, 5), (0, 2)]"""
show_neighbors(start_locations, grid)
"""find_neighbors((3, 3), grid) = [(2, 3), (3, 2), (3, 4)]"""
suggested_path = hw5_util.get_path(grid_index)
#print("Suggested path: ", suggested_path) # Debugging
"""Suggested path: [(3, 1), (3, 0), (2, 0), (1, 0), (1, 1), (0, 1), (0, 2), (1, 2)]"""
print(path_validator(suggested_path, grid)[1])
"""
(True, 'Valid path')
(False, 'Path: invalid step from (2, 4) to (3, 3)')
"""
if path_validator(suggested_path, grid)[0]:
downward, upward = movement_status(suggested_path, grid)
downward = abs(downward)
print("Downward {}\nUpward {}".format(downward, upward))
```
### hw5_part2.py
```python
import hw5_util
def find_height(point, grid):
""" Find the height of a point in the grid. """
return grid[point[0]][point[1]]
def find_grid_size(grid):
""" Find the size of the grid. """
return (len(grid), len(grid[0]))
def find_global_max(grid):
""" Find the global maximum of the grid. """
max_height = ((0,0), 0)
for i in range(len(grid)):
for j in range(len(grid[i])):
if grid[i][j] > max_height[1]:
max_height = ((i,j), grid[i][j])
return max_height
def find_neighbors(point, grid):
""" Find the neighbors of a point in the grid. """
neighbors = []
y, x = point
max_y, max_x = find_grid_size(grid)
neighbors.append((y-1, x)) if y-1 >= 0 else None
neighbors.append((y, x-1)) if x-1 >= 0 else None
neighbors.append((y, x+1)) if x+1 < max_x else None
neighbors.append((y+1, x)) if y+1 < max_y else None
return neighbors
def find_steepest_path(start, grid, max_step):
""" Find the steepest path."""
path = [start]
current = start
while True:
neighbors = find_neighbors(current, grid)
next_step = None
max_height_diff = 0
for n in neighbors:
height_diff = find_height(n, grid) - find_height(current, grid)
if height_diff > 0 and height_diff <= max_step and height_diff > max_height_diff:
next_step = n
max_height_diff = height_diff
if next_step is None:
break
path.append(next_step)
current = next_step
return path
def find_gradual_path(start, grid, max_step):
"""" Find the most gradual path."""
path = [start]
current = start
while True:
neighbors = find_neighbors(current, grid)
next_step = None
min_height_diff = float("inf")
for n in neighbors:
height_diff = find_height(n, grid) - find_height(current, grid)
if height_diff > 0 and height_diff <= max_step and height_diff < min_height_diff:
next_step = n
min_height_diff = height_diff
if next_step is None:
break
path.append(next_step)
current = next_step
return path
def show_path(path):
"""" Display the path."""
display = ""
counter = 0
for i in range(len(path)):
if counter == 5:
display += "\n"
counter = 0
display += "({}, {}) ".format(path[i][0], path[i][1])
counter += 1
print(display)
def find_path_end(path, grid):
""" Find the end of the path."""
end_point = path[-1]
end_height = find_height(end_point, grid)
max_point, max_height = find_global_max(grid)
if end_height == max_height:
return "global maximum"
neighbors = find_neighbors(end_point, grid)
is_local_max = True
for n in neighbors:
if find_height(n, grid) > end_height:
is_local_max = False
break
if is_local_max:
return "local maximum"
else:
return "no maximum"
def build_path_grid(grid, paths):
""" Build the path grid."""
rows, cols = find_grid_size(grid)
path_grid = [[0] * cols for _ in range(rows)]
for path in paths:
for point in path:
y, x = point
path_grid[y][x] += 1
return path_grid
def print_path_grid(path_grid):
""" Display the path grid."""
for i in range(len(path_grid)):
row = " "
for j in range(len(path_grid[i])):
if path_grid[i][j] > 0:
row += str(path_grid[i][j]) + " "
else:
row += "." + " "
print(row.rstrip())
def print_path(path, path_type):
""" Display the path."""
print("{} path".format(path_type))
show_path(path)
print(find_path_end(path, grid))
if __name__ == "__main__":
grid_index = input("Enter a grid index less than or equal to 3 (0 to end): ").strip()
#grid_index = 1 # Debugging
print(grid_index)
grid_index = int(grid_index)
grid = hw5_util.get_grid(int(grid_index))
"""
grid = [[15, 16, 18, 19, 12, 11],\
[13, 19, 23, 21, 16, 12],\
[12, 15, 17, 19, 20, 10],\
[10, 14, 16, 13, 9, 6]]
"""
start_locations = hw5_util.get_start_locations(grid_index)
max_step_height = input("Enter the maximum step height: ").strip()
#max_step_height = "4" # Debugging
print(max_step_height)
max_step_height = int(max_step_height)
print_path_grid_choice = input("Should the path grid be printed (Y or N): ").strip()
#print_path_grid_choice = "Y" # Debugging
print(print_path_grid_choice)
print_path_grid_choice = print_path_grid_choice.upper()
print("Grid has {} rows and {} columns".format(find_grid_size(grid)[0], find_grid_size(grid)[1]))
print("global max: {} {}".format(find_global_max(grid)[0], find_global_max(grid)[1]))
print("===")
paths = []
for start in start_locations:
steepest = find_steepest_path(start, grid, max_step_height)
gradual = find_gradual_path(start, grid, max_step_height)
print_path(steepest, "steepest")
print("...")
print_path(gradual, "most gradual")
print("===")
paths.append(steepest)
paths.append(gradual)
path_grid = build_path_grid(grid, paths)
print("Path grid")
print_path_grid(path_grid)
```

Binary file not shown.

View File

@@ -0,0 +1,436 @@
---
title: CSCI 1100 - Homework 6 - Files, Sets and Document Analysis
subtitle:
date: 2024-04-13T15:36:47-04:00
slug: csci-1100-hw-6
draft: false
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description: This blog post introduces a Python programming assignment for analyzing and comparing text documents using natural language processing techniques, such as calculating word length, distinct word ratios, and Jaccard similarity between word sets and pairs.
keywords: ["Python", "natural language processing", "text analysis", "document comparison"]
license:
comment: true
weight: 0
tags:
- CSCI 1100
- Homework
- RPI
- Python
- Programming
categories:
- Programming
collections:
- CSCI 1100
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary: This blog post introduces a Python programming assignment for analyzing and comparing text documents using natural language processing techniques, such as calculating word length, distinct word ratios, and Jaccard similarity between word sets and pairs.
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: true
lightgallery: false
password:
message:
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->
## Overview
This homework is worth 100 points total toward your overall homework grade. It is due Thursday, March 21, 2024 at 11:59:59 pm. As usual, there will be a mix of autograded points, instructor test case points, and TA graded points. There is just one "part" to this homework.
See the handout for Submission Guidelines and Collaboration Policy for a discussion on grading and on what is considered excessive collaboration. These rules will be in force for the rest of the semester.
You will need the data files we provide in `hw6_files.zip`, so be sure to download this file from the Course Materials section of Submitty and unzip it into your directory for HW 6. The zip file contains data files and example input / output for your program.
## Problem Introduction
There are many software systems for analyzing the style and sophistication of written text and even deciding if two documents were authored by the same individual. The systems analyze documents based on the sophistication of word usage, frequently used words, and words that appear closely together. In this assignment you will write a Python program that reads two files containing the text of two different documents, analyzes each document, and compares the documents. The methods we use are simple versions of much more sophisticated methods that are used in practice in the field known as natural language processing (NLP).
### Files and Parameters
Your program must work with three files and an integer parameter.
The name of the first file will be `stop.txt` for every run of your program, so you don't need to ask the user for it. The file contains what we will refer to as "stop words" — words that should be ignored. You must ensure that the file `stop.txt` is in the same folder as your `hw6_sol.py` python file. We will provide one example of it, but may use others in testing your code.
You must request the names of two documents to analyze and compare and an integer "maximum separation" parameter, which will be referred to as `max_sep` here. The requests should look like:
```text
Enter the first file to analyze and compare ==> doc1.txt
doc1.txt
Enter the second file to analyze and compare ==> doc2.txt
doc2.txt
Enter the maximum separation between words in a pair ==> 2
2
```
### Parsing
The job of parsing for this homework is to break a file of text into a single list of consecutive words. To do this, the contents from a file should first be split up into a list of strings, where each string contains consecutive non-white-space characters. Then each string should have all non-letters removed and all letters converted to lower case. For example, if the contents of a file (e.g., `doc1.txt`) are read to form the string (note the end-of-line and tab characters)
```python
s = " 01-34 can't 42weather67 puPPy, \r \t and123\n Ch73%allenge 10ho32use,.\n"
```
then the splitting should produce the list of strings
```python
['01-34', "can't", '42weather67', 'puPPy,', 'and123', 'Ch73%allenge', '10ho32use,.']
```
and this should be split into the list of (non-empty) strings
```python
['cant', 'weather', 'puppy', 'and', 'challenge', 'house']
```
Note that the first string, `'01-34'` is completely removed because it has no letters. All three files — `stop.txt` and the two document files called `doc1.txt` and `doc2.txt` above — should be parsed this way.
Once this parsing is done, the list resulting from parsing the file `stop.txt` should be converted to a set. This set contains what are referred to in NLP as "stop words" — words that appear so frequently in text that they should be ignored.
The files `doc1.txt` and `doc2.txt` contain the text of the two documents to compare. For each, the list returned from parsing should be further modified by removing any stop words. Continuing with our example, if `'cant'` and `'and'` are stop words, then the word list should be reduced to
```python
['weather', 'puppy', 'challenge', 'house']
```
Words like "and" are almost always in stop lists, while "cant" (really, the contraction "can't") is in some. Note that the word lists built from `doc1.txt` and `doc2.txt` should be kept as lists because the word ordering is important.
### Analyze Each Document's Word List
Once you have produced the word list with stop words removed, you are ready to analyze the word list. There are many ways to do this, but here are the ones required for this assignment:
1. Calculate and output the average word length, accurate to two decimal places. The idea here is that word length is a rough indicator of sophistication.
2. Calculate and output, accurate to three decimal places, the ratio between the number of distinct words and the total number of words. This is a measure of the variety of language used (although it must be remembered that some authors use words and phrases repeatedly to strengthen their message.)
3. For each word length starting at 1, find the set of words having that length. Print the length, the number of different words having that length, and at most six of these words. If for a certain length, there are six or fewer words, then print all six, but if there are more than six print the first three and the last three in alphabetical order. For example, suppose our simple text example above were expanded to the list
```python
['weather', 'puppy', 'challenge', 'house', 'whistle', 'nation', 'vest',
'safety', 'house', 'puppy', 'card', 'weather', 'card', 'bike',
'equality', 'justice', 'pride', 'orange', 'track', 'truck',
'basket', 'bakery', 'apples', 'bike', 'truck', 'horse', 'house',
'scratch', 'matter', 'trash']
```
Then the output should be
```text
1: 0:
2: 0:
3: 0:
4: 3: bike card vest
5: 7: horse house pride ... track trash truck
6: 7: apples bakery basket ... nation orange safety
7: 4: justice scratch weather whistle
8: 1: equality
9: 1: challenge
```
4. Find the distinct word pairs for this document. A word pair is a two-tuple of words that appear `max_sep` or fewer positions apart in the document list. For example, if the user input resulted in `max_sep == 2`, then the first six word pairs generated will be:
```python
('puppy', 'weather'), ('challenge', 'weather'),
('challenge', 'puppy'), ('house', 'puppy'),
('challenge', 'house'), ('challenge', 'whistle')
```
Your program should output the total number of distinct word pairs. (Note that `('puppy', 'weather')` and `('weather', 'puppy')` should be considered the same word pair.) It should also output the first 5 word pairs in alphabetical order (as opposed to the order they are formed, which is what is written above) and the last 5 word pairs. You may assume, without checking, that there are enough words to generate these pairs. Here is the output for the longer example above (assuming that the name of the file they are read from is `ex2.txt`):
```text
Word pairs for document ex2.txt
54 distinct pairs
apples bakery
apples basket
apples bike
apples truck
bakery basket
...
puppy weather
safety vest
scratch trash
track truck
vest whistle
```
5. Finally, as a measure of how distinct the word pairs are, calculate and output, accurate to three decimal places, the ratio of the number of distinct word pairs to the total number of word pairs.
### Compare Documents
The last step is to compare the documents for complexity and similarity. There are many possible measures, so we will implement just a few.
Before we do this we need to define a measure of similarity between two sets. A very common one, and the one we use here, is called Jaccard Similarity. This is a sophisticated-sounding name for a very simple concept (something that happens a lot in computer science and other STEM disciplines). If A and B are two sets, then the Jaccard similarity is just
$$
J(A, B) = \frac{|A \cap B)|}{|A \cup B)|}
$$
In plain English it is the size of the intersection between two sets divided by the size of their union. As examples, if $A$ and $B$ are equal, $J(A, B)$ = 1, and if A and B are disjoint, $J(A, B)$ = 0. As a special case, if one or both of the sets is empty the measure is 0. The Jaccard measure is quite easy to calculate using Python set operations.
Here are the comparison measures between documents:
1. Decide which has a greater average word length. This is a rough measure of which uses more sophisticated language.
2. Calculate the Jaccard similarity in the overall word use in the two documents. This should be accurate to three decimal places.
3. Calculate the Jaccard similarity of word use for each word length. Each output should also be accurate to three decimal places.
4. Calculate the Jaccard similarity between the word pair sets. The output should be accurate to four decimal places. The documents we study here will not have substantial similarity of pairs, but in other cases this is a useful comparison measure.
See the example outputs for details.
## Notes
- An important part of this assignment is to practice with the use of sets. The most complicated instance of this occurs when handling the calculation of the word sets for each word length. This requires you to form a list of sets. The set associated with entry k of the list should be the words of length k.
- Sorting a list or a set of two-tuples of strings is straightforward. (Note that when you sort a set, the result is a list.) The ordering produced is alphabetical by the first element of the tuple and then, for ties, alphabetical by the second. For example,
```python
>>> v = [('elephant', 'kenya'), ('lion', 'kenya'), ('elephant', 'tanzania'), \
('bear', 'russia'), ('bear', 'canada')]
>>> sorted(v)
[('bear', 'canada'), ('bear', 'russia'), ('elephant', 'kenya'), \
('elephant', 'tanzania'), ('lion', 'kenya')]
```
- Submit just a single Python file, `hw6_sol.py`.
- A component missing from our analysis is the frequency with which each word appears. This is easy to keep track of using a dictionary, but we will not do that for this assignment. As you learn about dictionaries think about how they might be used to enhance the analysis we do here.
## Document Files
We have provided the example described above and we will be testing your code along with several other documents (few of them are):
- Elizabeth Alexander's poem Praise Song for the Day.
- Maya Angelou's poem On the Pulse of the Morning.
- A scene from William Shakespeare's Hamlet.
- Dr. Seuss's The Cat in the Hat
- Walt Whitman's When Lilacs Last in the Dooryard Bloom'd (not all of it!)
All of these are available full-text on-line. See poetryfoundation.org and learn about some of the history of these poets, playwrites and authors.
## Supporting Files
{{< link href="HW6.zip" content="HW6.zip" title="Download HW6.zip" download="HW6.zip" card=true >}}
## Solution
### hw6_sol.py
```python
# Debugging
#work_dir = "/mnt/c/Users/james/OneDrive/RPI/Spring 2024/CSCI-1100/Homeworks/HW6/hw6_files/"
work_dir = ""
stop_word = "stop.txt"
def get_stopwords():
stopwords = []
stoptxt = open(work_dir + stop_word, "r")
stop_words = stoptxt.read().split("\n")
stoptxt.close()
stop_words = [x.strip() for x in stop_words if x.strip() != ""]
for i in stop_words:
text = ""
for j in i:
if j.isalpha():
text += j.lower()
if text != "":
stopwords.append(text)
#print("Debug - Stop words:", stopwords)
return set(stopwords)
def parse(raw):
parsed = []
parsing = raw.replace("\n"," ").replace("\t"," ").replace("\r"," ").split(" ")
#print("Debug - Parssing step 1:", parsing)
parsing = [x.strip() for x in parsing if x.strip() != ""]
#print("Debug - Parssing step 2:", parsing)
for i in parsing:
text = ""
for j in i:
if j.isalpha():
text += j.lower()
if text != "":
parsed.append(text)
#print("Debug - Parssing step 3:", parsed)
parsed = [x for x in parsed if x not in get_stopwords()]
#print("Debug - Parssing step 4:", parsed)
return parsed
def get_avg_word_len(file):
#print("Debug - File:", file)
filetxt = open(work_dir + file, "r")
raw = filetxt.read()
filetxt.close()
parsed = parse(raw)
#print("Debug - Parsed:", parsed)
avg = sum([len(x) for x in parsed]) / len(parsed)
#print("Debug - Average:", avg)
return avg
def get_ratio_distinct(file):
filetxt = open(work_dir + file, "r").read()
distinct = list(set(parse(filetxt)))
total = len(parse(filetxt))
ratio = len(distinct) / total
#print("Debug - Distinct:", ratio)
return ratio
def word_length_ranking(file):
filetxt = open(work_dir + file, "r").read()
parsed = parse(filetxt)
max_length = max([len(x) for x in parsed])
#print("Debug - Max length:", max_length)
ranking = [[] for i in range(max_length + 1)]
for i in parsed:
if i not in ranking[len(i)]:
ranking[len(i)].append(i)
#print("Debug - Adding", i, "to", len(i))
for i in range(len(ranking)):
ranking[i] = sorted(ranking[i])
#print("Debug - Ranking:", ranking)
return ranking
def get_word_set_table(file):
str1 = ""
data = word_length_ranking(file)
for i in range(1, len(data)):
cache = ""
if len(data[i]) <= 6:
cache = " ".join(data[i])
else:
cache = " ".join(data[i][:3]) + " ... "
cache += " ".join(data[i][-3:])
if cache != "":
str1 += "{:4d}:{:4d}: {}\n".format(i, len(data[i]), cache)
else:
str1 += "{:4d}:{:4d}:\n".format(i, len(data[i]))
return str1.rstrip()
def get_word_pairs(file, maxsep):
filetxt = open(work_dir + file, "r").read()
parsed = parse(filetxt)
pairs = []
for i in range(len(parsed)):
for j in range(i+1, len(parsed)):
if j - i <= maxsep:
pairs.append((parsed[i], parsed[j]))
return pairs
def get_distinct_pairs(file, maxsep):
total_pairs = get_word_pairs(file, maxsep)
pairs = []
for i in total_pairs:
cache = sorted([i[0], i[1]])
pairs.append((cache[0], cache[1]))
return sorted(list(set(pairs)))
def get_word_pair_table(file, maxsep):
pairs = get_distinct_pairs(file, maxsep)
#print("Debug - Pairs:", pairs)
str1 = " "
str1 += str(len(pairs)) + " distinct pairs" + "\n"
if len(pairs) <= 10:
for i in pairs:
str1 += " {} {}\n".format(i[0], i[1])
else:
for i in pairs[:5]:
str1 += " {} {}\n".format(i[0], i[1])
str1 += " ...\n"
for i in pairs[-5:]:
str1 += " {} {}\n".format(i[0], i[1])
return str1.rstrip()
def get_jaccard_similarity(list1, list2):
setA = set(list1)
setB = set(list2)
intersection = len(setA & setB)
union = len(setA | setB)
if union == 0:
return 0.0
else:
return intersection / union
def get_word_similarity(file1, file2):
file1txt = open(work_dir + file1, "r").read()
file2txt = open(work_dir + file2, "r").read()
parsed1 = parse(file1txt)
parsed2 = parse(file2txt)
return get_jaccard_similarity(parsed1, parsed2)
def get_word_similarity_by_length(file1, file2):
word_by_length_1 = word_length_ranking(file1)
word_by_length_2 = word_length_ranking(file2)
similarity = []
for i in range(1, max(len(word_by_length_1), len(word_by_length_2))):
if i < len(word_by_length_1) and i < len(word_by_length_2):
similarity.append(get_jaccard_similarity(word_by_length_1[i], word_by_length_2[i]))
else:
similarity.append(0.0)
return similarity
def get_word_similarity_by_length_table(file1, file2):
similarity = get_word_similarity_by_length(file1, file2)
str1 = ""
for i in range(len(similarity)):
str1 += "{:4d}: {:.4f}\n".format(i+1, similarity[i])
return str1.rstrip()
def get_word_pairs_similarity(file1, file2, maxsep):
pairs1 = get_distinct_pairs(file1, maxsep)
pairs2 = get_distinct_pairs(file2, maxsep)
return get_jaccard_similarity(pairs1, pairs2)
if __name__ == "__main__":
# Debugging
#file1st = "cat_in_the_hat.txt"
#file2rd = "pulse_morning.txt"
#maxsep = 2
#s = " 01-34 can't 42weather67 puPPy, \r \t and123\n Ch73%allenge 10ho32use,.\n"
#print(parse(s))
#get_avg_word_len(file1st)
#get_ratio_distinct(file1st)
#print(word_length_ranking(file1st)[10])
#print(get_word_set_table(file1st))
# Get user input
file1st = input("Enter the first file to analyze and compare ==> ").strip()
print(file1st)
file2rd = input("Enter the second file to analyze and compare ==> ").strip()
print(file2rd)
maxsep = int(input("Enter the maximum separation between words in a pair ==> ").strip())
print(maxsep)
files = [file1st, file2rd]
for i in files:
print("\nEvaluating document", i)
print("1. Average word length: {:.2f}".format(get_avg_word_len(i)))
print("2. Ratio of distinct words to total words: {:.3f}".format(get_ratio_distinct(i)))
print("3. Word sets for document {}:\n{}".format(i, get_word_set_table(i)))
print("4. Word pairs for document {}\n{}".format(i, get_word_pair_table(i, maxsep)))
print("5. Ratio of distinct word pairs to total: {:.3f}".format(len(get_distinct_pairs(i, maxsep)) / len(get_word_pairs(i, maxsep))))
print("\nSummary comparison")
avg_word_length_ranking = []
for i in files:
length = get_avg_word_len(i)
avg_word_length_ranking.append((i, length))
avg_word_length_ranking = sorted(avg_word_length_ranking, key=lambda x: x[1], reverse=True)
print("1. {} on average uses longer words than {}".format(avg_word_length_ranking[0][0], avg_word_length_ranking[1][0]))
print("2. Overall word use similarity: {:.3f}".format(get_word_similarity(file1st, file2rd)))
print("3. Word use similarity by length:\n{}".format(get_word_similarity_by_length_table(file1st, file2rd)))
print("4. Word pair similarity: {:.4f}".format(get_word_pairs_similarity(file1st, file2rd, maxsep)))
```

Binary file not shown.

View File

@@ -0,0 +1,483 @@
---
title: CSCI 1100 - Homework 7 - Dictionaries
subtitle:
date: 2024-09-12T15:36:47-04:00
slug: csci-1100-hw-7
draft: false
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description: "This blog post outlines a homework assignment worth 100 points, due on March 28, 2024, focusing on Python dictionary manipulation. The assignment includes two parts: an autocorrect program and a movie rating analysis, both requiring careful handling of data files and dictionary operations."
keywords: ["Python", "Dictionaries"]
license:
comment: true
weight: 0
tags:
- CSCI 1100
- Homework
- RPI
- Python
- Programming
categories:
- Programming
collections:
- CSCI 1100
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary: "This blog post outlines a homework assignment worth 100 points, due on March 28, 2024, focusing on Python dictionary manipulation. The assignment includes two parts: an autocorrect program and a movie rating analysis, both requiring careful handling of data files and dictionary operations."
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: true
lightgallery: false
password:
message:
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->
## Overview
This homework is worth 100 points and it will be due Thursday, March 28, 2024 at 11:59:59 pm.
It has two parts, each worth 50 points. Please download `hw7_files.zip` and unzip it into the directory for your HW7. You will find multiple data files to be used in both parts.
The goal of this assignment is to work with dictionaries. In part 1, you will do some simple file processing. Read the guidelines very carefully there. In part 2, we have done all the file work for you so you should be able to get the data loaded in just a few lines. For both parts, you will spend most of your time manipulating dictionaries given to you in the various files.
Please remember to name your files `hw7_part1.py` and `hw7_part2.py`.
As always, make sure you follow the program structure guidelines. You will be graded on program correctness as well as good program structure.
Remember as well that we will be continuing to test homeworks for similarity. So, follow our guidelines for the acceptable levels of collaboration. You can download the guidelines from the Course Resources section of Submitty if you need a refresher. Note that this includes using someone elses code from a previous semester. Make sure the code you submit is truly your own.
## Honor Statement
There have been a number of incidents of academic dishonesty on homework assignments and this must change. Cases are easily flagged using automated tools, and verified by the instructors. This results in substantial grade penalties, poor learning, frustration, and a waste of precious time for everyone concerned. In order to mitigate this, the following is a restatement of the course integrity policy in the form of a pledge. By submitting your homework solution files for grading on Submitty, you acknowledge that you understand and have abided by this pledge:
- I have not shown my code to anyone in this class, especially not for the purposes of guiding their own work.
- I have not copied, with or without modification, the code of another student in this class or who took the class in a previous semester.
- I have not used a solution found or purchased on the internet for this assignment.
- The work I am submitting is my own and I have written it myself.
- I understand that if I am found to have broken this pledge that I will receive a 0 on the assignment and an additional 10 point overall grade penalty.
You will be asked to agree to each of these individual statements before you can submit your solutions to this homework.
Please understand that if you are one of the vast majority of the students who follow the rules and only work with other students to understand problem descriptions, Python constructs, and solution approaches you will not have any trouble whatsoever.
## Part 1: Autocorrect
We have all used auto-correct to fix our various typos and mistakes as we write, but have you ever wondered how it works? Here is a small version of autocorrect that looks for a few common typographical errors.
To solve this problem, your program will read the names of three files:
- The first contains a list of valid words and their frequencies,
- The second contains a list of words to autocorrect, and
- The third contains potential letter substitutions (described below).
The input word file has two entries per line; the first entry on the line is a single valid word in the English language and the second entry is a float representing the frequency of the word in the lexicon. The two values are separated by a comma.
Read this English dictionary into a Python dictionary, using words as keys and frequency as values. You will use the frequency for deciding the most likely correction when there are multiple possibilities
The keyboard file has a line for each letter. The first entry on the line is the letter to be replaced and the remaining letters are possible substitutions for that letter. All the letters on the line are separated by spaces. These substitutions are calculated based on adjacency on the keyboard, so if you look down at your keyboard, you will see that the “a” key is surrounded by “q”, “w”, “s”, and “z”. Other substitutions were calculated similarly, so:
```text
b v f g h n
```
means that a possible replacement for `b` is any one of `v f g h n`. Read this keyboard file into a dictionary: the first letter is the key (e.g., b) and the remaining letters are the value, stored as a list.
Your program will then go through every single word in the input file, autocorrect each word and print the correction. To correct a single word, you will consider the following:
- **FOUND**: If the word is in the dictionary, it is correct. There is no need for a change. Print it as found, and go on to the next word.
- Otherwise consider all of the remaining possibilities.
- **DROP**: If the word is not found, consider all possible ways to drop a single letter from the word. Store any valid words (words that are in your English dictionary) in some container (list/set/dictionary). These will be candidate corrections.
- **INSERT**: If the word is not found, consider all possible ways to insert a single letter in the word. Store any valid words in some container (list/set/dictionary). These will be candidate corrections.
- **SWAP**: Consider all possible ways to swap two consecutive letters from the word. Store any valid words in some container (list/set/dictionary). These will be candidate corrections.
- **REPLACE**: Next consider all possible ways to change a single letter in the word with any other letter from the possible replacements in the keyboard file. Store any valid words in some container (list/set/dictionary). These will be candidate corrections.
For example, for the keyboard file we have given you, possible replacements for `b` are `v f g h n`. Hence, if you are replacing `b` in `abar`, you should consider: `avar`, `afar`, `agar`, `ahar`, `anar`.
After going through all of the above, if there are multiple potential matches, sort them by their potential frequency from the English dictionary and return the top 3 values that are in most frequent usage as the most likely corrections in order. If there are three or fewer potential matches, print all of them in order. In the unlikely event that two words are equally likely based on frequency, you should pick the one that comes last in lexicographical order. See the note below.
If there are no potential matches using any of the above corrections, print `NOT FOUND`. Otherwise, print the word (15 spaces), the number of matches, and at most three matches, all on one line.
An example output of your program for the English dictionary we have given you is contained in `part1_output_01.txt`. Note that, we will use a more extensive dictionary on Submitty, so your results may be different on Submitty than they are on your laptop.
When you are sure your homework works properly, submit it to Submitty. Your program must be named `hw7_part1.py` to work correctly.
### Notes:
1. Do NOT write a for loop to search to see if a string (word or letter) is in a dictionary! This will be very slow and may cause Submitty to terminate your program (and you to lose substantial points). Instead, you must use the `in` operator.
2. It is possible, but unlikely, that a candidate replacement word is generated more than once. We recommend that you gather all possible candidate replacements into a set before looking them up in the dictionary.
3. Ordering the potential matches by frequency can be handled easily. For each potential match, create a tuple with the frequencey first, followed by the word. Add this to a list and then sort the list in reverse order. For example, if the list is `v`, then you just need the line of code `v.sort(reverse=True)`
## Part 2: Well rated and not so well rated movies ...
In this section, we are providing you with two data files `movies.json` and `ratings.json` in JSON data format. The first data file is movie information directly from IMDB, including ratings for some movies but not all. The second file contains ratings from Twitter. Be careful: Not all movies in `movies.json` have a rating in `ratings.json`, and not all movies in `ratings.json` have relevant info in `movies.json`.
The data can be read in its entirety with the following five lines of code:
```python
import json
if __name__ == "__main__":
movies = json.loads(open("movies.json").read())
ratings = json.loads(open("ratings.json").read())
```
Both files store data in a dictionary. The first dictionary has movie ids as keys and a second dictionary containing an attribute list for the movie as a value. For example:
```python
print(movies['3520029'])
(movie with id '3520029') produces the output:
{'genre': ['Sci-Fi', 'Action', 'Adventure'], 'movie_year': 2010,
'name': 'TRON: Legacy', 'rating': 6.8, 'numvotes': 254865}
```
This is same as saying:
```python
movies = dict()
movies['3520029'] = {'genre': ['Sci-Fi', 'Action', 'Adventure'],
'movie_year': 2010, 'name': 'TRON: Legacy',
'rating': 6.8, 'numvotes': 254865}
```
If we wanted to get the individual information for each movie, we can use the following commands:
```python
print(movies['3520029']['genre'])
print(movies['3520029']['movie_year'])
print(movies['3520029']['rating'])
print(movies['3520029']['numvotes'])
```
which would provide the output:
```python
['Sci-Fi', 'Action', 'Adventure']
2010
6.8
254865
```
The second dictionary again has movie ids as keys, and a list of ratings as values. For example,
```python
print(ratings['3520029'])
(movie with id '3520029') produces the output:
[6, 7, 7, 7, 8]
```
So, this movie had 5 ratings with the above values.
Now, on to the homework.
### Problem specification
In this homework, assume you are given these two files called `movies.json` and `ratings.json`. Read the data in from these files. Ask the user for a year range: min year and max year, and two weights: `w1` and `w2`. Find all movies in movies made between min and max years (inclusive of both min and max years). For each movie, compute the combined rating for the movie as follows:
```python
(w1 * imdb_rating + w2 * average_twitter_rating) / (w1 + w2)
```
where the `imdb_rating` comes from movies and `average_twitter_rating` is the average rating from ratings.
If a movie is not rated in Twitter, or if the Twitter rating has fewer than 3 entries, skip the movie. Now, repeatedly ask the user for a genre of movie and return the best and worst movies in that genre based on the years given and the rating you calculated. Repeat until the user enters stop.
An example of the program run (how it will look when you run it using Spyder) is provided in file `hw7_part2_output_01.txt` (the second line for each movie has 8 spaces at the start of the line, and the rating is given in `{:.2f}` format).
The movies we are giving you for testing are a subset of the movies we will use during testing on Submitty, so do not be surprised if there are differences when you submit.
When you are sure your homework works properly, submit it to Submitty. Your program must be named `hw7_part2.py` to work correctly.
### General hint on sorting
It is possible that two movies have the same rating. Consider the following code:
```python
>>> example = [(1, "b"), (1, "a"), (2, "b"), (2, "a")]
>>> sorted(example)
[(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
>>> sorted(example, reverse=True)
[(2, 'b'), (2, 'a'), (1, 'b'), (1, 'a')]
```
Note that the sort puts tuples in order based on the index 0 value first, but in the case of ties, the tie is broken by the index 1 tuple. (If there were a tie in both the index 0 and the index 1 tuple, the sort would continue with the index 2 tuple if available and so on.) The same relationship holds when sorting lists of lists.
To determine the worst and best movies, the example code used a sort with the rating in the index 0 spot and with the name of the movie in the index 1 position. Keep this in mind when you are determining the worst and best movies.
## Supporting Files
{{< link href="HW7.zip" content="HW7.zip" title="Download HW7.zip" download="HW7.zip" card=true >}}
## Solution
> [!NOTE]
> I didn't get a full mark in this assignment (Only 96%), so you should not fully trust it. I may redo it to get a full mark solution. After that, I will add it here.
### hw7_part1.py
```python
"""
An implementation of HW7 Part 1
"""
# Global Variables
word_path = ""
#word_path = "/mnt/c/Users/james/OneDrive/RPI/Spring 2024/CSCI-1100/Homeworks/HW7/hw7_files/"
# Debugging Variables
dictionary_file = "words_10percent.txt"
input_file = "input_words.txt"
keyboard_file = "keyboard.txt"
def get_dictionary(file_name):
words_dict = dict()
data = open(file_name, 'r')
for lines in data:
lines = lines.strip()
the_key = lines.split(",")[0]
the_value = float(lines.split(",")[1])
words_dict[the_key] = the_value
data.close()
return words_dict
def get_keyboard(file_name):
keyboard_dict = dict()
data = open(file_name, 'r')
for lines in data:
lines = lines.strip()
the_key = lines.split(" ")[0]
keyboard_dict[the_key] = []
for i in lines.split(" ")[1:]:
keyboard_dict[the_key].append(i)
data.close()
return keyboard_dict
def check_in_dictionary(word, dictionary):
if word in dictionary:
return True
return False
def get_input_words(file_name):
input_words = []
file = open(file_name, 'r')
for lines in file:
lines = lines.strip()
input_words.append(lines)
file.close()
return input_words
def get_drop_words(word):
drop_words = set()
for i in range(len(word)):
drop_words.add(word[:i] + word[i+1:])
return drop_words
def get_insert_words(word):
insert_words = set()
alphabet = "abcdefghijklmnopqrstuvwxyz"
for i in range(len(word)+1):
for j in alphabet:
insert_words.add(word[:i] + j + word[i:])
#print("Inserting: ", word[:i] + j + word[i:])
return insert_words
def get_swap_words(word):
swap_words = set()
for i in range(len(word) - 1):
swap_words.add(word[:i] + word[i+1] + word[i] + word[i+2:])
return swap_words
def get_replace_words(word, keyboard):
replace_words = set()
#print(keyboard)
for i in range(len(word)):
for j in range(len(word[i])):
for k in keyboard[word[i][j]]:
replace_words.add(word[:i] + k + word[i+1:])
return replace_words
def get_all_possible_words(word, keyboard):
all_possible_words = set()
all_possible_words.update(get_drop_words(word))
all_possible_words.update(get_insert_words(word))
all_possible_words.update(get_swap_words(word))
all_possible_words.update(get_replace_words(word, keyboard))
return all_possible_words
def get_suggestions(word, dictionary, keyboard):
suggestions = dict()
all_possible_words = get_all_possible_words(word, keyboard)
for i in all_possible_words:
if i in dictionary:
suggestions[i] = dictionary[i]
topx = sorted(suggestions, key=lambda x: (suggestions[x], x), reverse=True)
#print(topx)
return topx
def construct_output(input_words, dictionary, keyboard):
output = ""
max_length = max([len(i) for i in input_words])
for i in input_words:
output += " " + " " * (max_length - len(i)) + i + " -> "
if check_in_dictionary(i, dictionary):
output += "FOUND"
elif len(get_suggestions(i, dictionary, keyboard)) == 0:
output += "NOT FOUND"
else:
output += "FOUND {:2d}".format(len(get_suggestions(i, dictionary, keyboard))) + ": "
suggestions = get_suggestions(i, dictionary, keyboard)[:3]
for j in suggestions:
output += " " + j
output += "\n"
return output
if __name__ == "__main__":
dictionary_file = input("Dictionary file => ").strip()
print(dictionary_file)
input_file = input("Input file => ").strip()
print(input_file)
keyboard_file = input("Keyboard file => ").strip()
print(keyboard_file)
dictionary = get_dictionary(word_path + dictionary_file)
#print(dictionary)
keyboard = get_keyboard(word_path + keyboard_file)
#print(keyboard)
#print(get_input_words(word_path + input_file))
#print(get_drop_words("hello"))
#print("shut" in get_insert_words("shu"))
#print(get_swap_words("hello"))
#print("integers" in get_replace_words("inteters", keyboard))
#print(get_all_possible_words("hello", keyboard))
#print(get_suggestions("doitd", dictionary, keyboard))
print(construct_output(get_input_words(word_path + input_file), dictionary, keyboard), end = "")
```
### hw7_part2.py
```python
"""
An implementation of HW7 Part 2
"""
import json
# Global Variables
word_path = ""
#word_path = "/mnt/c/Users/james/OneDrive/RPI/Spring 2024/CSCI-1100/Homeworks/HW7/hw7_files/"
genre = ""
# Debugging Variables
#min_year = 2000
#max_year = 2016
#imdb_weight = 0.7
#twitter_weight = 0.3
#genre = "sci-fi"
def get_movie_ids(movies, min_year, max_year):
ids = set()
for i in movies.keys():
if movies[i]['movie_year'] >= min_year and movies[i]['movie_year'] <= max_year:
ids.add(int(i))
return ids
def get_imdb_rating(movies, movie_id):
return float(movies[str(movie_id)]['rating'])
def get_twitter_rating(ratings, movie_id):
if str(movie_id) in ratings.keys():
return ratings[str(movie_id)]
else:
return []
def get_num_twitter_ratings(ratings, movie_id):
return len(get_twitter_rating(ratings, movie_id))
def get_weighted_rating(movies, ratings, movie_id, imdb_weight, twitter_weight):
imdb = get_imdb_rating(movies, movie_id)
twitter = 0.0
for i in get_twitter_rating(ratings, movie_id):
twitter += i
twitter /= len(get_twitter_rating(ratings, movie_id))
return (imdb * imdb_weight + twitter * twitter_weight) / (imdb_weight + twitter_weight)
def get_movie_name(movies, movie_id):
return movies[str(movie_id)]['name']
if __name__ == "__main__":
movies = json.loads(open(word_path + "movies.json").read())
ratings = json.loads(open(word_path + "ratings.json").read())
"""
movies['3520029'] = {'genre': ['Sci-Fi', 'Action', 'Adventure'],
'movie_year': 2010, 'name': 'TRON: Legacy',
'rating': 6.8, 'numvotes': 254865}
"""
min_year = int(input("Min year => ").strip())
print(min_year)
max_year = int(input("Max year => ").strip())
print(max_year)
imdb_weight = float(input("Weight for IMDB => ").strip())
print(imdb_weight)
twitter_weight = float(input("Weight for Twitter => ").strip())
print(twitter_weight)
ids = get_movie_ids(movies, min_year, max_year)
#print(ids)
while genre.lower() !="stop":
genre = input("\nWhat genre do you want to see? ").strip()
print(genre)
if genre == "stop":
break
min_rating = 10000.0
max_rating = 0.0
min_name = ""
max_name = ""
mv_min_year = 10000
mv_max_year = 0
for i in ids:
if get_num_twitter_ratings(ratings, i) <= 3:
continue
genres = movies[str(i)]['genre']
genres = [x.lower() for x in genres]
#print("Debug", i, genres)
if genre.lower() in genres:
rating = get_weighted_rating(movies, ratings, i, imdb_weight, twitter_weight)
#print("Debug", rating)
if rating < min_rating:
min_rating = rating
min_name = get_movie_name(movies, i)
mv_min_year = movies[str(i)]['movie_year']
if rating > max_rating:
max_rating = rating
max_name = get_movie_name(movies, i)
mv_max_year = movies[str(i)]['movie_year']
if min_name == "" or max_name == "":
print("\nNo {} movie found in {} through {}".format(genre, mv_min_year, mv_max_year))
else:
print("\nBest:\n Released in {}, {} has a rating of {:.2f}".format(mv_max_year, max_name, max_rating))
print("\nWorst:\n Released in {}, {} has a rating of {:.2f}".format(mv_min_year, min_name, min_rating))
genre = genre
#genre = "stop" # Debugging Only
```

Binary file not shown.

View File

@@ -0,0 +1,154 @@
---
title: "CSCI 1100 - Homework 8 - Bears, Berries, and Tourists Redux - Classes"
subtitle:
date: 2024-09-13T15:36:47-04:00
slug: csci-1100-hw-8
draft: false
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description: This blog post provides a detailed guide on completing Homework 8 for CSCI 1100, focusing on simulating a berry field with bears and tourists using Python classes. It covers the creation of BerryField, Bear, and Tourist classes, and instructions for submitting the assignment.
keywords: ["Python", "Classes", "Simulation", "Homework 8"]
license:
comment: true
weight: 0
tags:
- CSCI 1100
- Homework
- RPI
- Python
- Programming
categories:
- Programming
collections:
- CSCI 1100
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary: This blog post provides a detailed guide on completing Homework 8 for CSCI 1100, focusing on simulating a berry field with bears and tourists using Python classes. It covers the creation of BerryField, Bear, and Tourist classes, and instructions for submitting the assignment.
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: false
lightgallery: false
password:
message:
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->
## Overview
This homework is worth 100 points toward your overall homework grade and is due Thursday, April 18, 2024, at 11:59:59 pm. It has three parts. The first two are not worth many points and may end up being worth 0. They are mainly there to give you information to help you debug your solution. Please download `hw8_files.zip` and unzip it into the directory for your HW8. You will find data files and sample outputs for each of the parts.
The goal of this assignment is to work with classes. You will be asked to write a simulation engine and use classes to encapsulate data and functionality. You will have a lot of design choices to make. While we have done simulations before, this one will be more complex. It is especially important that you start slowly, build a program that works for simple cases, test it, and then add more complexity. We will provide test cases of increasing difficulty. Make sure you develop slowly and test thoroughly.
## Submission Instructions
In this homework, for the first time, you will be submitting multiple files to Submitty that together comprise a single program. Please follow these instructions carefully.
Each of Part 1, Part 2, and Part 3 will require you to write a main program: `hw8_part1.py`, `hw8_part2.py`, and `hw8_part3.py`, respectively. You must also submit three modules per part in addition to this main file, each of which encapsulates a class. The first is a file called `BerryField.py` that contains your BerryField class, a file called `Bear.py` that contains your Bear class, and a file called `Tourist.py` that contains your Tourist class.
As always, make sure you follow the program structure guidelines. You will be graded on good program structure as well as program correctness.
Remember as well that we will be continuing to test homeworks for similarity. So, follow our guidelines for the acceptable levels of collaboration. You can download the guidelines from the resources section in the Course Materials if you need a refresher. We take this very seriously and will not hesitate to impose penalties when warranted.
## Getting Started
You will need to write at least three classes for this assignment corresponding to a BerryField, a Bear, and a Tourist. We are going to give you a lot of freedom in how you organize these three classes, but each class must have at least an initializer and a string method. Additional methods are up to you. Each of the classes is described below.
### BerryField
The `BerryField` class must maintain and manage the location of berries as a square Row X Column grid with (0,0) being the upper left corner and (N-1, N-1) being the lower right corner. Each space holds 0-10 berry units.
- The initializer class must, minimally, be able to take in a grid of values (think of our Sudoku lab) and use it to create a berry field with the values contained in the grid.
- The string function must, minimally, be able to generate a string of the current state of the berry patch. Each block in the grid must be formatted with the `"{:>4}"` format specifier. If there is a bear at the location, the grid should have a `"B"`; if there is a tourist, the grid should have a `"T"`; and if there is both a bear and a tourist, the grid should have an `"X"`. If there is neither a bear nor a tourist, it should have the number of berries at the location.
- Berries grow. The BerryField class must provide a way to grow the berry field. When the berries grow, any location with a value `1 <= number of berries < 10` will gain an extra berry.
- Berries also spread. Any location with no berries that is adjacent to a location with 10 berries will get 1 berry during the grow operation.
### Bear
Each Bear has a location and a direction in which they are walking. Bears are also very hungry. In your program, you must manage 2 lists of bears. The first list contains those bears that are currently walking in the field. The second is a queue of bears waiting to enter the field.
- The initializer class must, minimally, be able to take in a row and column location and a direction of travel.
- The string function must, minimally, be able to print out the location and direction of travel for the bear and if the bear is asleep.
- Bears can walk `North (N)`, `South (S)`, `East (E)`, `West (W)`, `NorthEast (NE)`, `NorthWest (NW)`, `SouthEast (SE)`, or `SouthWest (SW)`. Once a bear starts walking in a direction, it never turns.
- Bears are always hungry. Every turn, unless there is a tourist on the same spot, the bear eats all the berries available on the space and then moves in its current direction to the next space. This continues during the current turn until the bear eats 30 berries or runs into a tourist.
- For the special case of a bear and a tourist being in the same place during a turn, the bear does not eat any berries, but the tourist mysteriously disappears and the bear falls asleep for three turns.
- Once a bear reaches the boundary of the field (its row or column becomes -1 or N), it is no longer walking in the field and need not be considered any longer.
### Tourist
Each Tourist has a location. Just like with bears, you must maintain a list of tourists currently in the field and a queue of tourists waiting to enter the field.
- The initializer class must, minimally, be able to take in a row and column location.
- Tourists see a bear if the bear is within 4 of their current position.
- The string function must, minimally, be able to print out the location of the tourist and how many turns have passed since they have seen a bear.
- Tourists stand and watch. They do not move, but they will leave the field if:
1. Three turns pass without them seeing a bear; they get bored and go home.
2. They can see three bears at the same time; they get scared and go home.
3. A bear runs into them; they mysteriously disappear and can no longer be found in the field.
## Execution
Remember to get `hw8_files_F19.zip` from the Course Materials section of Submitty. It has two sample input files and the expected output for your program.
For this homework, all of the data required to initialize your classes and program can be found in JSON files. Each of your 3 parts should start by asking for the name of the JSON file, reading the file, and then creating the objects you need based on the data read. The code below will help you with this.
```python
f = open("bears_and_berries_1.json")
data = json.loads(f.read())
print(data["berry_field"])
print(data["active_bears"])
print(data["reserve_bears"])
print(data["active_tourists"])
print(data["reserve_tourists"])
```
You will see that the field is a list of lists where each `[row][column]` value is the number of berries at that location; the `"active_bears"` and `"reserve_bears"` entries are lists of three-tuples `(row, column, direction)` defining the bears; and the `"active_tourists"` and `"reserve_tourists"` entries are lists of two-tuples `(row, column)` defining the tourists.
## Part 1
In Part 1, read the JSON file, create your objects, and then simply report on the initial state of the simulation by printing out the berry field, active bears, and active tourists. Name your program `hw8_part1.py` and submit it along with the three classes you developed.
## Part 2
In Part 2, start off the same by reading the JSON file, create your objects, and again print out the initial state of the simulation. Then run five turns of the simulation by:
- Growing the berries
- Moving the bears
- Checking on the tourists
- Printing out the state of the simulation
Do not worry about the reserve bears or reserve tourists entering the field, but report on any tourists or bears that leave. Name your program `hw8_part2.py` and submit it along with the three classes you developed.
## Part 3
In Part 3, do everything you did in Part 2, but make the following changes:
- After checking on the tourists, if there are still bears in the reserve queue and at least 500 berries, add the next reserve bear to the active bears.
- Then, if there are still tourists in the reserve queue and at least 1 active bear, add the next reserve tourist to the field.
- Instead of stopping after 5 turns, run until there are no more bears on the field and no more bears in the reserve list, or if there are no more bears on the field and no more berries.
- Finally, instead of reporting status every turn, report it every 5 turns and then again when the simulation ends.
As you go, report on any tourists or bears that leave or enter the field. Name your program `hw8_part3.py` and submit it along with the three classes you developed.
## Supporting Files
{{< link href="HW8.zip" content="HW8.zip" title="Download HW8.zip" download="HW8.zip" card=true >}}
## Solution
> [!NOTE]
> I didn't get a full mark in this assignment, so I didn't post the solution. I may redo it to get a full mark solution. After that, I will add it here.

View File

@@ -0,0 +1,504 @@
---
title: CSCI 1200 - Homework 1 - Spotify Playlists
subtitle:
date: 2025-02-15T13:38:46-05:00
lastmod: 2025-02-15T13:38:46-05:00
slug: csci-1200-hw-1
draft: false
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description: This blog post provides a detailed guide on developing a music playlist management program similar to Spotify using C++. It covers command-line parameter handling, file I/O operations, and the use of STL string and vector classes.
keywords: ["C++", "Programming", "Homework", "STL Vector","Playlist Management"]
license:
comment: true
weight: 0
tags:
- CSCI 1200
- Homework
- RPI
- C++
- Programming
categories:
- Programming
collections:
- CSCI 1200
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary: This blog post provides a detailed guide on developing a music playlist management program similar to Spotify using C++. It covers command-line parameter handling, file I/O operations, and the use of STL string and vector classes.
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: false
lightgallery: false
password:
message:
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->
## Assignment Requirements
{{< details >}}
Before starting this homework, make sure you have read and understood the Academic Integrity Policy.
In this assignment you will develop a program to manage music playlists like Spotify does, let's call this program New York Playlists. Please read the entire handout before starting to code the assignment.
## Learning Objectives
- Practice handling command line arguments.
- Practice handling file input and output.
- Practice the C++ Standard Template Library string and vector classes.
## Command Line Arguments
Your program will be run like this:
```console
./nyplaylists.exe playlist.txt actions.txt output.txt
```
Here:
- nyplaylists.exe is the executable file name.
- playlist.txt is the name of an input file which contains a playlist - in this README, we will refer to this file as the **playlist file**.
- actions.txt is an input file which defines a sequence of actions - in this README, we will refer to this file as the **actions file**.
- output.txt where to print your output to.
## Playlist File Format and Output File Format
The playlist file and the output file have the same format. Take the playlist_tiny1.txt as an example, this file has the following 4 lines:
```console
"Perfect Duet" Ed Sheeran, Beyonce
"Always Remember Us This Way" Lady Gaga current
"Million Reasons" Lady Gaga
"I Will Never Love Again - Film Version" Lady Gaga, Bradley Cooper
```
Except the second line, each line has two fields, the music title, and the artist(s). There is one single space separating these two fields.
The second line is special, it ends with the word **current**, meaning that the song "Always Remember Us This Way" is the currently playing song. This word **current** appears in the **playlist file** once and should also appear in the output file once.
## Actions File Format
The actions file defines actions. Take actions1.txt as an example, this file has the following lines:
```console
add "Umbrella" Rihanna
add "We Are Young" Fun
add "You Are Still the One" Shania Twain
remove "Million Reasons" Lady Gaga
add "Viva La Vida" Coldplay
move "I Will Never Love Again - Film Version" Lady Gaga, Bradley Cooper 1
next
next
next
previous
move "You Are Still the One" Shania Twain 4
```
The **actions file** may include 5 different types of actions:
- add, which adds a song to the end of the playlist.
- remove, which removes a song from the playlist.
- move, which moves a song to a new position - the new position is always included at the end of the line. The line *move "I Will Never Love Again - Film Version" Lady Gaga, Bradley Cooper 1*, moves the song "I Will Never Love Again - Film Version" to position 1, and the line *move "You Are Still the One" Shania Twain 4*, moves the song "You Are Still the One" to position 4. Note that, unliked array indexing in C/C++, positioning in Spotify starts at 1, as opposed to 0. This can be seen in the above Spotify screenshot: the first position is position 1.
- next, which skips the currently playing song and starts playing the song that is listed directly after it. Note that if the currently playing song is already at the bottom of the playlist, the action *next* will make the first song (i.e., the song at the very top of the playlist) as the currently playing song.
- previous, which skips the currently playing song and goes to the song listed directly before the currently playing song. Note that if the currently playing song is already at the top of the playlist, the action *previous* will make the last song (i.e., the song at the bottom of the playlist) as the currently playing song.
According to this sample **actions file**, 4 songs will be added to the playlist, 1 song will be removed, 2 songs will be moved. And the currently playing song will be a different song, instead of the song "Always Remember Us This Way".
When playlist_tiny1.txt and actions1.txt are supplied to your program as the two input files, your program should produce the following output file:
```console
"I Will Never Love Again - Film Version" Lady Gaga, Bradley Cooper
"Perfect Duet" Ed Sheeran, Beyonce
"Always Remember Us This Way" Lady Gaga
"You Are Still the One" Shania Twain
"Umbrella" Rihanna
"We Are Young" Fun current
"Viva La Vida" Coldplay
```
## Non-existent Songs
If a move action or a remove action as defined in the **actions file** attempts to move or remove a song which does not exist in the playlist, your program should ignore such an action.
## Duplicated Songs
In cases where the same song appears more than once on the playlist, choose the first song (to move or remove) - i.e., search the playlist, starting from the top to the bottom, identify the first occurrence of this song, and use it (to move or remove).
## Instructor's Code
You can test (but not view) the instructor's code here: [instructor code](http://ds.cs.rpi.edu/hws/playlists/). Note that this site is hosted on RPI's network and you can visit this site only if you are on RPI's network: either on campus or using a VPN service. Also note that, it is not your job in this assignment to play musics, the instructor's C++ code here is just used as the backend to manage the playlist.
## Program Requirements & Submission Details
In this assignment, you are required to use both std::string and std::vector. You are NOT allowed to use any data structures we have not learned so far.
Use good coding style when you design and implement your program. Organize your program into functions: dont put all the code in main! Be sure to read the [Homework Policies](https://www.cs.rpi.edu/academics/courses/spring25/csci1200/homework_policies.php) as you put the finishing touches on your solution. Be sure to make up new test cases to fully debug your program and dont forget to comment your code! Complete the provided template [README.txt](./README.txt). You must do this assignment on your own, as described in the [Collaboration Policy & Academic Integrity](https://www.cs.rpi.edu/academics/courses/spring25/csci1200/academic_integrity.php) page. If you did discuss the problem or error messages, etc. with anyone, please list their names in your README.txt file. Prepare and submit your assignment as instructed on the course webpage. Please ask a TA if you need help preparing your assignment for submission.
**Due Date**: 01/16/2025, 10pm.
## Rubric
13 pts
- README.txt Completed (3 pts)
- One of name, collaborators, or hours not filled in. (-1)
- Two or more of name, collaborators, or hours not filled in. (-2)
- No reflection. (-1)
- STL Vector & String (3 pts)
- Uses data structures which have not been covered in this class. (-3)
- Did not use STL vector (-2)
- Did not use STL string (-2)
- Program Structure (7 pts)
- No credit (significantly incomplete implementation) (-7)
- Putting almost everything in the main function. It's better to create separate functions for different tasks. (-2)
- Improper uses or omissions of const and reference. (-1)
- Almost total lack of helpful comments. (-4)
- Too few comments. (-2)
- Contains useless comments like commented-out code, terminal commands, or silly notes. (-1)
- Overly cramped, excessive whitespace, or poor indentation. (-1)
- Lacks error checking (num of args, invalid file names, invalid command, etc.) (-1)
- Poor choice of variable names: non-descriptive names (e.g. 'vec', 'str', 'var'), single-letter variable names (except single loop counter), etc. (-2)
- Uses global variables. (-1)
- Overly long lines, in excess of 100 or so characters. It's recommended to keep all lines short and put comments on their own lines. (-1)
{{< /details >}}
## Supporting Files
{{< link href="spotify_playlists.7z" content="spotify_playlists.7z" title="Download spotify_playlists.7z" download="spotify_playlists.7z" card=true >}}
## Program Design
Before start, we need to find out what need to do. Let's draw a flowchart to exam what are the steps.
```mermaid
flowchart TB
A(("Start")) --> D["Read 'playlist file', 'actions file'"]
subgraph "Initialize"
D --> E["Find the index of current song (if any)"]
end
E --> F{"For each action in 'actions file'"}
subgraph "Process Actions"
F -- next --> G["Find the index of current song (if any)"]
G --> H["Remove 'current' from current song"]
H --> I{"Is it the last song?"}
I -- Yes --> J["Set index to 0"]
I -- No --> K["Set index to index+1"]
J --> L["Mark new current song"]
K --> L["Mark new current song"]
F -- previous --> M["Find the index of current song (if any)"]
M --> N["Remove 'current' from current song"]
N --> O{"Is it the first song?"}
O -- Yes --> P["Set index to last song"]
O -- No --> Q["Set index to index-1"]
P --> R["Mark new current song"]
Q --> R["Mark new current song"]
F -- add --> S["'Build' the new song string"]
S --> T["Append to playlist"]
F -- remove --> U["'Build' the song string to remove"]
U --> V["Find the first occurrence (if any)"]
V --> W["Remove from playlist (ignore if not found)"]
F -- move --> X["'Build' the song string to move"]
X --> Y["Check 'move' destination"]
Y --> Z["Find the first occurrence (if any)"]
Z --> ZA["Remove from playlist (ignore if not found)"]
ZA --> ZB["Insert at new position"]
end
```
Then, we can plan what function to use in this program.
```mermaid
flowchart TB
subgraph "Main"
main["main()"]
end
subgraph "File IO"
load_list("load_list()")
get_text("get_text()")
write_list("write_list()")
end
subgraph "Helpers"
is_all_digits("is_all_digits()")
tokenizer("tokenizer()")
check_in_list("check_in_list()")
remove_in_list("remove_in_list()")
get_current("get_current()")
build_song("build_song()")
end
%% Connections
main --> load_list
load_list --> get_text
main --> write_list
main --> is_all_digits
main --> tokenizer
main --> check_in_list
main --> remove_in_list
main --> get_current
main --> build_song
remove_in_list --> check_in_list
```
## Pitfalls
1. It's hard to load each argument correctly. For example, song can include spaces, the singer can also have space or something else in their names. But luckily, we don't need care too much about the middle part. I mean the first argument is always the command. The rest of it is the song information we need to add / delete. I split the arguments / songs into parts by space. `<action> <song> <location>` and take the each part as needed.
2. When I am moving / adding the song. It's possible that the song has a `current` string at the end of line (in the playlist file already). If we only check the song's name, it will not pass some test cases. For example, this is how I handle this case for `move` command.
```diff
if (tokens[0] == "move") {
if (is_all_digits(tokens.back())){
//set target position
int dest = std::stoi(tokens.back());
//build song from tokens
std::string song;
song = build_song(tokens, 1, tokens.size() - 1);
+ //fix song name if it has current tag
+ if (!check_in_list(song, playlist) &&
+ !check_in_list(song + " current", playlist)) {continue;}
+ else if (check_in_list(song + " current", playlist)) {
+ song += " current";
+ }
remove_in_list(song, playlist);
playlist.insert(playlist.begin() + dest - 1, song);
} else {
std::cout << "ERROR: Missing move destination" << std::endl;
continue;
}
}
```
I added another check with the song + `current` in the playlist before I actually add it into the playlist.
## Solution
### nyplaylists.cpp
```cpp
//An implement of CSCI-1200 HW1 Spotify Playlists
//Date: 2025/1/16
//Author: JamesFlare
#include <vector>
#include <string>
#include <iostream>
#include <fstream>
std::string get_text(const std::string &fname) {
//load a text file into a string
std::ifstream inFile(fname);
//check if file exists
if (!inFile) {
std::cout << "Error: File not found" << std::endl;
return "";
}
std::string text;
std::string line;
while (std::getline(inFile, line)) {
text += line;
text += "\n";
}
inFile.close();
return text;
}
std::vector<std::string> load_list(const std::string &fname) {
//load a text file into a vector of strings
std::string text = get_text(fname);
std::vector<std::string> lines;
std::size_t start = 0;
std::size_t end = 0;
while ((end = text.find('\n', start)) != std::string::npos) {
lines.push_back(text.substr(start, end - start));
start = end + 1;
}
if (start < text.size()) {
lines.push_back(text.substr(start));
}
return lines;
}
bool is_all_digits(const std::string& s) {
//check if string is int
for (char c : s) {
if (!std::isdigit(static_cast<unsigned char>(c))) {
return false;
}
}
return !s.empty();
}
std::vector<std::string> tokenizer(const std::string &s) {
//split string into tokens
std::vector<std::string> tokens;
std::string token;
for (char c : s) {
if (c == ' ') {
tokens.push_back(token);
token = "";
} else {
token += c;
}
}
tokens.push_back(token);
return tokens;
}
bool check_in_list (const std::string &s, const std::vector<std::string> &list) {
//check if string is in list
for (std::string item : list) {
if (s == item) {
return true;
}
}
return false;
}
void remove_in_list (const std::string &s, std::vector<std::string> &list) {
//remove string from list
if (!check_in_list(s, list)) {return;}
for (int i = 0; i < list.size(); i++) {
if (list[i] == s) {
list.erase(list.begin() + i);
return;
}
}
}
int get_current (std::vector<std::string> &playlist) {
//return the index of the string has word current at the end
for (int i = 0; i < playlist.size(); i++) {
if (playlist[i].find("current") != std::string::npos) {
return i;
}
}
return -1;
}
std::string build_song (const std::vector<std::string> &tokens, const int &start, const int &end) {
//build string from tokens w/ start and end positions
std::string song;
for (int i = start; i < end; i++) {
song += tokens[i];
if (i != end - 1) {
song += " ";
}
}
return song;
}
void write_list(const std::string &fname, const std::vector<std::string> &list) {
//write list to file
std::ofstream outFile(fname);
for (std::string line : list) {
outFile << line << std::endl;
}
outFile.close();
}
int main(int argc, char *argv[]) {
//take 3 arguments
if (argc < 3) {
std::cout << "Error: Not enough arguments" << std::endl;
return 1;
}
//load arguments
std::string playlist_fname = argv[1];
std::string action_list_fname = argv[2];
std::string output_fname = argv[3];
//load working files
std::vector<std::string> playlist = load_list(playlist_fname);
std::vector<std::string> action_list = load_list(action_list_fname);
//get current playing song id
int current_song_id = get_current(playlist);
//execute actions
for (std::string command : action_list) {
//split command into tokens
std::vector<std::string> tokens = tokenizer(command);
if (tokens[0] == "next") {
current_song_id = get_current(playlist);
//remove "current" tag
playlist[current_song_id].erase(playlist[current_song_id].length() - 8);
if (current_song_id == playlist.size() - 1) {
current_song_id = 0;
} else {
current_song_id++;
}
//update current song
playlist[current_song_id] += " current";
}
if (tokens[0] == "previous") {
current_song_id = get_current(playlist);
//remove "current" tag
playlist[current_song_id].erase(playlist[current_song_id].length() - 8);
if (current_song_id == 0) {
current_song_id = playlist.size() - 1;
} else {
current_song_id--;
}
//update current song
playlist[current_song_id] += " current";
}
if (tokens[0] == "add") {
std::string song;
song = build_song(tokens, 1, tokens.size());
playlist.push_back(song);
}
if (tokens[0] == "remove") {
std::string song;
song = build_song(tokens, 1, tokens.size());
remove_in_list(song, playlist);
}
if (tokens[0] == "move") {
if (is_all_digits(tokens.back())){
//set target position
int dest = std::stoi(tokens.back());
//build song from tokens
std::string song;
song = build_song(tokens, 1, tokens.size() - 1);
//fix song name if it has current tag
if (!check_in_list(song, playlist) &&
!check_in_list(song + " current", playlist)) {continue;}
else if (check_in_list(song + " current", playlist)) {
song += " current";
}
remove_in_list(song, playlist);
playlist.insert(playlist.begin() + dest - 1, song);
} else {
std::cout << "ERROR: Missing move destination" << std::endl;
continue;
}
}
}
//write back file
write_list(output_fname, playlist);
return 0;
}
```

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

View File

@@ -1,6 +1,6 @@
---
slug: "excalidraw-full-stack-docker"
title: "Excalidraw Full-Stack Self-Deployment"
title: "Deploying a Full-stack Excalidraw Using Docker"
subtitle: ""
date: 2023-01-13T15:54:36+08:00
lastmod: 2024-03-11T12:39:36-05:00
@@ -23,7 +23,8 @@ tags:
categories:
- Tutorials
- Sharing
collections:
- Docker Compose
hiddenFromHomePage: false
hiddenFromSearch: false
@@ -43,8 +44,8 @@ seo:
images: []
repost:
enable: true
url: ""
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/theme-documentation-content/#front-matter
---

View File

@@ -0,0 +1,113 @@
---
title: Fix the Issue of Flarum Emails Not Being Sent Due to Queue.
subtitle:
date: 2024-10-25T12:30:55-04:00
slug: flarum-queue
draft: false
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description: This blog post addresses an issue with Flarum's email delivery, caused by improper Queue handling. It provides solutions using Docker commands and a Flarum plugin to ensure emails are sent correctly, especially when running Flarum in a Docker container.
keywords:
license:
comment: true
weight: 0
tags:
- Docker
- Flarum
- PHP
- Open Source
categories:
- Tutorials
- Sharing
collections:
- Docker Compose
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary: This blog post addresses an issue with Flarum's email delivery, caused by improper Queue handling. It provides solutions using Docker commands and a Flarum plugin to ensure emails are sent correctly, especially when running Flarum in a Docker container.
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: false
lightgallery: false
password:
message:
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->
## Introduction
Recently, while configuring Flarum, I encountered a peculiar issue where users were not receiving emails despite the Email SMTP configuration being correct. This included, but was not limited to, registration activation, password recovery, notifications, etc.
## Cause of the Issue
After searching through the sending logs, I discovered that this problem did not exist a few months ago. Reviewing my recent operations and community feedback, I narrowed the issue down to the Queue. In [Redis sessions, cache & queues](https://discuss.flarum.org/d/21873-redis-sessions-cache-queues), there is mention of the Queue. I overlooked this when initially using Redis.
## Solution
One approach is to execute `php flarum queue:work`, as suggested. However, this command opens an uninterrupted window, and we can use a process guardian to ensure it runs correctly. My Flarum instance runs in a Docker container, which is inconvenient for me. Nevertheless, we can run it first to see if it resolves the email sending issue.
```bash
docker exec flarum /bin/sh -c "cd /flarum/app && php flarum schedule:run"
```
I observed that emails were sent correctly after execution, confirming that the issue was due to the Queue not running properly.
The second method, which I ultimately adopted, involves a small plugin provided by [Database Queue - the simplest queue, even for shared hosting](https://discuss.flarum.org/d/28151-database-queue-the-simplest-queue-even-for-shared-hosting). This plugin uses Cron tasks to handle the Queue, requiring only that Cron runs normally.
To install the plugin, since I am in a Docker container, I reconstructed the command:
```bash
docker exec flarum /bin/sh -c "cd /flarum/app && composer require blomstra/database-queue:*"
```
`flarum` is the name of my container; you can modify it accordingly.
Then, restart Flarum and check if Cron has been correctly added. You should see something similar to:
```bash
root@debain:~# docker exec flarum /bin/sh -c "cd /flarum/app && php flarum schedule:list"
+-------------------------------------------------------+-----------+---------------------------------------------------------------------------------------------------------------------+----------------------------+
| Command | Interval | Description | Next Due |
+-------------------------------------------------------+-----------+---------------------------------------------------------------------------------------------------------------------+----------------------------+
| '/usr/bin/php8' 'flarum' drafts:publish | * * * * * | Publish all scheduled drafts. | 2024-10-25 17:00:00 +00:00 |
| '/usr/bin/php8' 'flarum' fof:best-answer:notify | 0 * * * * | After a configurable number of days, notifies OP of discussions with no post selected as best answer to select one. | 2024-10-25 17:00:00 +00:00 |
| '/usr/bin/php8' 'flarum' queue:work --stop-when-empty | * * * * * | | 2024-10-25 17:00:00 +00:00 |
+-------------------------------------------------------+-----------+---------------------------------------------------------------------------------------------------------------------+----------------------------+
```
`'/usr/bin/php8' 'flarum' queue:work --stop-when-empty` is what we expect, indicating no issues.
Remember to add Cron if you haven't already. You can refer to my example. First, enter crontab:
```bash
crontab -e
```
Add:
```bash
* * * * * /usr/bin/docker exec flarum /bin/sh -c "cd /flarum/app && php flarum schedule:run" >> /dev/null 2>&1
```
## Conclusion
Barring any unforeseen circumstances, you should have resolved the issue of emails not being sent. If emails still fail to send, it may be a configuration issue. Ensure the SMTP information is correct before starting and test it.
## References
- [Redis sessions, cache & queues](https://discuss.flarum.org/d/21873-redis-sessions-cache-queues)
- [Database Queue - the simplest queue, even for shared hosting](https://discuss.flarum.org/d/28151-database-queue-the-simplest-queue-even-for-shared-hosting)

View File

@@ -0,0 +1,371 @@
---
title: Use Docker Compose to Deploy the LobeChat Server Database Version
subtitle:
date: 2024-09-15T04:52:21-04:00
slug: install-lobechat-db
draft: false
author:
name: James
link: https://www.jamesflare.com
email:
avatar: /site-logo.avif
description: This blog post offers a comprehensive guide on setting up LobeChat DB version, including configuring Logto for authentication, MinIO for S3 storage, and PostgreSQL for the database. It also covers customizing Logto's sign-in experience and enabling various models for LobeChat.
keywords: ["LobeChat", "Logto", "MinIO", "PostgreSQL", "Docker", "S3 Storage", "Authentication", "Database Configuration"]
license:
comment: true
weight: 0
tags:
- Open Source
- LobeChat
- Docker
categories:
- Tutorials
- Sharing
collections:
- Docker Compose
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
hiddenFromRelated: false
summary: This blog post offers a comprehensive guide on setting up LobeChat DB version, including configuring Logto for authentication, MinIO for S3 storage, and PostgreSQL for the database. It also covers customizing Logto's sign-in experience and enabling various models for LobeChat.
resources:
- name: featured-image
src: featured-image.jpg
- name: featured-image-preview
src: featured-image-preview.jpg
toc: true
math: false
lightgallery: false
password:
message:
repost:
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---
<!--more-->
## Introduction
By default, LobeChat uses IndexedDB to store user data, meaning the data is stored locally in the browser. Consequently, it becomes impossible to synchronize across multiple devices and poses a risk of data loss. Meanwhile, there is a server database version of LobeChat that addresses these issues and also allows for knowledge base functionality.
However, configuring the LobeChat DB version isn't straightforward. It involves several parts: setting up the database, configuring authentication services, and configuring S3 storage[^1].
[^1]: See official documentation https://lobehub.com/en/docs/self-hosting/server-database
## Configuring Logto
I recommend deploying the Logto service separately to potentially use it in other projects and manage them independently.
First, create a directory and enter it:
```bash
mkdir logto
cd logto
```
Here is my `docker-compose.yaml` file for reference. Modify the relevant parts according to your own setup.
```yaml
services:
postgresql:
image: postgres:16
container_name: logto-postgres
volumes:
- './data:/var/lib/postgresql/data'
environment:
- 'POSTGRES_DB=logto'
- 'POSTGRES_PASSWORD=logto'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U postgres']
interval: 5s
timeout: 5s
retries: 5
restart: always
logto:
image: svhd/logto:latest
container_name: logto
ports:
- '127.0.0.1:3034:3034'
- '127.0.0.1:3035:3035'
depends_on:
postgresql:
condition: service_healthy
environment:
- 'PORT=3034'
- 'ADMIN_PORT=3035'
- 'TRUST_PROXY_HEADER=1'
- 'DB_URL=postgresql://postgres:logto@postgresql:5432/logto'
- 'ENDPOINT=https://logto.example.com'
- 'ADMIN_ENDPOINT=https://logto-admin.example.com'
entrypoint: ['sh', '-c', 'npm run cli db seed -- --swe && npm start']
```
After modifying, write the `docker-compose.yaml` file. Then start the container:
```bash
docker compose up -d
```
> [!WARNING]
> Don't forget to set `X-Forwarded-Proto` header in Nginx!
this proxy must support HTTPS because all of Logto's APIs must be run in a secure environment, otherwise errors will occur[^2]. Additionally, just having HTTPS isn't enough; you also need to set the `X-Forwarded-Proto` header value to `https` to inform Logto that users are accessing it via HTTPS. I use Nginx as my reverse proxy service and provide a reference configuration below (modify according to your situation).
[^2]: Discussion on errors https://github.com/logto-io/logto/issues/4279
```nginx
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://127.0.0.1:3034;
proxy_redirect off;
}
```
If you are manually configuring your Nginx configuration file rather than using a graphical tool like Nginx Proxy Manager, you need to complete other parts on your own (do not directly copy). In other words, if you use Nginx Proxy Manager, you can modify the `proxy_pass` and then directly input it into the Advanced settings of the corresponding reverse proxy.
Afterwards, you can access the ADMIN_ENDPOINT to complete registration and configuration (the first registered account will automatically become an admin), remember to add an Application (prepare for LobeChat DB version installation), with a type selected as Next.js (App Router). Several key parameters should not be written incorrectly (replace domain names with your own LobeChat DB instance):
- `Redirect URIs` write `https://lobe.example.com/api/auth/callback/logto`
- `Post sign-out redirect URIs` write `https://lobe.example.com/`
- `CORS allowed origins` write `https://lobe.example.com`
There are three parameters that we will use when configuring the LobeChat DB version: Issuer endpoint, App ID, and App secrets (add one). Note them down.
You can also visit the `/demo-app` path of your user ENDPOINT to test login and registration functions. If everything is fine, then Logto should be properly configured, allowing you to proceed with further steps.
## Configuring MinIO
I recommend deploying MinIO separately for potential use in other projects as well.
Create a directory and enter it:
```bash
mkdir minio
cd minio
```
Here is my `docker-compose.yaml` file for reference:
```yaml
services:
minio:
image: quay.io/minio/minio
container_name: minio
restart: unless-stopped
environment:
- MINIO_DOMAIN=minio.example.com
- MINIO_SERVER_URL=https://minio.example.com
- MINIO_BROWSER_REDIRECT_URL=https://console.minio.example.com
- MINIO_ROOT_USER=xxxx #change it
- MINIO_ROOT_PASSWORD=xxxxx #change it
ports:
- "9000:9000"
- "9090:9090"
volumes:
- ./data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
command: server /data --console-address ":9090"
```
After modifying, write the `docker-compose.yaml` file. Then start the container:
```bash
docker compose up -d
```
Subsequently, log into your MinIO instance from your MINIO_BROWSER_REDIRECT_URL, create a Bucket (e.g., name it as `lobe`; if you change this, remember to modify corresponding configuration files), and configure an Access Policy similar to the following JSON file:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": [
"*"
]
},
"Action": [
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::lobe"
]
},
{
"Effect": "Allow",
"Principal": {
"AWS": [
"*"
]
},
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::lobe"
],
"Condition": {
"StringEquals": {
"s3:prefix": [
"files/*"
]
}
}
},
{
"Effect": "Allow",
"Principal": {
"AWS": [
"*"
]
},
"Action": [
"s3:DeleteObject",
"s3:GetObject",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::lobe/files/**"
]
}
]
}
```
Then go to Access Keys and create a token, save these values as they will be used in the LobeChat DB version configuration.
## Configuring LobeChat DB Version
Now we start configuring the LobeChat DB version. First, create a directory and enter it:
```bash
mkdir lobe-db
cd lobe-db
```
Here is my `docker-compose.yaml` file for reference; remember to modify according to your setup:
```yaml
services:
postgresql:
image: pgvector/pgvector:pg16
container_name: lobe-postgres
volumes:
- './data:/var/lib/postgresql/data'
environment:
- 'POSTGRES_DB=lobe-db'
- 'POSTGRES_PASSWORD=lobe-db'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U postgres']
interval: 5s
timeout: 5s
retries: 5
restart: always
lobe:
image: lobehub/lobe-chat-database
container_name: lobe-database
ports:
- 127.0.0.1:3033:3210
depends_on:
postgresql:
condition: service_healthy
environment:
- 'APP_URL=https://lobe-db.example.com'
- 'NEXT_AUTH_SSO_PROVIDERS=logto'
- 'KEY_VAULTS_SECRET=NIdSgLKmeFhWmTuQKQYzn99oYk64aY0JTSssZuiWR8A=' #generate using `openssl rand -base64 32`
- 'NEXT_AUTH_SECRET=+IHNVxT2qZpA8J+vnvuwA5Daqz4UFFJOahK6z/GsNIo=' #generate using `openssl rand -base64 32`
- 'NEXTAUTH_URL=https://lobe.example.com/api/auth'
- 'LOGTO_ISSUER=https://logto.example.com/oidc' #Issuer endpoint
- 'LOGTO_CLIENT_ID=xxxx' #App ID
- 'LOGTO_CLIENT_SECRET=xxxx' #App secrets
- 'DATABASE_URL=postgresql://postgres:lobe-db@postgresql:5432/lobe-db'
- 'POSTGRES_PASSWORD=lobe-db'
- 'LOBE_DB_NAME=lobe-db'
- 'S3_ENDPOINT=https://minio.example.com'
- 'S3_BUCKET=lobe'
- 'S3_PUBLIC_DOMAIN=https://minio.example.com'
- 'S3_ACCESS_KEY_ID=xxxxx'
- 'S3_SECRET_ACCESS_KEY=xxxxxx'
- 'S3_ENABLE_PATH_STYLE=1'
- 'OPENAI_API_KEY=sk-xxxxxx' #your OpenAI API Key
- 'OPENAI_PROXY_URL=https://api.openai.com/v1'
- 'OPENAI_MODEL_LIST=-all,+gpt-4o,+gpt-4o-mini,+claude-3-5-sonnet-20240620,+deepseek-chat,+o1-preview,+o1-mini' #change on your own needs, see https://lobehub.com/zh/docs/self-hosting/environment-variables/model-provider#openai-model-list
restart: always
```
For security reasons, `KEY_VAULTS_SECRET` and `NEXT_AUTH_SECRET` should be a random 32-character string. You can generate it using the command `openssl rand -base64 32`.
Then modify domain names in environment variables to your own setup. Additionally, several Logto values need to be set:
- `Issuer endpoint` corresponds to `LOGTO_ISSUER`
- `App ID` corresponds to `LOGTO_CLIENT_ID`
- `App secrets` corresponds to `LOGTO_CLIENT_SECRET`
These can all be found on the Application page you created.
For S3 configuration, also modify accordingly (e.g., `S3_ENDPOINT`, `S3_BUCKET`, `S3_PUBLIC_DOMAIN`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`). As for `S3_ENABLE_PATH_STYLE`, it is usually set to `1`. If your S3 provider uses virtual-host style, change this value to `0`.
{{< admonition type=question title="What are the differences between path-style and virtual-host?" open=true >}}
Path-style and virtual-host are different ways of accessing buckets and objects in S3. The URL structure and domain name resolution differ:
Assuming your S3 provider's domain is s3.example.net, bucket is mybucket, object is config.env, the specific differences are as follows:
- Path-style: `s3.example.net/mybucket/config.env`
- Virtual-host: `mybucket.s3.example.net/config.env`
{{< /admonition >}}
Finally, configure your API-related content (optional). My configuration example uses OpenAI. If you do not set it up on the server side, users will need to enter their own keys in the frontend.
After modifying, write the `docker-compose.yaml` file and start the container:
```bash
docker compose up -d
```
In theory, you can now access LobeChat DB version. Before deploying to production, carefully check for any security issues. If you have questions, feel free to comment.
## Additional Content
### Customizing Logto Login/Registration Options
On the Logto management page, there is a Sign-in experience section with various customization options such as enabling or disabling registration and using social media SSO. By default, the Sign-up identifier is Username; I recommend configuring SMTP in Connectors to change it to Email address so users can recover their passwords via email.
### Enabling Dark Mode for Logto Login/Registration Pages
On the Logto management page, under Sign-in experience, check Enable dark mode to turn on dark mode.
### Adding GitHub Login/Registration Options in Logto
In the Logto management page, go to Connectors and add GitHub under Social connectors. Other options are similar.
### Configuring Additional Models
LobeChat supports many models; you can set different environment variables to enable them. See the official documentation for `OPENAI_MODEL_LIST` configuration options and explanations [here](https://lobehub.com/en/docs/self-hosting/environment-variables/model-provider). There are also other model provider options like DeepSeek.
You can retrieve Model List via API on the frontend to select needed models.
## References
- [Deploying Server-Side Database for LobeChat](https://lobehub.com/en/docs/self-hosting/server-database)
- [bug: use docker deploy logto v1.6 will always redirect to /unknown-session #4279](https://github.com/logto-io/logto/issues/4279)
- [Deployment | Logto docs #reverse-proxy](https://docs.logto.io/docs/recipes/deployment/#reverse-proxy)
- [Deploying LobeChat Server Database with Docker Compose](https://lobehub.com/en/docs/self-hosting/server-database/docker-compose)
- [LobeChat Model Service Providers - Environment Variables and Configuration #openai-model-list](https://lobehub.com/en/docs/self-hosting/environment-variables/model-provider#openai-model-list)

View File

@@ -1,5 +1,5 @@
---
title: Migrate Umami Docker From One Server to Another
title: Migrate the Docker-deployed Umami From One Server to Another.
subtitle:
date: 2024-03-11T18:03:39-04:00
slug: umami-docker-migration
@@ -15,12 +15,14 @@ license:
comment: true
weight: 0
tags:
- PostgreSQL
- Open Source
- Docker
- Umami
- PostgreSQL
- Open Source
- Docker
- Umami
categories:
- Tutorials
- Tutorials
collections:
- Docker Compose
hiddenFromHomePage: false
hiddenFromSearch: false
hiddenFromRss: false
@@ -37,7 +39,7 @@ lightgallery: false
password:
message:
repost:
enable: true
enable: false
url:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Some files were not shown because too many files have changed in this diff Show More