Compare commits
80 Commits
eefccf904a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1340ff2a0e | ||
|
|
e2398a8a39 | ||
|
|
6a996f1c09 | ||
|
|
68ffcf4956 | ||
|
|
a38c21b1a9 | ||
|
|
54bf5ca168 | ||
|
|
df2c11ae67 | ||
|
|
c501581f39 | ||
|
|
dc9bda2b37 | ||
|
|
2f3f75d3f2 | ||
| aebff3d595 | |||
|
|
83fb593dd6 | ||
|
|
764bb967f6 | ||
| d558d5834e | |||
|
|
21e87884c6 | ||
|
|
c968a3ae00 | ||
|
|
6726a156b3 | ||
| a10a010701 | |||
|
|
f4af875bb3 | ||
|
|
4b57659747 | ||
|
|
c7f48c6fe9 | ||
| 594aa545da | |||
| 286a3d2f32 | |||
| df94c4721b | |||
|
|
2fa41ea756 | ||
|
|
4eb7cd640b | ||
|
|
5f52c8b518 | ||
|
|
0e9f89d331 | ||
|
|
44447ec5b8 | ||
|
|
df21d69df4 | ||
|
|
47358d25a8 | ||
|
|
fc70b1776c | ||
|
|
e1e1e305bc | ||
|
|
323c90005b | ||
|
|
ccb5641919 | ||
|
|
2a881236cc | ||
|
|
218dcad925 | ||
|
|
02cce87a93 | ||
| 637bdeb6c0 | |||
|
|
7222828595 | ||
|
|
7878b7623b | ||
|
|
d3c9fe3a83 | ||
| 71e64a0329 | |||
| 9240ca2d83 | |||
| cedd0a3830 | |||
| 54019e743b | |||
| a148c972e1 | |||
| 521ce492c0 | |||
| 34011a8589 | |||
| dcf2763424 | |||
| 021d9935d4 | |||
| f1677360fc | |||
| 574ceec1ff | |||
| bf4a723a7a | |||
| 5de976f4b3 | |||
| a24aec9914 | |||
| c86e21521b | |||
|
|
2a1ed5983f | ||
|
|
06085a2b19 | ||
|
|
a100b6794e | ||
|
|
3b991a8135 | ||
|
|
320e5c296b | ||
|
|
1a0f9f7bf1 | ||
|
|
8e07a57a6a | ||
|
|
3ea3cf0c1c | ||
|
|
ccb8ed859c | ||
|
|
e076e5ba36 | ||
|
|
8bb682728e | ||
|
|
a2261bab32 | ||
|
|
3b6a0249bb | ||
|
|
c53391f4b6 | ||
|
|
6e8ecc4dbc | ||
|
|
861f4792ed | ||
|
|
33584d0123 | ||
|
|
4ff4a125ff | ||
|
|
03a1cf73d6 | ||
|
|
4980de8456 | ||
|
|
70b115b8d3 | ||
|
|
c54ab7bea0 | ||
|
|
5443c93d1e |
5
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
*.lock
|
||||
public/
|
||||
resources/
|
||||
resources/_gen/
|
||||
isableFastRander/
|
||||
.hugo_build.lock
|
||||
*Zone.Identifier
|
||||
7
.gitmodules
vendored
@@ -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
|
||||
|
||||
15
README.md
@@ -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
|
||||
|
||||
@@ -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
@@ -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 >}}
|
||||
45
archetypes/post-bundle/index.md
Normal 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
@@ -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
@@ -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;
|
||||
}
|
||||
19
assets/css/_shortcodes/_columns.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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 don’t, 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
|
||||
16
config/_default/languages.toml
Normal 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 国际许可协议。"
|
||||
81
config/_default/menus.en.toml
Normal 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"
|
||||
81
config/_default/menus.zh-cn.toml
Normal 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"
|
||||
34
content/en/friends/index.md
Normal 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 >}}
|
||||
BIN
content/en/friends/knowscount-logo.webp
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
31
content/en/me/index.md
Normal 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 >}}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
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 %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&d="
|
||||
data-srcset="https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&d=, https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&d= 1.5x, https://gravatar.jamesflare.com/avatar/75cea16f157b9c5da5435379ab6cf294?s=32&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"]
|
||||
```
|
||||
@@ -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 |
BIN
content/en/posts/csci-1100/crib-sheets/Final Crib Sheet A.pdf
Normal file
BIN
content/en/posts/csci-1100/crib-sheets/Test 2 Crib Sheet A.pdf
Normal file
BIN
content/en/posts/csci-1100/crib-sheets/Test 2 Crib Sheet B.pdf
Normal file
BIN
content/en/posts/csci-1100/crib-sheets/Test 3 Crib Sheet A.pdf
Normal file
BIN
content/en/posts/csci-1100/crib-sheets/Test 3 Crib Sheet B.pdf
Normal file
84
content/en/posts/csci-1100/crib-sheets/index.md
Normal 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>
|
||||
2097
content/en/posts/csci-1100/exam-2-overview/index.md
Normal file
1781
content/en/posts/csci-1100/exam-3-overview/index.md
Normal file
139
content/en/posts/csci-1100/exam-4-overview/index.md
Normal 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]
|
||||
> ```
|
||||
BIN
content/en/posts/csci-1100/hw-1/HW1.zip
Normal file
314
content/en/posts/csci-1100/hw-1/index.md
Normal 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)
|
||||
```
|
||||
BIN
content/en/posts/csci-1100/hw-2/HW2.zip
Normal file
325
content/en/posts/csci-1100/hw-2/index.md
Normal 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 else’s 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 else’s 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 won’t 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.")
|
||||
```
|
||||
BIN
content/en/posts/csci-1100/hw-3/HW3.zip
Normal file
435
content/en/posts/csci-1100/hw-3/index.md
Normal 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 else’s 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 else’s 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 doesn’t 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 Pikachu’s 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 year’s 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)
|
||||
```
|
||||
BIN
content/en/posts/csci-1100/hw-4/HW4.zip
Normal file
385
content/en/posts/csci-1100/hw-4/index.md
Normal 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 state’s 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'. Here’s 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("...")
|
||||
```
|
||||
BIN
content/en/posts/csci-1100/hw-5/HW5.zip
Normal file
473
content/en/posts/csci-1100/hw-5/index.md
Normal 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 — we’ll 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)
|
||||
```
|
||||
BIN
content/en/posts/csci-1100/hw-6/HW6.zip
Normal file
436
content/en/posts/csci-1100/hw-6/index.md
Normal 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)))
|
||||
```
|
||||
BIN
content/en/posts/csci-1100/hw-7/HW7.zip
Normal file
483
content/en/posts/csci-1100/hw-7/index.md
Normal 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 else’s 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
|
||||
```
|
||||
BIN
content/en/posts/csci-1100/hw-8/HW8.zip
Normal file
154
content/en/posts/csci-1100/hw-8/index.md
Normal 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.
|
||||
504
content/en/posts/csci-1200/hw-1/index.md
Normal 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: don’t 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 don’t 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;
|
||||
}
|
||||
```
|
||||
BIN
content/en/posts/csci-1200/hw-1/spotify_playlists.7z
Normal file
|
After Width: | Height: | Size: 64 KiB |
1297
content/en/posts/csci-1200/hw-2/index.md
Normal file
BIN
content/en/posts/csci-1200/hw-2/ride_sharing.7z
Normal file
1206
content/en/posts/csci-1200/hw-3/index.md
Normal file
BIN
content/en/posts/csci-1200/hw-3/matrix1_array.avif
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
content/en/posts/csci-1200/hw-3/matrix_class.7z
Normal 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
|
||||
---
|
||||
|
||||
113
content/en/posts/docker-compose/flarum-queue/index.md
Normal 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)
|
||||
371
content/en/posts/docker-compose/lobechat-db/index.md
Normal 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)
|
||||
@@ -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
|
||||
|
||||
296
content/en/posts/dsas/engage-cca-api/index.md
Normal file
1
content/en/posts/dsas/engage-cca-api/raw.txt
Normal file
BIN
content/en/posts/ecse-1010/lab01/Lab01.pdf
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 504 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 541 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 876 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 674 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 36 KiB |
1159
content/en/posts/ecse-1010/lab01/index.md
Normal file
BIN
content/en/posts/ecse-1010/lab02/Lab02.pdf
Normal file
BIN
content/en/posts/ecse-1010/lab02/P1-1-a.avif
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-1-b-2.avif
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-1-b.avif
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-1-c-2.avif
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-1-c.avif
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-2-a.avif
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-2-b.avif
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-3-a.avif
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-4-a-2.avif
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-4-a.avif
Normal file
|
After Width: | Height: | Size: 368 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-4-b.avif
Normal file
|
After Width: | Height: | Size: 287 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-4-c-2.avif
Normal file
|
After Width: | Height: | Size: 400 KiB |
BIN
content/en/posts/ecse-1010/lab02/P1-4-c.avif
Normal file
|
After Width: | Height: | Size: 9.4 KiB |