├── public ├── .nojekyll ├── robots.txt ├── _headers ├── favicon.ico ├── avatar_192.webp ├── icp.gov.moe.png ├── pwa-192x192.png ├── pwa-512x512.png ├── favicon-16x16.png ├── favicon-32x32.png ├── apple-touch-icon.png ├── katex │ └── fonts │ │ ├── KaTeX_Main-Bold.ttf │ │ ├── KaTeX_AMS-Regular.ttf │ │ ├── KaTeX_Main-Bold.woff │ │ ├── KaTeX_Main-Bold.woff2 │ │ ├── KaTeX_Main-Italic.ttf │ │ ├── KaTeX_Math-Italic.ttf │ │ ├── KaTeX_AMS-Regular.woff │ │ ├── KaTeX_AMS-Regular.woff2 │ │ ├── KaTeX_Fraktur-Bold.ttf │ │ ├── KaTeX_Fraktur-Bold.woff │ │ ├── KaTeX_Fraktur-Bold.woff2 │ │ ├── KaTeX_Main-Italic.woff │ │ ├── KaTeX_Main-Italic.woff2 │ │ ├── KaTeX_Main-Regular.ttf │ │ ├── KaTeX_Main-Regular.woff │ │ ├── KaTeX_Main-Regular.woff2 │ │ ├── KaTeX_Math-Italic.woff │ │ ├── KaTeX_Math-Italic.woff2 │ │ ├── KaTeX_SansSerif-Bold.ttf │ │ ├── KaTeX_Script-Regular.ttf │ │ ├── KaTeX_Size1-Regular.ttf │ │ ├── KaTeX_Size1-Regular.woff │ │ ├── KaTeX_Size2-Regular.ttf │ │ ├── KaTeX_Size2-Regular.woff │ │ ├── KaTeX_Size3-Regular.ttf │ │ ├── KaTeX_Size3-Regular.woff │ │ ├── KaTeX_Size4-Regular.ttf │ │ ├── KaTeX_Size4-Regular.woff │ │ ├── KaTeX_Caligraphic-Bold.ttf │ │ ├── KaTeX_Fraktur-Regular.ttf │ │ ├── KaTeX_Fraktur-Regular.woff │ │ ├── KaTeX_Main-BoldItalic.ttf │ │ ├── KaTeX_Main-BoldItalic.woff │ │ ├── KaTeX_Math-BoldItalic.ttf │ │ ├── KaTeX_Math-BoldItalic.woff │ │ ├── KaTeX_SansSerif-Bold.woff │ │ ├── KaTeX_SansSerif-Bold.woff2 │ │ ├── KaTeX_SansSerif-Italic.ttf │ │ ├── KaTeX_Script-Regular.woff │ │ ├── KaTeX_Script-Regular.woff2 │ │ ├── KaTeX_Size1-Regular.woff2 │ │ ├── KaTeX_Size2-Regular.woff2 │ │ ├── KaTeX_Size3-Regular.woff2 │ │ ├── KaTeX_Size4-Regular.woff2 │ │ ├── KaTeX_Caligraphic-Bold.woff │ │ ├── KaTeX_Caligraphic-Bold.woff2 │ │ ├── KaTeX_Caligraphic-Regular.ttf │ │ ├── KaTeX_Fraktur-Regular.woff2 │ │ ├── KaTeX_Main-BoldItalic.woff2 │ │ ├── KaTeX_Math-BoldItalic.woff2 │ │ ├── KaTeX_SansSerif-Italic.woff │ │ ├── KaTeX_SansSerif-Italic.woff2 │ │ ├── KaTeX_SansSerif-Regular.ttf │ │ ├── KaTeX_SansSerif-Regular.woff │ │ ├── KaTeX_SansSerif-Regular.woff2 │ │ ├── KaTeX_Typewriter-Regular.ttf │ │ ├── KaTeX_Typewriter-Regular.woff │ │ ├── KaTeX_Caligraphic-Regular.woff │ │ ├── KaTeX_Caligraphic-Regular.woff2 │ │ └── KaTeX_Typewriter-Regular.woff2 └── 404.html ├── .npmrc ├── posts ├── compiler │ ├── SSSS.png │ └── image-20230207231446062.png ├── csapp-cache │ ├── 4-a.png │ ├── 4-b.png │ ├── 4-b-2.png │ ├── 4-b-3.png │ └── 4-b-1-2.png ├── csapp-shell │ ├── 5-1.png │ ├── 5-diff.png │ └── 5-regex.png ├── lc3-bench │ └── main.png ├── to-dashu │ ├── IMG_5460.JPG │ ├── IMG_5462.JPG │ ├── IMG_5463.JPG │ ├── IMG_5464.JPG │ ├── IMG_5466(1).JPG │ ├── image-20230303220344074.png │ ├── IMG_5465(20230303-205342).JPG │ ├── 306CD7D7A3F421477AF61180974AD766.png │ ├── 61560FC8262A81CC83DD7DF13BED2178.png │ └── A1207CEF3BE4995746742E1B9EBC8FDA.png ├── re-deriv │ ├── 20250124dfa.png │ └── 20250124rules.png ├── algorithm-basis │ └── snu23k.png ├── shoushimin │ ├── 20250620-1.png │ ├── 20250620-2.png │ └── 20250620-3.png ├── sort-algorithm │ └── xb5ks5.png ├── serverless-valine │ └── 1269gtz.jpg ├── bangumi_anime_list │ └── my_anime_list.png ├── specialization │ └── naive-pagination.png ├── digital-lab │ ├── image-20220319212215367.png │ ├── image-20220319212315831.png │ ├── image-20220319212513260.png │ ├── image-20220319212941961.png │ ├── image-20220319213207916.png │ ├── image-20220319213412728.png │ ├── image-20220319214751930.png │ ├── image-20220319215151766.png │ └── image-20220319220158821.png ├── hackergame │ ├── image-20221022184001281.png │ ├── image-20221022184410940.png │ ├── image-20221022184813334.png │ ├── image-20221022185020708.png │ ├── image-20221025112042609.png │ ├── image-20221025112108412.png │ ├── image-20221025112224020.png │ ├── image-20221025113409348.png │ ├── image-20221025114139041.png │ ├── image-20221025114330813.png │ ├── image-20221025114429200.png │ ├── image-20221025115311892.png │ ├── image-20221025135700030.png │ ├── image-20221025161113238.png │ ├── image-20221025161205519.png │ ├── image-20221027180129861.png │ ├── image-20221027181714529.png │ ├── image-20221028222814768.png │ ├── image-20221029114614543.png │ ├── image-20221029114804447.png │ ├── image-20221029115146949.png │ ├── image-20221029115220748.png │ ├── image-20221029115306713.png │ └── image-20221029180452894.png ├── vscode-ascendc-clangd │ ├── docs-host-clangd.png │ └── docs-device-clangd.png ├── atypical-tour-to-peking │ ├── 20250224-railway.png │ ├── 20250224-sasaki.jpg │ └── 20250224-summer-palace.jpeg ├── formal-languages-and-complexity-notes │ └── 20250307-nfa.png ├── hello-world.md ├── math-anal-intregion.md ├── ssh-tunnel.md ├── to-dashu.md ├── hexo-night-git.md ├── shoushimin.md ├── senior-year.md ├── static-page-aes.md ├── atypical-tour-to-peking.md ├── top-and-bottom-type.md ├── digital-lab.md ├── github-actions-ci.md ├── bangumi_anime_list.md ├── re-deriv.md ├── sudoko-astar.md ├── pwa.md ├── set-union.md ├── specialization.md ├── lc3-bench.md ├── csapp-shell.md ├── sudoko-iddfs.md ├── lifemanual.md └── vue3-fastapi.md ├── src ├── utils │ ├── math.ts │ ├── array.ts │ └── date.ts ├── pages │ ├── archive.vue │ ├── [...notFound].md │ ├── tags │ │ ├── [tag].vue │ │ └── index.vue │ ├── about.md │ ├── links.vue │ ├── Index.vue │ ├── posts │ │ └── [post].vue │ └── bangumi.vue ├── modules │ ├── nprogress.ts │ └── router.ts ├── components │ ├── Sidebar.vue │ ├── PostHeader.vue │ ├── PostSearch.vue │ ├── PostFooter.vue │ ├── CommonFooter.vue │ ├── ArchiveCard.vue │ ├── APlayer.vue │ ├── Pagination.vue │ ├── Header.vue │ ├── Comment.vue │ ├── Toc.vue │ └── ControlPanel.vue ├── main.ts ├── composables │ ├── useSiteNavigation.ts │ ├── useDarks.ts │ ├── usePage.ts │ ├── useSummary.ts │ ├── useMobileSidebar.ts │ ├── useToc.ts │ ├── usePostData.ts │ ├── useCustomScroll.ts │ └── useBangumi.ts ├── types │ └── env.d.ts ├── styles │ ├── aplayer-dark.css │ ├── main.css │ └── md.css └── App.vue ├── .gitignore ├── eslint.config.js ├── partial-evaluate ├── log.ts ├── config.ts ├── utils.ts ├── index.ts ├── vueComponent.ts ├── README.md └── replacement.ts ├── tsconfig.json ├── windi.config.ts ├── LICENSE.md ├── README.md ├── index.html ├── .autocorrectrc ├── package.json └── vite.config.ts /public/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=workbox-window -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | -------------------------------------------------------------------------------- /public/_headers: -------------------------------------------------------------------------------- 1 | https://q-blog.pages.dev/* 2 | X-Robots-Tag: noindex 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /posts/compiler/SSSS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/compiler/SSSS.png -------------------------------------------------------------------------------- /public/avatar_192.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/avatar_192.webp -------------------------------------------------------------------------------- /public/icp.gov.moe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/icp.gov.moe.png -------------------------------------------------------------------------------- /public/pwa-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/pwa-192x192.png -------------------------------------------------------------------------------- /public/pwa-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/pwa-512x512.png -------------------------------------------------------------------------------- /posts/csapp-cache/4-a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/csapp-cache/4-a.png -------------------------------------------------------------------------------- /posts/csapp-cache/4-b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/csapp-cache/4-b.png -------------------------------------------------------------------------------- /posts/csapp-shell/5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/csapp-shell/5-1.png -------------------------------------------------------------------------------- /posts/lc3-bench/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/lc3-bench/main.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /posts/csapp-cache/4-b-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/csapp-cache/4-b-2.png -------------------------------------------------------------------------------- /posts/csapp-cache/4-b-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/csapp-cache/4-b-3.png -------------------------------------------------------------------------------- /posts/csapp-shell/5-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/csapp-shell/5-diff.png -------------------------------------------------------------------------------- /posts/to-dashu/IMG_5460.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/to-dashu/IMG_5460.JPG -------------------------------------------------------------------------------- /posts/to-dashu/IMG_5462.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/to-dashu/IMG_5462.JPG -------------------------------------------------------------------------------- /posts/to-dashu/IMG_5463.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/to-dashu/IMG_5463.JPG -------------------------------------------------------------------------------- /posts/to-dashu/IMG_5464.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/to-dashu/IMG_5464.JPG -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/apple-touch-icon.png -------------------------------------------------------------------------------- /posts/csapp-cache/4-b-1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/csapp-cache/4-b-1-2.png -------------------------------------------------------------------------------- /posts/csapp-shell/5-regex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/csapp-shell/5-regex.png -------------------------------------------------------------------------------- /posts/re-deriv/20250124dfa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/re-deriv/20250124dfa.png -------------------------------------------------------------------------------- /posts/to-dashu/IMG_5466(1).JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/to-dashu/IMG_5466(1).JPG -------------------------------------------------------------------------------- /posts/algorithm-basis/snu23k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/algorithm-basis/snu23k.png -------------------------------------------------------------------------------- /posts/re-deriv/20250124rules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/re-deriv/20250124rules.png -------------------------------------------------------------------------------- /posts/shoushimin/20250620-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/shoushimin/20250620-1.png -------------------------------------------------------------------------------- /posts/shoushimin/20250620-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/shoushimin/20250620-2.png -------------------------------------------------------------------------------- /posts/shoushimin/20250620-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/shoushimin/20250620-3.png -------------------------------------------------------------------------------- /posts/sort-algorithm/xb5ks5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/sort-algorithm/xb5ks5.png -------------------------------------------------------------------------------- /posts/serverless-valine/1269gtz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/serverless-valine/1269gtz.jpg -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Main-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Main-Bold.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_AMS-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_AMS-Regular.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Main-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Main-Bold.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Main-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Main-Bold.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Main-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Main-Italic.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Math-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Math-Italic.ttf -------------------------------------------------------------------------------- /posts/bangumi_anime_list/my_anime_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/bangumi_anime_list/my_anime_list.png -------------------------------------------------------------------------------- /posts/compiler/image-20230207231446062.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/compiler/image-20230207231446062.png -------------------------------------------------------------------------------- /posts/specialization/naive-pagination.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/specialization/naive-pagination.png -------------------------------------------------------------------------------- /posts/to-dashu/image-20230303220344074.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/to-dashu/image-20230303220344074.png -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_AMS-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_AMS-Regular.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_AMS-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_AMS-Regular.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Fraktur-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Fraktur-Bold.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Fraktur-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Fraktur-Bold.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Fraktur-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Fraktur-Bold.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Main-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Main-Italic.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Main-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Main-Italic.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Main-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Main-Regular.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Main-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Main-Regular.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Main-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Main-Regular.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Math-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Math-Italic.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Math-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Math-Italic.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_SansSerif-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_SansSerif-Bold.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Script-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Script-Regular.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Size1-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Size1-Regular.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Size1-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Size1-Regular.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Size2-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Size2-Regular.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Size2-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Size2-Regular.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Size3-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Size3-Regular.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Size3-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Size3-Regular.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Size4-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Size4-Regular.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Size4-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Size4-Regular.woff -------------------------------------------------------------------------------- /posts/digital-lab/image-20220319212215367.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/digital-lab/image-20220319212215367.png -------------------------------------------------------------------------------- /posts/digital-lab/image-20220319212315831.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/digital-lab/image-20220319212315831.png -------------------------------------------------------------------------------- /posts/digital-lab/image-20220319212513260.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/digital-lab/image-20220319212513260.png -------------------------------------------------------------------------------- /posts/digital-lab/image-20220319212941961.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/digital-lab/image-20220319212941961.png -------------------------------------------------------------------------------- /posts/digital-lab/image-20220319213207916.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/digital-lab/image-20220319213207916.png -------------------------------------------------------------------------------- /posts/digital-lab/image-20220319213412728.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/digital-lab/image-20220319213412728.png -------------------------------------------------------------------------------- /posts/digital-lab/image-20220319214751930.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/digital-lab/image-20220319214751930.png -------------------------------------------------------------------------------- /posts/digital-lab/image-20220319215151766.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/digital-lab/image-20220319215151766.png -------------------------------------------------------------------------------- /posts/digital-lab/image-20220319220158821.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/digital-lab/image-20220319220158821.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221022184001281.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221022184001281.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221022184410940.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221022184410940.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221022184813334.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221022184813334.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221022185020708.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221022185020708.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221025112042609.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221025112042609.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221025112108412.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221025112108412.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221025112224020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221025112224020.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221025113409348.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221025113409348.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221025114139041.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221025114139041.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221025114330813.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221025114330813.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221025114429200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221025114429200.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221025115311892.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221025115311892.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221025135700030.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221025135700030.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221025161113238.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221025161113238.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221025161205519.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221025161205519.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221027180129861.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221027180129861.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221027181714529.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221027181714529.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221028222814768.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221028222814768.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221029114614543.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221029114614543.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221029114804447.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221029114804447.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221029115146949.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221029115146949.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221029115220748.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221029115220748.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221029115306713.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221029115306713.png -------------------------------------------------------------------------------- /posts/hackergame/image-20221029180452894.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/hackergame/image-20221029180452894.png -------------------------------------------------------------------------------- /posts/to-dashu/IMG_5465(20230303-205342).JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/to-dashu/IMG_5465(20230303-205342).JPG -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Caligraphic-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Caligraphic-Bold.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Fraktur-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Fraktur-Regular.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Fraktur-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Fraktur-Regular.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Main-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Main-BoldItalic.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Main-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Main-BoldItalic.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Math-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Math-BoldItalic.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Math-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Math-BoldItalic.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_SansSerif-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_SansSerif-Bold.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_SansSerif-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_SansSerif-Bold.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_SansSerif-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_SansSerif-Italic.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Script-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Script-Regular.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Script-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Script-Regular.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Size1-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Size1-Regular.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Size2-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Size2-Regular.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Size3-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Size3-Regular.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Size4-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Size4-Regular.woff2 -------------------------------------------------------------------------------- /posts/vscode-ascendc-clangd/docs-host-clangd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/vscode-ascendc-clangd/docs-host-clangd.png -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Caligraphic-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Caligraphic-Bold.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Caligraphic-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Caligraphic-Bold.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Caligraphic-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Caligraphic-Regular.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Fraktur-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Fraktur-Regular.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Main-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Main-BoldItalic.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Math-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Math-BoldItalic.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_SansSerif-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_SansSerif-Italic.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_SansSerif-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_SansSerif-Italic.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_SansSerif-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_SansSerif-Regular.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_SansSerif-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_SansSerif-Regular.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_SansSerif-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_SansSerif-Regular.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Typewriter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Typewriter-Regular.ttf -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Typewriter-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Typewriter-Regular.woff -------------------------------------------------------------------------------- /posts/atypical-tour-to-peking/20250224-railway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/atypical-tour-to-peking/20250224-railway.png -------------------------------------------------------------------------------- /posts/atypical-tour-to-peking/20250224-sasaki.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/atypical-tour-to-peking/20250224-sasaki.jpg -------------------------------------------------------------------------------- /posts/vscode-ascendc-clangd/docs-device-clangd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/vscode-ascendc-clangd/docs-device-clangd.png -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Caligraphic-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Caligraphic-Regular.woff -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Caligraphic-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Caligraphic-Regular.woff2 -------------------------------------------------------------------------------- /public/katex/fonts/KaTeX_Typewriter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/public/katex/fonts/KaTeX_Typewriter-Regular.woff2 -------------------------------------------------------------------------------- /src/utils/math.ts: -------------------------------------------------------------------------------- 1 | export function getRandInt(min: number, max: number): number { 2 | return min + Math.round(Math.random() * (max - min)) 3 | } 4 | -------------------------------------------------------------------------------- /posts/to-dashu/306CD7D7A3F421477AF61180974AD766.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/to-dashu/306CD7D7A3F421477AF61180974AD766.png -------------------------------------------------------------------------------- /posts/to-dashu/61560FC8262A81CC83DD7DF13BED2178.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/to-dashu/61560FC8262A81CC83DD7DF13BED2178.png -------------------------------------------------------------------------------- /posts/to-dashu/A1207CEF3BE4995746742E1B9EBC8FDA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/to-dashu/A1207CEF3BE4995746742E1B9EBC8FDA.png -------------------------------------------------------------------------------- /posts/atypical-tour-to-peking/20250224-summer-palace.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/atypical-tour-to-peking/20250224-summer-palace.jpeg -------------------------------------------------------------------------------- /posts/formal-languages-and-complexity-notes/20250307-nfa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuly0322/Q-Blog/HEAD/posts/formal-languages-and-complexity-notes/20250307-nfa.png -------------------------------------------------------------------------------- /src/pages/archive.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /src/pages/[...notFound].md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # 404 4 | 5 | 你来到了一个 404 页喵~ 6 | 7 | 咱喵欢迎你点击下面的按钮继续浏览喵~ 8 | 9 | 返回首页 10 | 11 |
12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /node_modules 3 | /dist 4 | /stats.html 5 | /public/posts 6 | /public/images 7 | /public/page.json 8 | /public/feed.xml 9 | /src/jsons/summary.json 10 | /src/types/auto-imports.d.ts 11 | /src/types/components.d.ts 12 | package-lock.json 13 | -------------------------------------------------------------------------------- /src/modules/nprogress.ts: -------------------------------------------------------------------------------- 1 | import NProgress from 'nprogress' 2 | import { router } from './router' 3 | 4 | export default () => { 5 | router.beforeEach(() => { 6 | NProgress.start() 7 | }) 8 | router.afterEach(() => { 9 | NProgress.done() 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | 3 | export default antfu({ 4 | rules: { 5 | 'unused-imports/no-unused-vars': ['error', { caughtErrors: 'none' }], 6 | 'ts/no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true }], 7 | }, 8 | ignores: ['posts/*.md'], 9 | }) 10 | -------------------------------------------------------------------------------- /src/components/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | -------------------------------------------------------------------------------- /partial-evaluate/log.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | export default (() => { 3 | const logs: string[] = [] 4 | return { 5 | log: (message: string) => logs.push(message), 6 | getLogs: () => logs, 7 | print: () => { 8 | console.log(logs.join('\n')) 9 | }, 10 | clear: () => logs.length = 0, 11 | } 12 | })() 13 | -------------------------------------------------------------------------------- /src/pages/tags/[tag].vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /src/utils/array.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Count occurrences of each element in an array 3 | * @param arr Array of elements to count 4 | * @returns Map of element to count 5 | */ 6 | export function counter(arr: Array): Map { 7 | return arr.reduce( 8 | (acc: Map, e: T) => acc.set(e, (acc.get(e) || 0) + 1), 9 | new Map(), 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import App from './App.vue' 2 | import nprogress from './modules/nprogress' 3 | import router from './modules/router' 4 | 5 | import 'virtual:windi-base.css' 6 | import 'virtual:windi-components.css' 7 | 8 | import './styles/main.css' 9 | import 'virtual:windi-utilities.css' 10 | import 'virtual:windi-devtools' 11 | 12 | const app = createApp(App) 13 | app.use(router) 14 | app.use(nprogress) 15 | app.mount('#app') 16 | -------------------------------------------------------------------------------- /src/composables/useSiteNavigation.ts: -------------------------------------------------------------------------------- 1 | const navOptions = [ 2 | { 3 | label: '主页', 4 | to: '/', 5 | match: '/$', 6 | }, 7 | { 8 | label: '友链', 9 | to: '/links', 10 | match: '/links$', 11 | }, 12 | { 13 | label: '动画', 14 | to: '/bangumi', 15 | match: '/bangumi$', 16 | }, 17 | { 18 | label: '关于', 19 | to: '/about', 20 | match: '/about$', 21 | }, 22 | ] 23 | 24 | export default () => ({ navOptions }) 25 | -------------------------------------------------------------------------------- /src/modules/router.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import routes from 'virtual:generated-pages' 3 | import { createRouter, createWebHistory } from 'vue-router' 4 | 5 | const { saveScrollPosition, customScrollBehavior } = useCustomScroll() 6 | 7 | export const router = createRouter({ 8 | routes, 9 | history: createWebHistory(), 10 | scrollBehavior: customScrollBehavior, 11 | }) 12 | router.beforeEach(saveScrollPosition) 13 | 14 | export default (app: App) => app.use(router) 15 | -------------------------------------------------------------------------------- /src/utils/date.ts: -------------------------------------------------------------------------------- 1 | export function getCurrentYear() { 2 | return new Date().getFullYear() 3 | } 4 | 5 | export function getCurrentSeason() { 6 | const month = new Date().getMonth() 7 | if (month >= 2 && month <= 4) 8 | return '春' 9 | if (month >= 5 && month <= 7) 10 | return '夏' 11 | if (month >= 8 && month <= 10) 12 | return '秋' 13 | return '冬' 14 | } 15 | 16 | export function formatDate(date: string) { 17 | const d = new Date(date) 18 | return `${d.getFullYear()}.${d.getMonth() + 1}.${d.getDate()}` 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 关于 3 | --- 4 | 5 |
6 | 7 | avatar 8 | 9 | ## 关于我 10 | 11 | 爱好 System/PL/Security 及其他任何 CS 相关的有趣知识~ 12 | 13 | 我的 Anime List。 14 | 15 | ## 联系方式 16 | 17 | - 主页:[liuly.moe](https://liuly.moe) 18 | - GitHub:[liuly0322](https://github.com/liuly0322) 19 | - Telegram 频道:[liuly_home](https://t.me/liuly_home) 20 | - 邮箱: 21 | - me AT liuly.moe (general) 22 | - lly AT mail.ustc.edu.cn (academic) 23 | 24 |
25 | -------------------------------------------------------------------------------- /src/composables/useDarks.ts: -------------------------------------------------------------------------------- 1 | // skeletonDark: https://github.com/tusen-ai/naive-ui/issues/5777 2 | import { autoCompleteDark, createTheme, dividerDark, rateDark, tagDark } from 'naive-ui' 3 | import { skeletonDark } from 'naive-ui/es/skeleton/styles' 4 | 5 | const isDark = useDark() 6 | const toggleDark = useToggle(isDark) 7 | const darkTheme = createTheme([autoCompleteDark, dividerDark, rateDark, tagDark, skeletonDark]) 8 | const darkOverrides = { 9 | Layout: { 10 | color: 'rgb(24, 24, 28)', 11 | }, 12 | } 13 | 14 | export default () => ({ isDark, toggleDark, darkTheme, darkOverrides }) 15 | -------------------------------------------------------------------------------- /src/composables/usePage.ts: -------------------------------------------------------------------------------- 1 | const { data }: { data: Ref } = useFetch('/page.json').json() 2 | 3 | const { summary, firstPageAbstracts } = useSummary() 4 | const page = ref(1) 5 | const pageMax = Math.ceil(summary.length / 10) 6 | 7 | const postsOnPage = computed(() => { 8 | const currSummary = data.value ?? firstPageAbstracts 9 | return currSummary 10 | .map((detail: string, i: number) => ({ 11 | detail, 12 | summary: summary[i], 13 | })) 14 | .slice((page.value - 1) * 10, page.value * 10) 15 | }) 16 | 17 | export default () => ({ data, page, pageMax, postsOnPage }) 18 | -------------------------------------------------------------------------------- /partial-evaluate/config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { vueComponentMJSEvaluate } from './vueComponent' 3 | 4 | export interface Config { 5 | silent?: boolean 6 | components: { 7 | [componentName: string]: Record 8 | } 9 | } 10 | 11 | export function getTransformers(id: string, userConfig: Config) { 12 | const transformers = [] 13 | if (id.endsWith('.mjs')) { 14 | const componentName = path.basename(id).replace(/\.mjs$/, '') 15 | if (userConfig.components[componentName]) 16 | transformers.push(vueComponentMJSEvaluate) 17 | } 18 | return transformers 19 | } 20 | -------------------------------------------------------------------------------- /src/composables/useSummary.ts: -------------------------------------------------------------------------------- 1 | import data from '~/jsons/summary.json' 2 | import { counter } from '~/utils/array' 3 | 4 | export interface PostSummary { 5 | title: string 6 | date: string 7 | tags: string[] 8 | url: string 9 | } 10 | 11 | const summary: PostSummary[] = data.posts 12 | const firstPageAbstracts = data.firstPageAbstracts 13 | 14 | const tags = summary.map(post => post.tags).flat() 15 | const tagCount = [...counter(tags).entries()] 16 | .sort((tag_a, tag_b) => tag_b[1] - tag_a[1]) 17 | .map(([s, n]) => ({ content: s, times: n })) 18 | 19 | export default () => ({ summary, firstPageAbstracts, tagCount }) 20 | -------------------------------------------------------------------------------- /posts/hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hello New World 3 | date: 2022-03-28 13:50:20 4 | tags: [vue, typescript] 5 | category: web 6 | --- 7 | 8 | 博客搬迁至自建 Vue3 框架 9 | 10 | ## feature 11 | 12 | - 暗黑模式支持 13 | - 全局搜索(标题支持) 14 | - 单页面应用带来的良好路由体验(指可以一直听歌) 15 | - RSS 支持 16 | - Markdown 部分 17 | - mathjax 支持 18 | - 锚点支持 19 | - 高亮支持 20 | 21 | 22 | 23 | ## to be done 24 | 25 | - 未支持 mermaid 26 | - 文章增添目录 27 | - 目前不是 SSG,因此对 SEO 不太友好 28 | 29 | ## 技术支持 30 | 31 | - GitHub Pages 32 | - Cloudflare(可能快了一点吧(大概)) 33 | - Vue3 34 | - Vite2 35 | - Naive-ui(大部分组件来源) 36 | - Aplayer 37 | - Hugo Kagome(部分主题参考) 38 | - QuAn\_(~~头像支持~~) 39 | - ... 40 | -------------------------------------------------------------------------------- /src/components/PostHeader.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | -------------------------------------------------------------------------------- /src/types/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | 6 | declare module 'frontmatter' { 7 | export default function frontmatter(content: string): { 8 | data: Record 9 | content: string 10 | } 11 | } 12 | 13 | declare module 'markdown-it-texmath' { 14 | import type { PluginWithOptions } from 'markdown-it' 15 | 16 | const plugin: PluginWithOptions 17 | export default plugin 18 | } 19 | 20 | declare module 'vite-plugin-vsharp' { 21 | export default function vsharp(config: any): any 22 | } 23 | -------------------------------------------------------------------------------- /partial-evaluate/utils.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import os from 'node:os' 3 | import path from 'node:path' 4 | 5 | const tempDir = os.tmpdir() 6 | 7 | export default function saveFiles(id: string, original: string, transformed: string) { 8 | const filename = id.split('/').pop()! 9 | const hash = Math.random().toString(36).slice(2, 8) 10 | const originalPath = path.join(tempDir, `${filename}-${hash}-original.js`) 11 | const transformedPath = path.join(tempDir, `${filename}-${hash}-transformed.js`) 12 | fs.writeFileSync(originalPath, original) 13 | fs.writeFileSync(transformedPath, transformed) 14 | return `Original: ${originalPath}\nTransformed: ${transformedPath}` 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/links.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | -------------------------------------------------------------------------------- /src/styles/aplayer-dark.css: -------------------------------------------------------------------------------- 1 | html.dark .aplayer { 2 | background: rgb(24, 24, 28); 3 | } 4 | 5 | html.dark .aplayer .aplayer-list ol li { 6 | border-top: 1px solid #161616; 7 | } 8 | 9 | html.dark .aplayer .aplayer-list ol li.aplayer-list-light { 10 | background: #161616; 11 | } 12 | 13 | html.dark .aplayer.aplayer-withlist .aplayer-info { 14 | border-bottom: 1px solid #161616; 15 | } 16 | 17 | html.dark .aplayer .aplayer-lrc::before, 18 | html.dark .aplayer .aplayer-lrc::after { 19 | background: linear-gradient(180deg, 20 | hsla(240, 7.7%, 10.2%, 0) 0, 21 | hsla(240, 7.7%, 10.2%, 0.8)); 22 | } 23 | 24 | html.dark .aplayer .aplayer-list ol li:hover { 25 | background: #101010; 26 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "jsx": "preserve", 5 | "lib": ["esnext", "dom"], 6 | "useDefineForClassFields": true, 7 | "baseUrl": ".", 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "paths": { 11 | "~/*": ["src/*"] 12 | }, 13 | "resolveJsonModule": true, 14 | "strict": true, 15 | "sourceMap": true, 16 | "esModuleInterop": true, 17 | "isolatedModules": true, 18 | "skipLibCheck": true 19 | }, 20 | "include": [ 21 | "build", 22 | "src/**/*.ts", 23 | "src/**/*.d.ts", 24 | "src/**/*.vue", 25 | "./vite.config.ts" 26 | ], 27 | "exclude": ["node_modules", "dist"] 28 | } 29 | -------------------------------------------------------------------------------- /src/components/PostSearch.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 31 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | llyのblog 6 | 7 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/components/PostFooter.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 29 | -------------------------------------------------------------------------------- /src/pages/tags/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 28 | -------------------------------------------------------------------------------- /src/components/CommonFooter.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 22 | -------------------------------------------------------------------------------- /src/composables/useMobileSidebar.ts: -------------------------------------------------------------------------------- 1 | // Cache DOM elements to avoid repeated queries 2 | let mduiElement: Element | null = null 3 | let sidebarElement: Element | null = null 4 | 5 | function getMduiElement() { 6 | if (!mduiElement) 7 | mduiElement = document.querySelector('.mdui-overlay') 8 | return mduiElement 9 | } 10 | 11 | function getSidebarElement() { 12 | if (!sidebarElement) 13 | sidebarElement = document.querySelector('#sidebar') 14 | return sidebarElement 15 | } 16 | 17 | function openSidebar() { 18 | getMduiElement()?.classList.add('mdui-overlay-show') 19 | getSidebarElement()?.classList.add('sidebar-open') 20 | } 21 | 22 | function closeSidebar() { 23 | getMduiElement()?.classList.remove('mdui-overlay-show') 24 | getSidebarElement()?.classList.remove('sidebar-open') 25 | } 26 | 27 | function toggleSidebar() { 28 | const currNavOpen = getSidebarElement()?.classList.contains('sidebar-open') 29 | currNavOpen ? closeSidebar() : openSidebar() 30 | } 31 | 32 | export default () => ({ openSidebar, closeSidebar, toggleSidebar }) 33 | -------------------------------------------------------------------------------- /src/components/ArchiveCard.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 35 | -------------------------------------------------------------------------------- /src/composables/useToc.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/txtxj/C-Blog/blob/master/src/composables/useToc.ts 2 | // MIT License 3 | 4 | interface TocElement { 5 | id: string 6 | text: string 7 | tab: number 8 | } 9 | 10 | const tocElements: Ref = ref([]) 11 | 12 | const enableToc = ref(false) 13 | 14 | function setToc(element: HTMLElement) { 15 | const titleElements: HTMLElement[] = Array.from(element.querySelectorAll('h2,h3,h4')) 16 | enableToc.value = titleElements.length > 0 17 | enableToc.value && updateToc(titleElements) 18 | } 19 | 20 | function updateToc(titleElements: HTMLElement[]) { 21 | tocElements.value = [] 22 | let minTag = '9' 23 | for (const e of titleElements) { 24 | if (minTag > e.tagName[1]) 25 | minTag = e.tagName[1] 26 | } 27 | for (const e of titleElements) { 28 | tocElements.value.push({ 29 | id: e.id, 30 | text: e.textContent ?? '', 31 | tab: Number.parseInt(e.tagName[1]) - Number.parseInt(minTag) - 1, 32 | }) 33 | } 34 | } 35 | 36 | function getToc() { 37 | return tocElements 38 | } 39 | 40 | export default () => ({ setToc, getToc, enableToc }) 41 | -------------------------------------------------------------------------------- /src/pages/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 31 | -------------------------------------------------------------------------------- /src/components/APlayer.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 41 | -------------------------------------------------------------------------------- /windi.config.ts: -------------------------------------------------------------------------------- 1 | import animationPlugin from '@windicss/plugin-animations' 2 | import { defineConfig } from 'windicss/helpers' 3 | import plugin from 'windicss/plugin' 4 | import lineClampPlugin from 'windicss/plugin/line-clamp' 5 | import Typography from 'windicss/plugin/typography' 6 | 7 | export default defineConfig({ 8 | darkMode: 'class', 9 | attributify: true, 10 | plugins: [ 11 | Typography(), 12 | animationPlugin(), 13 | lineClampPlugin, 14 | plugin(({ addComponents }) => { 15 | const components = { 16 | '.card': { 17 | borderRadius: '0.5rem', 18 | boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', 19 | borderWidth: '0.8px', 20 | borderColor: 'rgba(255, 255, 255, 0.09)', 21 | }, 22 | '.blue-link': { 23 | color: '#258fb8', 24 | }, 25 | '.show-more': { 26 | lineHeight: '1em', 27 | padding: '6px 15px', 28 | borderRadius: '15px', 29 | color: '#fff', 30 | background: '#258fb8', 31 | textShadow: '0 1px #1e7293', 32 | textDecoration: 'none', 33 | }, 34 | } 35 | addComponents(components) 36 | }), 37 | ], 38 | }) 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | ## Blog posts, i.e. posts under `posts/` folder 4 | 5 | CC BY-NC-SA 4.0 6 | 7 | ## Source code of the software 8 | 9 | MIT LICENSE 10 | 11 | Copyright (c) 2022 liuly 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 14 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 15 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 16 | to permit persons to whom the Software is furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 21 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 23 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /src/components/Pagination.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 40 | -------------------------------------------------------------------------------- /src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 35 | -------------------------------------------------------------------------------- /src/styles/main.css: -------------------------------------------------------------------------------- 1 | @import './md.css'; 2 | 3 | html, 4 | body, 5 | #app { 6 | height: 100%; 7 | margin: auto; 8 | padding: 0; 9 | } 10 | 11 | body { 12 | max-width: 1280px; 13 | font-size: 14px; 14 | } 15 | 16 | html.dark { 17 | color: #e5e7eb; 18 | background: rgb(24, 24, 28); 19 | } 20 | 21 | .utterances { 22 | background: white; 23 | } 24 | html.dark .utterances { 25 | background: rgb(24, 24, 28); 26 | } 27 | 28 | #nprogress { 29 | pointer-events: none; 30 | } 31 | 32 | #nprogress .bar { 33 | background: rgba(13, 148, 136, 0.75); 34 | position: fixed; 35 | z-index: 1031; 36 | top: 0; 37 | left: 0; 38 | width: 100%; 39 | height: 2px; 40 | } 41 | 42 | /* katex displayMode */ 43 | .katex-display { 44 | overflow-x: auto; 45 | overflow-y: hidden; 46 | } 47 | 48 | /* (移动端)遮罩层 */ 49 | .mdui-overlay { 50 | position: fixed; 51 | top: 64px; 52 | left: 0; 53 | width: 5000px; 54 | height: 5000px; 55 | z-index: 2; 56 | background: rgba(0, 0, 0, 0.4); 57 | backface-visibility: hidden; 58 | display: none; 59 | opacity: 0; 60 | transition-duration: 0.3s; 61 | transition-property: opacity, visibility; 62 | will-change: opacity; 63 | } 64 | 65 | .mdui-overlay-show { 66 | display: unset; 67 | opacity: 1; 68 | } 69 | 70 | .sidebar-open { 71 | right: 0 !important; 72 | } 73 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 33 | 34 | 41 | -------------------------------------------------------------------------------- /partial-evaluate/index.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from './config' 2 | import _generator from '@babel/generator' 3 | import * as parser from '@babel/parser' 4 | import { getTransformers } from './config' 5 | import logger from './log' 6 | import saveFiles from './utils' 7 | 8 | // @ts-expect-error: https://github.com/babel/babel/issues/15269 9 | const generator = _generator.default as typeof _generator 10 | 11 | export default (config?: Config) => { 12 | const userConfig: Config = config || { 13 | components: {}, 14 | } 15 | if (userConfig.silent === true) 16 | logger.print = () => {} 17 | 18 | return { 19 | name: 'partial-evaluate-optimizer-plugin', 20 | async transform(code: string, id: string) { 21 | const transformers = getTransformers(id, userConfig) 22 | if (transformers.length === 0) 23 | return null 24 | const ast = parser.parse(code, { sourceType: 'module' }) 25 | 26 | for (const transformer of transformers) 27 | transformer(ast, id, userConfig) 28 | 29 | const { code: transformedCode } = generator(ast, { retainLines: true }) 30 | const log = saveFiles(id, code, transformedCode) 31 | logger.log(`${log}\n`) 32 | return code === transformedCode 33 | ? null 34 | : { 35 | code: transformedCode, 36 | map: null, 37 | } 38 | }, 39 | closeBundle() { 40 | logger.print() 41 | logger.clear() 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /posts/math-anal-intregion.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 一个利用积分区域对称性得到的结论 3 | date: 2021-05-07 07:54:09 4 | tags: [数学] 5 | mathjax: true 6 | category: 笔记 7 | --- 8 | 9 | 题目来源于数学分析 B2 的一道作业证明题: 10 | 11 | $$ 12 | \int_{0}^{a}dx_{1}\int_{0}^{x_{1}}dx_{2}...\int_{0}^{x_{n-1}}f(x_{1})f(x_{2})...f(x_{n}) dx_{n}=\frac{1}{n!}(\int_{0}^{a} f(t) dt )^{n} 13 | $$ 14 | 15 | 这是一个关于多重积分的题目,我们可以通过积分区域的对称性来巧妙解决。 16 | 17 | 18 | 19 | 首先,本题积分区域为: 20 | 21 | $$0 \le x_{n} \le x_{n-1} \le ... \le x_{2} \le x_{1} \le a$$ 22 | 23 | 注意到这事实上相当于 $[0,a]$ 上的无序数组 $(x_{1},x_{2},x_{3},...,x_{n})$ 选定顺序,总顺序组数即为 $n$ 的全排列数 $n!$ 组。 24 | 25 | 而任意给定一组顺序,都相当于划分出了 $n$ 维空间中的一个区域,显然,这些区域具备以下性质: 26 | 27 | 1. 除了边界之外,这些区域彼此无交 28 | 2. 这些区域的并是 $[0,a]^{n}$ 29 | 3. 由于对称性, $F=f(x_{1})f(x_{2})...f(x_{n})$ 在每个区域上的积分值相等 30 | 31 | 故: 32 | 33 | $$LHS=\frac{1}{n!}\int_{0}^{a}\mathrm{d}x_{1}\int_{0}^{a}\mathrm{d}x_{2}...\int_{0}^{a}f(x_{1})f(x_{2})...f(x_{n}) \mathrm{d}x_{n}=RHS$$ 34 | 35 | 证毕。 36 | 37 | --- 38 | 39 | 对于并不具有完全对称性的某些题目,也可以引入这个结论解决。 40 | 41 | 例如数学分析 B2 的另一道证明题: 42 | 43 | $$\int_{0}^{a}\mathrm{d}x_{1}\int_{0}^{x_{1}}\mathrm{d}x_{2}...\int_{0}^{x_{n-1}}f(x_{n}) \mathrm{d}x_{n}=\frac{1}{(n-1)!}\int_{0}^{a}f(t)(a-t)^{n-1} \mathrm{d}t$$ 44 | 45 | 本题并不是关于 $n$ 个变量均对称,所以考虑更改积分区域,先对 $x_{i}$ $, 1 \le i \le n-1$ 进行积分。 46 | 47 | 故: 48 | 49 | $$ 50 | \begin{align*} 51 | LHS&=\int_{0}^{a}\mathrm{d}x_{n}\int_{x_{n}}^{a}\mathrm{d}x_{n-1}...\int_{x_{2}}^{a}f(x_{n}) \mathrm{d}x_{1}\\\\&=\frac{1}{(n-1)!}\int_{0}^{a}f(x_{n})(a-x_{n})^{n-1} \mathrm{d}x_{n}=RHS 52 | \end{align*} 53 | $$ 54 | 55 | 证毕。 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

Q Blog

6 | 7 |

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |

22 | 23 |

Q-Blog - Quicker and Cuter

24 | 25 | Welcome to [my personal blog site](https://blog.liuly.moe) ([subscribe](https://blog.liuly.moe/feed.xml)). 26 | 27 | - Vite5, Vue3, TypeScript, Naive UI 28 | - Responsive Web Design, Dark Mode, PWA 29 | - [WIP] **(Have a look!) Rollup [partial evaluate plugin](./partial-evaluate/)** 30 | 31 | ## Usage 32 | 33 | Installation: 34 | 35 | ```bash 36 | npm install -g pnpm # install pnpm first 37 | pnpm i 38 | ``` 39 | 40 | Development: 41 | 42 | ```bash 43 | pnpm dev # run server at `localhost:3000` 44 | ``` 45 | 46 | Build: 47 | 48 | ```bash 49 | pnpm build # compile to `dist` folder 50 | pnpm preview # preview the production build 51 | ``` 52 | 53 | ## Credits 54 | 55 | - [tov-template](https://github.com/dishait/tov-template) 56 | - [Vite PWA](https://github.com/vite-pwa/vite-plugin-pwa) 57 | -------------------------------------------------------------------------------- /src/composables/usePostData.ts: -------------------------------------------------------------------------------- 1 | import type { AsyncComputedOnCancel } from '@vueuse/core' 2 | 3 | function getPostName(post: string) { 4 | const parts = post.split('/') 5 | const lastPart = parts[parts.length - 1] || parts[parts.length - 2] || '' 6 | return lastPart.split('.')[0] 7 | } 8 | 9 | async function getPostData(postName: string, onCancel?: AsyncComputedOnCancel) { 10 | const abortController = new AbortController() 11 | onCancel && onCancel(() => abortController.abort()) 12 | return fetch(`/posts/${postName}.htm`, { signal: abortController.signal }) 13 | .then(res => res.text()) 14 | } 15 | 16 | function getCachedSeconds(postName: string) { 17 | const now = Date.now() 18 | const cachedTime = sessionStorage.getItem(`${postName}__time`) ?? '0' 19 | return (now - Number(cachedTime)) / 1000 20 | } 21 | 22 | async function getCachedPostData(post: string, onCancel?: AsyncComputedOnCancel) { 23 | const postName = getPostName(post) 24 | const cached = sessionStorage.getItem(postName) 25 | const cachedSeconds = getCachedSeconds(postName) 26 | if (cached && cachedSeconds < 3600) 27 | return cached 28 | 29 | const data = await getPostData(postName, onCancel) 30 | 31 | sessionStorage.setItem(postName, data) 32 | sessionStorage.setItem(`${postName}__time`, Date.now().toString()) 33 | return data 34 | } 35 | 36 | const emptySummary = Object.freeze({ url: '', title: '404 Not Found', tags: [], date: '' }) 37 | 38 | const { summary } = useSummary() 39 | function getCurrentPostSummary(post: string) { 40 | return summary.find(post_ => post.includes(post_.url)) ?? emptySummary 41 | } 42 | 43 | export default () => ({ emptySummary, getCachedPostData, getCurrentPostSummary }) 44 | -------------------------------------------------------------------------------- /src/components/Comment.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 54 | -------------------------------------------------------------------------------- /src/components/Toc.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 37 | 38 | 57 | 58 | 68 | -------------------------------------------------------------------------------- /posts/ssh-tunnel.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SSH Tunnel 连接远程服务 3 | date: 2022-09-07 10:29:58 4 | tags: [ssh] 5 | category: web 6 | --- 7 | 8 | 算是一个 ssh 的小 trick 9 | 10 | 有的时候我们在开发过程中是 ssh 连接远程开发服务器完成的,这个时候启动服务,往往就会开一个 11 | 12 | ```shell 13 | ➜ Local: http://localhost:5173/ 14 | ➜ Network: http://***.**.***.0:5173/ 15 | ``` 16 | 17 | 之类的服务在服务器上 18 | 19 | 如果服务器有公网 IP,那自然可以通过公网 IP 来访问,当没有公网 IP 我们又需要在本地预览效果时呢? 20 | 21 | 其实也可以通过 ssh 完成 22 | 23 | 24 | 25 | 简单来说,ssh 绑定本地的某个端口到远程服务器的服务端口即可,这样本地即可访问远程服务器提供的服务 26 | 27 | 以上需求对应: 28 | 29 | ```shell 30 | ssh -i ~/.../密钥文件.pem -N -L 本地port:127.0.0.1:服务器port root@服务器url或IP 31 | ``` 32 | 33 | 其中 `127.0.0.1` 也即为本地的 localhost 地址,这样我们就可以通过本地的 `localhost:port` 或者 `127.0.0.1:port` 来访问远程服务 34 | 35 | ## 注意 36 | 37 | 需要注意的是,远程服务需要在 0.0.0.0 上启动,该 IP 可用于外部网络接口 38 | 39 | 例如,对于 `Vite` 而言: 40 | 41 | ```shell 42 | pnpm dev --host 0.0.0.0 43 | ``` 44 | 45 | 添加 `--host` 参数以指定 46 | 47 | 这样一来本地网页就可以看到预览了 48 | 49 | ## 用途 50 | 51 | 多端都可以预览,例如安卓可以使用 `termux` 来使用 `ssh`,这下有个安卓板子就可以开发了 52 | 53 | - VSCode 有网页版好文明 54 | - ~~Electron 是坏文明~~ 55 | - ~~苹果板子应该也可以,但我没有~~ 56 | 57 | ## SSH 其他用途 58 | 59 | 还可以用来做 `proxy` 或者使用跳板机 60 | 61 | 直接抄 `tldr` 了 62 | 63 | ```shell 64 | # 代理 65 | - SSH tunneling: Dynamic port forwarding (SOCKS proxy on `localhost:1080`): 66 | ssh -D {{1080}} {{username}}@{{remote_host}} 67 | 68 | # 端口转发 69 | - SSH tunneling: Forward a specific port (`localhost:9999` to `example.org:80`) along with disabling pseudo-[T]ty allocation and executio[N] of remote commands: 70 | ssh -L {{9999}}:{{example.org}}:{{80}} -N -T {{username}}@{{remote_host}} 71 | 72 | # 使用跳板机 73 | - SSH jumping: Connect through a jumphost to a remote server (Multiple jump hops may be specified separated by comma characters): 74 | ssh -J {{username}}@{{jump_host}} {{username}}@{{remote_host}} 75 | 76 | # SSH Key 自动继承 77 | - Agent forwarding: Forward the authentication information to the remote machine (see `man ssh_config` for available options): 78 | ssh -A {{username}}@{{remote_host}} 79 | ``` -------------------------------------------------------------------------------- /src/pages/posts/[post].vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 61 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | llyのblog 17 | 31 | 39 | 40 | 41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/ControlPanel.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 54 | -------------------------------------------------------------------------------- /src/composables/useCustomScroll.ts: -------------------------------------------------------------------------------- 1 | import type { RouteLocationNormalized, RouteLocationNormalizedLoaded } from 'vue-router' 2 | 3 | const { closeSidebar } = useMobileSidebar() 4 | 5 | interface scrollPosition { 6 | left: number 7 | top: number 8 | } 9 | 10 | function scroll(position: scrollPosition) { 11 | window.scrollTo(position) 12 | } 13 | 14 | const { page } = usePage() 15 | watch(page, () => { 16 | scroll({ left: 0, top: 0 }) 17 | }) 18 | 19 | let deferedScrollPosition: scrollPosition = { left: 0, top: 0 } 20 | 21 | function deferScroll() { 22 | scroll(deferedScrollPosition) 23 | deferedScrollPosition = { left: 0, top: 0 } 24 | } 25 | 26 | function setDeferScroll(path: string, position: scrollPosition): boolean { 27 | if (path.startsWith('/posts/')) { 28 | deferedScrollPosition = position 29 | return true 30 | } 31 | return false 32 | } 33 | 34 | function resetIndexPageNumber(path: string, isSavedPosition: boolean) { 35 | if (path === '/' && !isSavedPosition) 36 | page.value = 1 37 | } 38 | 39 | function customScrollBehavior(to: RouteLocationNormalized, from: RouteLocationNormalizedLoaded, savedPosition: scrollPosition | null): false { 40 | const isSavedPosition = savedPosition !== null 41 | const position = getSavedScrollPosition(to.path, isSavedPosition) 42 | resetIndexPageNumber(to.path, isSavedPosition) 43 | setDeferScroll(to.path, position) || scroll(position) || closeSidebar() 44 | return false 45 | } 46 | 47 | function getScrollPosition(): scrollPosition { 48 | return { left: window.scrollX, top: window.scrollY } 49 | } 50 | 51 | function saveScrollPosition(to: RouteLocationNormalized, from: RouteLocationNormalizedLoaded) { 52 | const position = getScrollPosition() 53 | sessionStorage.setItem(from.path, JSON.stringify(position)) 54 | } 55 | 56 | function getSavedScrollPosition(path: string, enable: boolean): scrollPosition { 57 | if (!enable) 58 | return { left: 0, top: 0 } 59 | const position = sessionStorage.getItem(path) 60 | return position ? JSON.parse(position) : { left: 0, top: 0 } 61 | } 62 | 63 | export default () => ({ saveScrollPosition, customScrollBehavior, scroll, deferScroll }) 64 | -------------------------------------------------------------------------------- /posts/to-dashu.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 大蜀山游记「偷得浮生半日闲」 3 | category: 日志 4 | date: 2023-03-03 21:47:31 5 | tags: [杂谈] 6 | --- 7 | 8 | 大学前我一向是有些讨厌写游记的,现在想来那时写的游记往往都是「作业性质」,被迫应付每周的作文任务。往往这类游记还得编出一个中心思想,例如工业游需要编一个「以后好好学习报效祖国」,文化游要编一个「从古人身上,我领悟到了什么精神」,诸如此类。那时的旅游常常也不是完全出于自己的意愿:虽然不能说是被迫的,但总归还是被家长,学校安排的,我个人还是比较喜欢宅在家(~~从小就开始宅了~~)。 9 | 10 | 上了大学后,自己的自由时间一下子多了起来。正值早春时节,加上基本考完了延期到本学期初的期末考试(虽然还有一门,但是在两天之后),于是临时给自己拟定了一个「爬大蜀山」的计划,并以这篇「不需要中心思想的游记」简单记录我的忙里偷闲。 11 | 12 | 13 | 14 | ## 准备 15 | 16 | 去大蜀山是在 3 月 2 日晚临时拟定的。此时匿名群里已经有很多同学都考完了期末考试,而正值新冠疫情后放开的第一个春天,能看到很多人分享自己去外地的旅行。我也有点按捺不住心中的思绪,临时起意一个人去爬山。 17 | 18 | ![61560FC8262A81CC83DD7DF13BED2178](to-dashu/61560FC8262A81CC83DD7DF13BED2178.png) 19 | 20 | *图:《向山进发》,好看捏* 21 | 22 | 大蜀山离科大很近,于是也不需要做什么特殊的准备。3 月 3 日早起考算法之前去罗森买了个饭团,就当是中午在山上吃的便当了。 23 | 24 | ## 向山进发 25 | 26 | 3 月 3 日 10:30 考完算法,和同学简单对了对答案后就踏上了旅途。步行上路,就当是远足了。 27 | 28 | ![image-20230303220344074](to-dashu/image-20230303220344074.png) 29 | 30 | *图:谷歌导航给出的预估步行时间* 31 | 32 | 天气比较好,在金大地 1912 街区就可以远远望见大蜀山了。之前只在晚上来过这里,倒是没有发现。 33 | 34 | ![A1207CEF3BE4995746742E1B9EBC8FDA](to-dashu/A1207CEF3BE4995746742E1B9EBC8FDA.png) 35 | 36 | *图:路过公交站台的广告,联想到 CS 的就业形势,令人感叹* 37 | 38 | 大蜀山脚下有一片小湖。作为合肥人,之前也来过几次大蜀山了,但都是直接乘车前往大蜀山森林公园景区入口,每每错过了这一景色。 39 | 40 | ![IMG_5460](to-dashu/IMG_5460.JPG) 41 | 42 | *图:大蜀山脚下的小湖,波光粼粼* 43 | 44 | 之后就到了大蜀山烈士陵园。去大蜀山入口的分岔路并没有路牌指引。不过跟着感觉走就可以了。中间路过一个林业学校,不知道里面的学生会不会天天爬大蜀山。 45 | 46 | ## 登山 47 | 48 | 正是梅花盛开的时节。环山公路两旁的梅花很是惹眼。 49 | 50 | ![IMG_5462](to-dashu/IMG_5462.JPG) 51 | 52 | ![IMG_5463](to-dashu/IMG_5463.JPG) 53 | 54 | ![IMG_5464](to-dashu/IMG_5464.JPG) 55 | 56 | 这次上山过程我试着走了一些小路。小心翼翼地寻找下一个石子做为落脚点,并注意踩着松软的泥土会打滑。说起来我之前是有些恐高的,但不知是不是因为在学校体育课上体验了两次攀岩,现在好像没这么害怕了(~~已经没什么好怕的了~~)。 57 | 58 | 行至半程时吃了午饭(~~虽然我还是一个人~~)。 59 | 60 | ![IMG_5465(20230303-205342)](to-dashu/IMG_5465(20230303-205342).JPG) 61 | 62 | 在山上吃饭果然会有种别样的氛围。 63 | 64 | 登山步道是通往山顶最快的道路。 65 | 66 | ![IMG_5466(1)](to-dashu/IMG_5466(1).JPG) 67 | 68 | 爬山过程中要注意体力的分配捏,匀速前进。 69 | 70 | 山顶的景色倒有点平平无奇了,只是拍了张回望黄山路的照片。 71 | 72 | ![306CD7D7A3F421477AF61180974AD766](to-dashu/306CD7D7A3F421477AF61180974AD766.png) 73 | 74 | 中间的道路就是黄山路,已经看不见中科大力。图片可能看不太出来,这里海拔已经接近山顶了。 75 | 76 | 之后就是愉悦的下山过程,一边下山一边外放《向山进发》的各季 op/ed,兴尽而归。下山后又顺便去合肥唯一一家 cinity 影厅看了《流浪地球 2》,后吃完晚饭,再回宿舍,不表。 77 | -------------------------------------------------------------------------------- /partial-evaluate/vueComponent.ts: -------------------------------------------------------------------------------- 1 | import type { ParseResult } from '@babel/parser' 2 | import type { NodePath } from '@babel/traverse' 3 | import type { CallExpression, File } from '@babel/types' 4 | import type { Config } from './config' 5 | import path from 'node:path' 6 | import _traverse from '@babel/traverse' 7 | import logger from './log' 8 | import { replaceDestructure, replaceMemberExpression } from './replacement' 9 | 10 | // @ts-expect-error: https://github.com/babel/babel/issues/15269 11 | const traverse = _traverse.default as typeof _traverse 12 | 13 | export function vueComponentMJSTraverseHandler(componentName: string, userConfig: Config) { 14 | return { 15 | CallExpression(path: NodePath) { 16 | if ( 17 | path.get('callee').isIdentifier({ name: 'defineComponent' }) 18 | && path.parentPath.isExportDefaultDeclaration() 19 | ) { 20 | let setupNode: NodePath | null = null 21 | let renderNode: NodePath | null = null 22 | path.traverse({ 23 | ObjectMethod(nodePath) { 24 | const key = nodePath.node.key 25 | if (key.type !== 'Identifier') 26 | return 27 | const name = key.name 28 | if (name === 'setup') 29 | setupNode = nodePath 30 | if (name === 'render') 31 | renderNode = nodePath 32 | if (setupNode && renderNode) 33 | return false 34 | }, 35 | }) 36 | 37 | logger.log(`Optimizing props for ${componentName}:`) 38 | const PropsWithKnownValue = userConfig.components[componentName] 39 | if (setupNode) { 40 | replaceMemberExpression(setupNode, 'props', PropsWithKnownValue) 41 | replaceDestructure(setupNode, 'props', PropsWithKnownValue) 42 | } 43 | 44 | if (renderNode) { 45 | replaceMemberExpression(renderNode, 'this', PropsWithKnownValue) 46 | replaceDestructure(renderNode, 'this', PropsWithKnownValue) 47 | } 48 | } 49 | }, 50 | } 51 | } 52 | 53 | export function vueComponentMJSEvaluate(ast: ParseResult, id: string, userConfig: Config) { 54 | const componentName = path.basename(id).replace(/\.mjs$/, '') 55 | traverse(ast, vueComponentMJSTraverseHandler(componentName, userConfig)) 56 | } 57 | -------------------------------------------------------------------------------- /src/composables/useBangumi.ts: -------------------------------------------------------------------------------- 1 | import { formatDate } from '~/utils/date' 2 | 3 | interface Anime { 4 | updated_at: string 5 | comment: string 6 | tags: { name: string, count: number }[] 7 | subject: { 8 | date: string 9 | images: { 10 | small: string 11 | grid: string 12 | large: string 13 | medium: string 14 | common: string 15 | } 16 | name: string 17 | name_cn: string 18 | short_summary: string 19 | tags: { name: string, count: number }[] 20 | score: number 21 | type: number 22 | id: number 23 | eps: number 24 | volumes: number 25 | collection_total: number 26 | rank: number 27 | } 28 | subject_id: number 29 | vol_status: number 30 | ep_status: number 31 | subject_type: number 32 | type: number 33 | rate: number 34 | private: boolean 35 | } 36 | 37 | interface Collections { 38 | total: number 39 | limit: number 40 | offset: number 41 | data: Anime[] 42 | } 43 | 44 | function prettyAnimeDates(animes: Anime[]) { 45 | return animes.map((anime) => { 46 | const update_time = formatDate(anime.updated_at) 47 | return { 48 | ...anime, 49 | updated_at: update_time, 50 | } 51 | }) 52 | } 53 | 54 | const PAGE_SIZE = 12 55 | const animeList: Ref = ref([]) 56 | async function updateBangumiData(page: number) { 57 | const offset = page * PAGE_SIZE 58 | // https://gist.github.com/liuly0322/7100018ad6cd9f82aff3fee1e9bcd6f3 59 | const res = await fetch( 60 | `https://bgm.liuly.moe/v0/users/undef_baka/collections?subject_type=2&type=2&limit=${PAGE_SIZE}&offset=${offset}`, 61 | ) 62 | if (!res.ok) 63 | throw new Error('Network response was not ok') 64 | 65 | const data: Collections = await res.json() 66 | const totalSize = data.total 67 | animeList.value = animeList.value.concat(prettyAnimeDates(data.data)) 68 | if (offset + data.data.length >= totalSize) 69 | throw new Error('No more data') 70 | } 71 | 72 | let page = 0 73 | const loading = ref(true) 74 | async function updateNewPage() { 75 | try { 76 | await updateBangumiData(page) 77 | page++ 78 | } 79 | catch (error) { 80 | loading.value = false 81 | } 82 | } 83 | 84 | let ticking = false 85 | async function updateAnimeList() { 86 | if (ticking || !loading.value) 87 | return 88 | ticking = true 89 | await updateNewPage() 90 | ticking = false 91 | } 92 | 93 | export default () => ({ animeList, loading, updateAnimeList }) 94 | -------------------------------------------------------------------------------- /src/styles/md.css: -------------------------------------------------------------------------------- 1 | .md-blog { 2 | max-width: 1000px; 3 | overflow-wrap: break-word; 4 | } 5 | 6 | .md-blog p, 7 | .md-blog table { 8 | line-height: 1.6em; 9 | margin: 1.6em 0; 10 | } 11 | .md-blog blockquote { 12 | margin: 10px 0; 13 | padding-left: 15px; 14 | padding-right: 10px; 15 | border-left: 3px solid #ccc; 16 | } 17 | .md-blog blockquote p { 18 | margin: 0; 19 | } 20 | .md-blog h1 { 21 | font-size: 2em; 22 | } 23 | .md-blog h2 { 24 | font-size: 1.5em; 25 | } 26 | .md-blog h3 { 27 | font-size: 1.2em; 28 | } 29 | .md-blog h1, 30 | .md-blog h2, 31 | .md-blog h3, 32 | .md-blog h4, 33 | .md-blog h5, 34 | .md-blog h6 { 35 | font-weight: bold; 36 | line-height: 1.1em; 37 | margin: 1.1em 0; 38 | } 39 | 40 | .md-blog a { 41 | color: #258fb8; 42 | text-decoration: underline; 43 | } 44 | .md-blog a:hover { 45 | text-decoration: none; 46 | } 47 | .md-blog ul, 48 | .md-blog ol, 49 | .md-blog dl { 50 | padding-left: 20px; 51 | margin-top: 1.6em; 52 | margin-bottom: 1.6em; 53 | } 54 | .md-blog ul { 55 | list-style: circle; 56 | } 57 | .md-blog ol { 58 | list-style: decimal; 59 | } 60 | .md-blog ul ul, 61 | .md-blog ol ul, 62 | .md-blog ul ol, 63 | .md-blog ol ol { 64 | margin-top: 0; 65 | margin-bottom: 0; 66 | } 67 | .md-blog img, 68 | .md-blog video { 69 | max-width: 100%; 70 | height: auto; 71 | display: block; 72 | margin: auto; 73 | } 74 | .md-blog iframe { 75 | border: none; 76 | } 77 | .md-blog table { 78 | width: 100%; 79 | border-collapse: collapse; 80 | border-spacing: 0; 81 | } 82 | .md-blog th { 83 | font-weight: bold; 84 | border-bottom: 3px solid #ddd; 85 | padding-bottom: 0.5em; 86 | } 87 | .md-blog td { 88 | border-bottom: 1px solid #ddd; 89 | padding: 10px 0; 90 | } 91 | .md-blog td footer { 92 | font-size: 14px; 93 | margin: 1.6em 0; 94 | font-family: font-sans; 95 | } 96 | .md-blog td footer cite:before { 97 | content: "—"; 98 | padding: 0 0.5em; 99 | } 100 | code[class*=language-] { 101 | padding: 1em; 102 | background-color: #fbfbfb; 103 | display: block; 104 | overflow: auto; 105 | } 106 | html.dark code[class*=language-] { 107 | background-color: #151515; 108 | } 109 | .md-blog code:not(pre>code) { 110 | color: #2f8a89; 111 | background-color: #fbfbfb; 112 | padding: 0.2em 0.4em; 113 | } 114 | html.dark .md-blog code:not(pre>code) { 115 | color: #429988; 116 | background-color: #151515; 117 | } 118 | html.dark .shiki, 119 | html.dark .shiki span { 120 | color: var(--shiki-dark) !important; 121 | } -------------------------------------------------------------------------------- /partial-evaluate/README.md: -------------------------------------------------------------------------------- 1 | # rollup-plugin-partial-evaluate 2 | 3 | [WIP] 4 | 5 | A Rollup plugin to partially evaluate expressions at build time. 6 | 7 | ## Why this 8 | 9 | Third-party Vue components often expose many props, and it's common to use them with static values. For example, you might use a `n-tag` component fron [naive-ui](https://github.com/tusen-ai/naive-ui) like this: 10 | 11 | ```vue 12 | 21 | ``` 22 | 23 | Well, although `n-tag` support closable and many other cool features, you don't need them in this case (you may just want its beautiful colors). But the props are still there, and the component will be larger than it should be. 24 | 25 | In this case, you import this plugin and use it like this: 26 | 27 | ```javascript 28 | // vite.config.js 29 | import PartialEvaluate from 'rollup-plugin-partial-evaluate' 30 | 31 | export default { 32 | plugins: [ 33 | PartialEvaluator({ 34 | silent: true, // silence the logs 35 | components: { // components to be evaluated. 36 | Tag: { // check your library `.mjs` filename 37 | disabled: false, 38 | checkable: false, 39 | closable: false, 40 | onClose: undefined, 41 | }, 42 | }, 43 | // I guess there are other works can be done with other options 44 | // like we can optimize not only Vue components but also other mjs files 45 | // WIP 46 | }), 47 | ] 48 | } 49 | ``` 50 | 51 | Then your output `dist/assets/Tag-xxxxxxx.js` will be magically smaller than before! In my case, it's 12.76kb to 9.22kb. 52 | 53 | Another example is `n-auto-complete`. After configuration: 54 | 55 | ```typescript 56 | // If you don't need features below 57 | Input: { 58 | loading: undefined, 59 | showCount: false, 60 | maxlength: undefined, 61 | pair: false, 62 | type: '\'text\'', 63 | } 64 | ``` 65 | 66 | It reduces 8.22kb after bundling! 67 | 68 | ## Context 69 | 70 | [rollup#4466](https://github.com/rollup/rollup/issues/4466) discusses the possibility of remove unused default function arguments. This plugin is a proof of concept of a relative idea: We can explicitly specify the values of some props of **Vue components**, and remove the unused code at build time. 71 | 72 | [WIP] Although tracking exports and imports is not a trivial task, and consumes a lot of time, we can still do some simple work: For those only **exported once and used once**, we can safely replace arguments with their caller's provided values. In this case, redundant code can be removed. 73 | 74 | ## How it works 75 | 76 | This plugin will parse the AST of the input file, and replace the expressions with their specified values. It's not a simple string replace, but a real AST transformation. 77 | -------------------------------------------------------------------------------- /.autocorrectrc: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://huacnlee.github.io/autocorrect/schema.json 2 | rules: 3 | # Default rules: https://github.com/huacnlee/autocorrect/raw/main/autocorrect/.autocorrectrc.default 4 | spellcheck: 1 5 | space-word: 0 6 | space-punctuation: 0 7 | space-bracket: 0 8 | fullwidth: 0 9 | textRules: 10 | # Config some special rule for some texts 11 | # For example, if we wants to let "Hello你好" just warning, and "Hi你好" to ignore 12 | # "Hello你好": 2 13 | # "Hi你好": 0 14 | # "llyのblog": 0 15 | fileTypes: 16 | # Config the files associations, you config is higher priority than default. 17 | # "rb": ruby 18 | # "Rakefile": ruby 19 | # "*.js": javascript 20 | # ".mdx": markdown 21 | spellcheck: 22 | words: 23 | # Please do not add a general English word (eg. apple, python) here. 24 | # Users can add their special words to their .autocorrectrc file by their need. 25 | - ActiveMQ 26 | - Flex 27 | - AirPods 28 | - Aliyun 29 | - API 30 | - App Store 31 | - AppKit 32 | - AppStore = App Store 33 | - AWS 34 | - CacheStorage 35 | - CDN 36 | - CentOS 37 | - CloudFront 38 | - Cminusf 39 | - CORS 40 | - CPU 41 | - DNS 42 | - Elasticsearch 43 | - ESLint 44 | - Facebook 45 | - GeForce 46 | - GitHub 47 | - GitLab 48 | - Google 49 | - GPU 50 | - H5 51 | - Hadoop 52 | - HBase 53 | - HDFS 54 | - HKEX 55 | - HTML 56 | - CSS 57 | - Vue 58 | - JS 59 | - TeX 60 | - Hexo 61 | - Copilot 62 | - Verilator 63 | - HTTP 64 | - HTTPS 65 | - I10n 66 | - I18n 67 | - iMovie 68 | - IndexedDB 69 | - Intel 70 | - iOS 71 | - iPad 72 | - iPadOS 73 | - iPhone 74 | - iTunes 75 | - jQuery 76 | - JSON 77 | - JWT 78 | - Linux 79 | - localStorage 80 | - macOS 81 | - Markdown 82 | - Microsoft 83 | - MongoDB 84 | - Mozilla 85 | - MVC 86 | - MySQL 87 | - Nasdaq 88 | - Netflix 89 | - NodeJS = Node.js 90 | - NoSQL 91 | - NVDIA 92 | - NYSE 93 | - OAuth 94 | - Objective-C 95 | - OLAP 96 | - OSS 97 | - P2P 98 | - PaaS 99 | - RabbitMQ 100 | - Redis 101 | - RESTful 102 | - RSS 103 | - RubyGem 104 | - RubyGems 105 | - SaaS 106 | - Sass 107 | - SDK 108 | - Shopify 109 | - SQL 110 | - SQLite 111 | - SQLServer 112 | - SSL 113 | - Tesla 114 | - TikTok 115 | - tvOS 116 | - TypeScript 117 | - JavaScript 118 | - Ubuntu 119 | - UML 120 | - URI 121 | - URL 122 | - VIM 123 | - watchOS 124 | - WebAssembly 125 | - WebKit 126 | - Webpack 127 | - Wi-Fi 128 | - Windows 129 | - WWDC 130 | - Xcode 131 | - XML 132 | - YAML 133 | - YML 134 | - YouTube 135 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lly_blog", 3 | "type": "module", 4 | "version": "1.5.0", 5 | "description": "vite + vue3 + ts 开箱即用现代开发模板", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "postinstall": "npm run build", 10 | "dev:host": "vite --host", 11 | "dev:open": "vite --open", 12 | "preview": "vite preview", 13 | "deps:fresh": "npx taze -w", 14 | "typecheck": "vue-tsc --noEmit", 15 | "deps:fresh:major": "npx taze major -w", 16 | "deps:fresh:minor": "npx taze minor -w", 17 | "deps:fresh:patch": "npx taze patch -w", 18 | "preview:host": "vite preview --host", 19 | "preview:open": "vite preview --open", 20 | "lint": "eslint ./src ./build", 21 | "lint:fix": "eslint ./src ./build --fix" 22 | }, 23 | "devDependencies": { 24 | "@antfu/eslint-config": "^3.16.0", 25 | "@babel/core": "^7.26.0", 26 | "@babel/generator": "^7.26.5", 27 | "@babel/parser": "^7.26.5", 28 | "@babel/traverse": "^7.26.5", 29 | "@babel/types": "^7.26.5", 30 | "@iconify-json/ant-design": "^1.2.5", 31 | "@iconify-json/carbon": "^1.2.5", 32 | "@iconify-json/fluent-emoji-flat": "^1.2.2", 33 | "@iconify-json/line-md": "^1.2.5", 34 | "@rollup/plugin-terser": "^0.4.4", 35 | "@shikijs/markdown-it": "^2.1.0", 36 | "@types/babel__core": "^7.20.5", 37 | "@types/babel__generator": "^7.6.8", 38 | "@types/babel__traverse": "^7.20.6", 39 | "@types/markdown-it": "^14.1.2", 40 | "@types/markdown-it-link-attributes": "^3.0.5", 41 | "@types/node": "^22.10.10", 42 | "@types/nprogress": "^0.2.3", 43 | "@types/rss": "^0.0.32", 44 | "@vitejs/plugin-vue": "^5.2.1", 45 | "@vue/compiler-sfc": "^3.5.13", 46 | "@vue/runtime-core": "^3.5.13", 47 | "@vueuse/core": "^12.5.0", 48 | "@windicss/plugin-animations": "^1.0.9", 49 | "aplayer-ts": "^2.6.0", 50 | "art-template": "^4.13.2", 51 | "eslint": "^9.18.0", 52 | "eslint-plugin-vue": "^9.32.0", 53 | "frontmatter": "^0.0.3", 54 | "image-size": "^1.2.0", 55 | "katex": "^0.16.21", 56 | "lightningcss": "^1.29.1", 57 | "markdown-it": "^14.1.0", 58 | "markdown-it-anchor": "^9.2.0", 59 | "markdown-it-link-attributes": "^4.0.1", 60 | "markdown-it-texmath": "^1.0.0", 61 | "naive-ui": "^2.41.0", 62 | "nprogress": "^0.2.0", 63 | "rollup-plugin-visualizer": "^5.14.0", 64 | "rss": "^1.2.2", 65 | "terser": "^5.37.0", 66 | "typescript": "^5.7.3", 67 | "unplugin-auto-import": "^19.0.0", 68 | "unplugin-icons": "^22.0.0", 69 | "unplugin-vue-components": "^28.0.0", 70 | "unplugin-vue-markdown": "^28.1.0", 71 | "vite": "^5.4.14", 72 | "vite-plugin-pages": "^0.32.4", 73 | "vite-plugin-pwa": "^0.21.1", 74 | "vite-plugin-vsharp": "^1.8.1", 75 | "vite-plugin-windicss": "^1.9.4", 76 | "vue": "^3.5.13", 77 | "vue-router": "^4.5.0", 78 | "vue-tsc": "^2.2.0", 79 | "windicss": "^3.5.6" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /posts/hexo-night-git.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hexo 博客夜间模式的实现兼 git 工作流简介 3 | date: 2021-07-09 09:36:41 4 | tags: [hexo, git] 5 | category: web 6 | tocbot: true 7 | --- 8 | 9 | 本篇博客将记录 Hexo 默认 landscape 主题的夜间模式实现,以及 git 工作流的简单介绍。 10 | 11 | ## 基本思路 12 | 13 | 要想实现夜间模式,就意味着应用 CSS 的改变。这里作为 JS 练习,采用纯前端的方法实现:如果当前开启了夜间模式,那么就向根结点,即 HTML 结点增加一个类 `dark-theme`,再重写深色模式使用的样式表,放置在原有样式表最后即可。 14 | 15 | 16 | 17 | 为了定位到根结点,可以采用 `document.documentElement` 18 | 19 | 为了向某一结点增加 / 删除类,可以采用 `classList.toggle('dark-theme')`,toggle 本意便是在两种状态间切换。 20 | 21 | 于是合并,就得到了第一版的 JS 代码: 22 | 23 | ```javascript 24 | function changeDarkTheme() { 25 | document.documentElement.classList.toggle("dark-theme"); 26 | } 27 | ``` 28 | 29 | 接下来顺利成章的开始写界面上的按钮以及夜间模式对应的样式表: 30 | 31 | 对应的 CSS 文件可以在网页的检查中找到,都写在一块,很方便看( 32 | 33 | 这样一来就完成了夜间模式的初版。 34 | 35 | ## 利用 sessionStorage 记忆 36 | 37 | 在上文那样简单完成初版后,问题很快就会出现:当进入新的页面时,网站无法记住之前的选择,这就导致想使用夜间模式时,每次进入新的网页都需要重新设置夜间模式,很麻烦。这样看来,我们需要能够全局记住用户的选择。 38 | 39 | 在 html5 中,提供了两种站点本地存储的方式,分别是 sessionStorage 和 localStorage。它们的区别在于 sessionStorage 的数据仅相当于在当前的标签页中生效,关闭标签页后数据即丢失,而 localStorage 则会全局生效,即使关闭浏览器也会继续存储。 40 | 41 | 这里采用 sessionStorage 实现(因为希望每次先考虑用户是否已经有系统设置上的偏好(深色模式)) 42 | 43 | ```javascript 44 | function changeDarkTheme() { 45 | document.documentElement.classList.toggle("dark-theme"); 46 | if (document.documentElement.classList.contains("dark-theme")) { 47 | sessionStorage.setItem("hexoTheme", "1"); 48 | } else { 49 | sessionStorage.setItem("hexoTheme", "0"); 50 | } 51 | } 52 | 53 | if (sessionStorage.getItem("hexoTheme") === null) { 54 | //第一次访问网站,试图参考系统设定 55 | const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)"); 56 | if (prefersDarkScheme.matches) { 57 | sessionStorage.setItem("hexoTheme", "1"); 58 | changeDarkTheme(); 59 | } else { 60 | sessionStorage.setItem("hexoTheme", "0"); 61 | } 62 | } else { 63 | const theme = sessionStorage.getItem("hexoTheme"); 64 | if (theme == 1) { 65 | changeDarkTheme(); 66 | } 67 | } 68 | ``` 69 | 70 | 每次打开网页时,通过检测 sessionStorage 判断是否需要应用夜间模式。而夜间模式的切换按钮作用就是添加 / 删除 `dark-theme` 类,并更改 sessionStorage。 71 | 72 |

注意:这个 JS 应该放在 head 中加载,如果放在 footer 后加载会导致浅色样式生效后再应用深色模式

73 | 74 | ## git 工作流 75 | 76 | 本次增加夜间模式所进行的修改都是在修改 Hexo 主题基础之上完成的。而一个主题会包含很多文件,有时候很难记住自己所进行的修改,再加上修改一般会很多很杂,这个时候就切实需要一个好用的版本控制工具。于是这里采用了 git。 77 | 78 | ### git 安装及配置 79 | 80 | git 的安装直接在官网下载即可。Ubuntu 则可以直接 `sudo apt-get install git` 来安装 git。 81 | 82 | 此后配置用户名和邮箱: 83 | 84 | ```bash 85 | git config --global user.name "xxx" 86 | git config --global user.email "xxx" 87 | ``` 88 | 89 | 为了搭配 GitHub 使用,可以利用 SSH 验证。 90 | 91 | 输入: 92 | 93 | ```bash 94 | ssh-keygen -C 'you email address@gmail.com' -t rsa 95 | ``` 96 | 97 | 在 `~/.ssh` 中找到公钥文件,打开复制至自己 GitHub 设置中去即可。 98 | 99 | 可以通过 `git init` 初始化仓库,考虑和 GitHub 同步,则可以 `git clone` 来复制已有仓库。GitHub 某一个仓库中点击 clone,选择 SSH,复制后面内容,添加到 `git clone` 后即可。也可以自行指定本地仓库的路径,否则则会 clone 到当前文件夹。 100 | 101 | 此后通过 `git commit -m "这里写注释"` 可以提交当前修改(还是在本地仓库)。最后 `git push origin main` 即可推送到 GitHub。 102 | 103 | vscode 图形界面已经集成了 git 的一些基本操作,可以很方便的完成推送。 104 | 105 | 如果想查看每次 commit 造成的更改,可以使用 `git log`,增加参数 `-p` 会显示每次 commit 造成的变化(增量形式),增加参数 `-num`(num 是自己制定的数字),则可以限制显示的提交次数。 106 | -------------------------------------------------------------------------------- /src/pages/bangumi.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 88 | -------------------------------------------------------------------------------- /posts/shoushimin.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 《小市民系列》:小市民的后黄金时代——苦涩后的温柔 3 | date: 2025-06-20 11:07:35 4 | tags: [杂谈, anime] 5 | category: 日志 6 | --- 7 | 8 | 本文是对米泽穗信《小市民系列》的书评。以后 Bangumi 个人写的比较满意的 ACGN 评论也会放在这个博客上。本文假定读者已经阅读过《小市民系列》或观看过改编的两季动画。 9 | 10 | > <完结作是“尾声,也是序章”> 11 | 12 | 自《秋季限定糖渍栗子事件》出版以来,已经十五年过去。米泽穂信的青春推理小说“小市民”系列四部曲终于在《冬季限定夹心巧克力事件》画上了句号。音乐中的尾声是高潮后的余韵。《秋》中,小鸠和小佐内的自我认知的故事已经完结。《冬》一方面是《秋》中狐狼成长与关系性之余韵,另一方面又是对小市民起源故事的补完。本文就以小鸠的成长为线,感受《小市民》系列的魅力。 13 | 14 | 15 | 16 | ## 序章 17 | 18 | > 正在作梦的我看著梦中那个得意忘形的我,心情非常苦涩。不知是不是这种心情影响了梦境,有一个人从不断欢呼的观众之中走出来。 19 | >

20 | > 那是谁呢?会对我说这种话的人,我想得出好几个。他,或是她,笑咪咪地说: 21 | >

22 | > 「真的很厉害。精彩的推理,缜密的论证。可是,那个,嗯,该怎么说呢,虽然有些难以启齿,但我还是直说吧。 23 | >

24 | > 你真的很惹人厌耶。」 25 | >

26 | > 真是的,这个梦未免太吓人了。醒来以后,我的心脏还是怦怦地跳个不停,我简直担心自己会不会因此得到心脏病。 27 | 28 | **小市民故事的缘起从《春》开头小鸠做的梦中便有所暗示**。正如原文,是一个相当具有米泽风味的苦涩故事。自以为无所不能的少年想要影响与改造世界,我想这也是大家都曾做过的梦吧,“像我这样的人,认真起来一定可以做出些什么!” 29 | 30 | 但是少年在一腔热血、满腹激情和冲劲的同时,也是不成熟的,缺乏对世界认知的。少年可以考试考到高分;少年可以推理出学校内事件的“犯人”……局限在校园场景内的少年可以做到很多事情。但是在更大的社会乃至世界尺度内,**有太多的事情少年做不到**。 31 | 32 | 其他米泽的作品里也有这样的情节。少年无法挽回奔向危险故土的少女(《再见,妖精》);即使少年能找到失踪的少女也只能默默陪伴在哭泣的她的身边(《迟来的翅膀》)…… 33 | 34 | 对小鸠来说,他的这份故事在《冬》中得到了补完。初中时的小鸠沉迷在推理的乐趣和发现真相的快感中,丝毫不顾后果的调查日坂祥太郎的交通事故,然而结果却是破坏了日坂姐弟二人恢复幸福家庭的契机。小鸠什么也没做到,只不过是伤害了日坂君。 35 | 36 | ![](./shoushimin/20250620-1.png) 37 | 38 | **少年的黄金时代结束了,苦涩感涌上心头**。 39 | 40 | 这种少年阶段自负的全能感的丧失,对中二感的破坏,对世界系的反叛是米泽作品苦涩味道的源头。虽然说“少年有很多事做不到”不等于说“少年什么事都做不到”,但总归还是让作品的基调带上了一份灰色的阴影。 41 | 42 | 少年陷入了成长的迷茫:为什么世界这么灰暗荒芜,为什么我这样无力渺小? 43 | 44 | ## 迷茫的小市民 45 | 46 | > 于是我再次选择了逃避,屏蔽了这起事件的一切后续消息。视而不见,听而不闻。对于整起事件,我所知的内容仅仅是那天下午读到的晚报简讯。更没有在逮捕犯人后跟日坂君谈起过这件事。日坂君在事故之后也是寡言少语,和任何人都不怎么说话。 47 | 48 | **黄金时代结束后,少年的第一选择是逃避。小市民的故事开始了**。 49 | 50 | > 我发誓上高中后要自我封印自己这愚蠢的癖好。我们和彼此约定要互帮互助,共同恪守小市民的行为规范。 51 | 52 | 然而「小市民」这个词既体现出狐狼对自己的低估,又太傲慢。这不是狐狼找到的最终答案。小市民们还需要经历高中三年的磨砺才最终评估出了自己是个怎么样的人。这中间就是《春》《夏》《秋》的故事。期间狐狼二人一直在尝试扮演「小市民」,但是现实没有这么顺利。 53 | 54 | 《夏》中,二人发现了隐藏在彼此「小市民」互惠关系下的谎言。因为他们彼此理解、彼此依赖,所以狐狼在一起时无法放弃自己的本性,无法成为「小市民」。 55 | 56 | 《秋》中,二人分开了。然而这样就能成为「小市民」了吗?答案也是否定的。越是尝试正常生活,狐狼二人越是感到这和自己本性的相抵触。小鸠觉得与仲丸的交往愈发压抑,小佐内觉得瓜野真是无趣。 57 | 58 | 分类讨论完毕,结论:**狐狼二人无法成为「小市民」。狐狼二人虽然并非全能,但也不是无能**。虽然逛街约会也很美好,但我果然还是更想把连续纵火案的推理经过都说给你听。 59 | 60 | ## 尾声 61 | 62 | > 我们口中的「小市民」是用来和周围人们相处的口号,是为了避免再次受到孤立的场面话。就像是投降的白旗,用来告诉别人「我一无是处,请放过我吧」。 63 | >

64 | > 这句口号说了三年,我终于明白了。如果我真的想和别人和平共处,根本不需要用这种话来抹杀自己的本性。我越是挥舞白旗,心中越会萌生出反叛和厌恶,而且会越来越轻视别人。 65 | >

66 | > 不是这样的。我真正需要的并不是披上「小市民」的羊皮。 67 | >

68 | > 只要有一个理解我的人就足够了。 69 | 70 | 我无意进行比较,只是感到庆幸,米泽穂信让故事有了一个温柔的结局(看完《再见,妖精》实在是郁闷……)。 71 | 72 | **《秋》的结尾,少年迎来了他的后黄金时代。狐狼找到了彼此的唯一理解者**。 73 | 74 | > 「发现她在校刊社的周遭蠢动时,我就猜到她打算报复某人了。」 75 | >

76 | > 「我早就知道你一定看得出来。」 77 | 78 | **在全能与无能之间,狐狸与狼发现了只有彼此才能看得见的绚丽风景,找到了独属于二人的世界。就算世界灰暗不讲理也无所谓,就算我能做到的事情有限也无所谓,因为我已经抓住了让世界变成彩色的你**。 79 | 80 | 「至少对现在的我来说……你是不可或缺的。」 81 | 82 | ![](./shoushimin/20250620-2.png) 83 | 84 | *今晚真的很热。感觉比刚才更热了*。 85 | 86 | 我承认我无法任由自己心意改变世界,既然如此我要找出我和世界的和谐相处之道。到这里还是传统青春成长故事的理论。但《小市民》美妙的地方就在于狐狼二人共同构建了他们与世界的和谐。他们可以互相理解并包容对方的异质,并携手走向、开创属于自己的未来。 87 | 88 | > 《冬季》卷中小鸠在现在的时间轴上应该一次也没有使用“小市民”这个词。因为这个词对他来说,已经不再需要了。 89 | 90 | 《冬》是狐狼二人对「小市民」的告别与前行。**自我认知已经完成的狐狼二人在高三疯狂撒糖qwq**。看完《冬》后的读者一定能感受到小佐内始终对在医院的小鸠的担心牵挂和爱。这一部分附录中第三篇文章摘抄了很多原文段落,好磕。 91 | 92 | ![](./shoushimin/20250620-3.png) 93 | 94 | 结语:在米泽穗信笔下,小市民不是终点,而是一条通往真正自我的过渡地带。在告别幻想万能的同时,狐狼也终于拥抱了真实的温柔。我们这样的少年虽然做不到拯救世界,但也能找到生活下去的理由啊! 95 | 96 | ## 附录 97 | 98 | 一些比较喜欢的分析及参考文章: 99 | 100 | 1. [后世界系的瓶颈 - 北窓](https://book.douban.com/review/6650934/) 101 | 2. [从《冰菓》说开去——与世界系抗争的米泽穗信 - 巴甫洛夫的忌日](https://www.gcores.com/articles/106434) 102 | 3. [在百无聊赖日常中,为你创造名为“迷宫”的居所:'小市民'系列主人公成长向浅析——以《冬季限定法式巧克力事件》为中心](https://book.douban.com/review/15919822/) 103 | 4. [专访:米泽穗信谈【小市民】系列完结——青春之谜的金字塔](https://www.bilibili.com/opus/939796276931723302) 104 | -------------------------------------------------------------------------------- /posts/senior-year.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 为美好的大四献上总结 3 | category: 日志 4 | date: 2024-04-29 00:46:27 5 | tags: [杂谈] 6 | --- 7 | 8 | 一篇杂谈文章。在四月份末写大四总结似乎是一件很奇怪的事情(是否也是一种五月病),但过去一年的经历实在让我产生了太多梦幻的感觉,有些害怕这些感觉就这么溜走:我并没有拍照记录生活的习惯,偶尔回看为数不多的照片也常常忘记了当时的思绪。所以我决定 push 自己写下这篇文章。(虽然已经有很多想法不记得了,本文写作过程中一直在翻自己的 Twitter 和 Telegram) 9 | 10 | 大概是一些关于实习、毕业设计、科研和未来生活的碎碎念。 11 | 12 | 13 | 14 | ## MSRA 实习 15 | 16 | 实习的想法是早就产生了,不过大三上投米哈游游戏后端暑假实习一面挂了,当时确实 OS 基础不是很牢,八股也没怎么准备。之后有着找实习的想法,大三下把培养方案要求的学分都学完了,不过当时并没有考虑 MSRA,直到现实给了我狠狠的一击——找一个适合自己的实习并不是一件容易的事情,我对开发业务的兴趣也不是很大,对开发八股也不是很熟。后来看同学都在投 MSRA 才考虑起了这个机会,开始准备。不过还是没去成自己更感兴趣的 System 组,来了上海的无线组。 17 | 18 | > U149完结了;本科最后一门考试也要结束了,脑中充斥着不真实感,一学期选了七门专业课好在最后还是平平安安学下来了,接下来该好好享受暑假了 19 | 20 | (去上海前发的 telegram,其实根本没啥暑假,七月二十多就入职了) 21 | 22 | 实习本身可能没啥好说的,我在无线组基本就打打杂,确实没有很系统的活给我干,在参与一些光追模拟信号和算法(深度学习)相关的项目,认清了自己果然不适合机器学习。不过实习的同学们人都很好,有点怀念一起吃饭的日子。不过因为没有想象中的收获,还是没按原计划干到六月底,而是两月初春节前就离职了。 23 | 24 | MSRA 的体验很好,升降桌、不限量的零食饮料(饮料可以喝无糖,零食太长胖了),弹性工作随时可以 remote。后面又在某厂找了一段实习才怀念起这些东东的好。 25 | 26 | ## 上海 27 | 28 | ### 城市 29 | 30 | 九个月过去,高德地图里的上海探索度已经达到了 20%+,可见完全达到了学习和生活的平衡。我很喜欢上海的气候条件和开放包容的文化氛围。作为一个安徽人,上海的气候没有北京那么寒冷干燥,没有川渝地区或广东那么闷热潮湿,正好适合我。 31 | 32 | 上海这座城市里有忙忙碌碌的人们(也是我对上海的第一印象),也有悠闲自在的生活。有 996 的大厂员工,也有苏州河沿岸的钓鱼佬,各个公园里面散步的人们。虽然 City Walk 一词现在经常被拿来调侃,但不得不说在上海散步的体验是绝佳的。 33 | 34 | 沿着浦西滨江绿道从徐汇滨江走到黄埔滨江外滩,既有现代化的高楼大厦,也有历史悠久的建筑和舒适的江风。浦东滨江则有大大小小的世博公园、前滩公园等,花两块钱乘坐渡轮在两岸间穿梭,好不惬意。 35 | 36 | 说到历史建筑,就不得不提衡复风貌区(俗称梧桐区)。这里有很多老式的石库门建筑,有很多文艺小店和咖啡馆,武康大楼、田子坊。这里的街区不是宽马路商业中心形式,而是发达的沿街商业区,有很多小巷子和小店,适合慢慢逛。 37 | 38 | 徐家汇公园是另一处我很喜欢的地方。它就坐落在徐家汇地铁站旁,附近就是繁华的美罗城,港汇恒隆等商业区。公园里却有黑天鹅有小瀑布,植物和花卉的设计也颇有移步换景之感。 39 | 40 | 上海 City Walk 的体验也离不开公共交通系统的支持。虽然市区地铁站密度还没有覆盖到一公里一站的程度,也少有大站快线,但总体而言拥堵的时间和程度都可以接受。上海的公交车也很值得称赞,尤其是夜班公交的存在,如果你也有过零点在上海虹桥看着一百多块钱的打车费后选择两块钱的夜班公交车的经历,你就懂了( 41 | 42 | 铁路的覆盖也很好。尝试金山铁路直达车坐到金山(上海南方)看海,虽然天气不好没怎么看成。此外也坐地铁去过朱家角(上海西方),打算去滴水湖(上海东南)。现在对上海地铁比对合肥地铁都熟。听闻连接虹桥和浦东国际机场的机场联络线 2024 就要建好了,可喜可贺。 43 | 44 | ### ACG 45 | 46 | 虽然上面花了很多篇幅说上海的城市风貌,但最让我喜欢的还是上海的 ACG 氛围。有高达、EVA 立像;有各种各样的谷店;活动上音乐会、Livehouse、漫展都是不缺的;还有各种各样的小众店铺和活动(~~雾雨咖啡店~~)。 47 | 48 | 虽然接不到广告,但自认为对上海谷店的理解已经超过很多小红书攻略作者了。比较集中的是南京东路片区,百联 ZX、百米香榭、外文书店、新世界城、第一百货、迪美(以及三者下方的人民广场地下商业区)、静安大悦城等。此外杨浦区的大学路的 animate 和徐汇区的美罗城(和旁边的 TPY 中心)都有不少店铺。 49 | 50 | 大部分店铺卖的东西其实大同小异,当下比较流行的还是一些二游乙游偶像和一些运动番(小排球蓝锁 free 等),在一些比较特色的店铺或者中古店还是能逛到很多好东西的。百联 ZX 负一楼有一家角川书屋,虽然比较小但有一些特色的翻译书籍,Fate/迷宫饭/冰菓等;负一楼有一面墙卖的吧唧也比较深夜番向;楼上的 animate 也有不少 IP;漫库有一些轻小说;bushiroad 专卖店有青木阳菜(~~圣青木~~)的签名。马上 Aniplex 店也要开了。 51 | 52 | 一些厂商往往也会在上海有专门店,比如卡普空和光荣特库摩。虽然它们卖的东西不是很二(?)。 53 | 54 | 不过更好逛的往往还是一些个人店和中古店。周末的时候迪美和第一百货都会有摆摊活动,除此之外百米香榭(手办和卡牌居多)也是一处个人店集散地。中古店的话,迪美有一家 kyoko,东西很多看着就不太卖的出去但挺全,除了常见的徽章之外还会有不少 CD,挂画之类的商品。在新世界城和 TPY 中心各开了一家的万香屋东西也不错,物语/小圆/Fate/(幸运星、轻音、京吹等京都动画作品)都有,但这家店太贵了,真想买可以先在网上看看价格。 55 | 56 | 活动的话一般可以在 B 站会员购或者 CPP 看,B 站会员购不能时间排序其实挺麻烦,所以个人在爬 B 站数据然后 [展示界面](https://liuly.moe/BilibiliAnimationExhibitionInformationCollection/)。(credits:@zxcsjf) 57 | 58 | 不知不觉就写成旅游攻略了,但上海确实是文化氛围浓厚()大四一年宅浓度大大上升了。早该享受生活了。 59 | 60 | ## 毕设 61 | 62 | 说起毕设确实有点有心栽花花不开,无心插柳柳成荫的感觉。从大四开始就纠结于毕设选题。在 MSRA 做的事情很好,能发文章,但并不是我喜欢的事情。后来一度陷入摆烂状态,下半学期三月份看见同学在捣鼓博客后又把自己的博客捡了起来,这个时候已经完全对毕设无所谓了,想着五月份再开始随便找个题目凑合凑合。结果没想到一折腾博客还真发现现有的 Vue 生态打包会有很多无用的代码,于是就有了给 rollup 的 PR 和毕设的题目。一开始调试的时候异常兴奋,不过要达到 rollup PR 的完成度还是挺折磨的,de 了无数 bug。中间和同学讨论确定想法的过程也很爽。 63 | 64 | 要说这段经历的收获,或许是让我不再那么“眼高手低”。虽然从去年七月开始我的 QQ 签名就一直是“敢想敢做”,但实际上我还是有很多脚步没有真正迈出。过程中一度有好多时刻想摆烂放弃,但完成了再看也不过如此,所以还是不要怕失败。不过虽然说是这么说,第一版 PR 被合并后第二天看见一个二十多条评论的 issue 还是挺慌的。revert,重构,真正走过一遍流程后才明白 Code Review 不能只是看起来对测试跑着也对就行,还是要深刻理解自己写的每行代码。 65 | 66 | ~~此外前端不愧是有点娱乐圈了,被 Evan You 评论后看同学在大群发截图还是挺羞耻的~~ 67 | 68 | ## 想做的事 69 | 70 | 一年间也算是对科研有了更广泛的见识,也产生了一点自己的想法。科研确实是一件兴趣驱动的事情,而我好像很难对一个领域产生深入的兴趣的坚持。虽然算是对 PL 感兴趣,但也啃不下去类型论 HoTT 那些东西。还是更喜欢那些立即就会有反馈的目标明确的事情,或许这就是为什么我这四年一直对前端有兴趣的原因,生活还是应该多做自己让感兴趣/开心的事情。不知道是不是看冻鳗看的,现在变得挺热爱生活的,是好事。造新轮子是一件很有成就感的事情,希望我写的代码能让世界变得更美好了一点点。为美好的世界献上祝福! 71 | -------------------------------------------------------------------------------- /posts/static-page-aes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 静态页面 AES 加密的使用 3 | date: 2021-09-10 21:09:09 4 | tags: [密码学] 5 | category: web 6 | tocbot: true 7 | --- 8 | 9 | 本文将简单介绍一种静态界面需要加密的情景,并给出使用 AES 加密算法的一种实现。 10 | 11 | ## 静态内容的加密 12 | 13 | 有些时候,我们可能需要在页面上添加一些需要密码才能查看到内容。但对于一个静态页面而言,页面源代码初始时便已经由服务器发送到了设备上,所以仅仅只通过增加一个输入密码框来“欺骗”用户并不能很好地起到保密的作用。鉴于此,我们得考虑静态页面源代码中相关内容就已经被加密,而用户通过自己的设备输入密钥,运算,来获取原有的明文。 14 | 15 | 16 | 17 | ## 已知明文攻击 18 | 19 | 如果仅仅发送密文,需要用户自己输入密钥解密,这种方式理论上已经比较安全了,但我们还希望在前端增加一个密码判定,能够判断用户是否使用了正确的密钥,只有密钥正确才会将解密的明文放到页面上,否则解密出的“明文”没有意义,应该不予反应。 20 | 21 | 在这种情况下,我们可以考虑额外增加一组对应的试探明文和密文,用户使用密钥时,先利用这个密钥解密试探密文,如果解密出了试探明文,那么再继续解密密文,得到正确的明文。 22 | 23 | 这样做虽然使得界面更加的人性化,但是却额外增加了一定的风险:攻击者可以额外得到一组明文和密文。就密码学而言,这称为 **已知明文攻击** ,因为攻击者可以根据这一组明文和密文来猜测正确的密钥,所以在加密时,我们选择的算法应当是能防范已知明文攻击的。 24 | 25 | 不过对于现代的加密算法而言,都不需要担心这个问题。 26 | 27 | 本篇文章中,我们使用 AES 加密算法来解决我们的需求。这是一种对称的加密算法,也就是加密和解密共用同一个密钥。 28 | 29 | ## AES 加密的使用 30 | 31 | ### 引入 AES 加密 32 | 33 | 想要在静态页面使用 AES 加密,可以直接使用谷歌开发的一个纯 JavaScript 的加密算法类库: **crypto-js** 。 34 | 35 | 引入: 36 | 37 | ```html 38 | 42 | ``` 43 | 44 | 再查阅该 JS 的 AES 相关 API: 45 | 46 | ```javascript 47 | var CryptoJS = require("crypto-js"); 48 | 49 | // Encrypt 50 | var ciphertext = CryptoJS.AES.encrypt( 51 | "my message", 52 | "secret key 123" 53 | ).toString(); 54 | 55 | // Decrypt 56 | var bytes = CryptoJS.AES.decrypt(ciphertext, "secret key 123"); 57 | var originalText = bytes.toString(CryptoJS.enc.Utf8); 58 | 59 | console.log(originalText); // 'my message' 60 | ``` 61 | 62 | 还是非常简明的。 63 | 64 | ### 静态页面中的使用 65 | 66 | 首先看一下最后成品: 67 | 68 | (见 [原博客](http://home.ustc.edu.cn/~liuly0322/blog/2021/09/10/static-page-aes/#more),现在搬迁到 Vue3 + TS 了重写有点麻烦) 69 | 70 | 随后我们再逐部分介绍。 71 | 72 | 这个成品的正确密钥可以在文末获取(括弧笑 73 | 74 | ~~由于懒得写 CSS 所以很丑~~ 75 | 76 | #### 预加密处理 77 | 78 | 首先需要在提前对试探明文和明文进行加密。 79 | 80 | 可以现在任意网页中引入 `crypto-js` (比如本页面),然后根据 API 说明,进行加密。控制台中输入: 81 | 82 | ```javascript 83 | temp = CryptoJS.AES.encrypt("明文", "密钥").toString(); 84 | ``` 85 | 86 | 然后输入 `temp` ,键入回车后即可查看对应的密文 87 | 88 | 举例: 89 | 90 | ```javascript 91 | temp = CryptoJS.AES.encrypt("stDkxwE", "password").toString(); 92 | //获取本文的试探密文 93 | ``` 94 | 95 | 对于明文部分,我们可以选择使用 HTML 文本,这样只要后面 JavaScript 采用 innerHTML 方法替换文本就可以保留需要的格式。 96 | 97 | #### 界面部分 98 | 99 | ```html 100 |
101 | 105 | 请输入密码查看隐藏内容: 106 | 107 |
108 | ``` 109 | 110 | 整体加密部分采用一个黑色边框包裹起来。 111 | 112 | 初始时,密文部分 `display: none` ,仅显示输入框。密文部分还包含了一组试探的明文和密文,分别在 `data-origin` 和 `data-now` 。 113 | 114 | 提交按钮会传入 `this` 参数,指示当前按钮。 115 | 116 | #### JS 逻辑 117 | 118 | ```javascript 119 | function crypto(sub) { 120 | const e = sub.parentNode.firstElementChild; //密文部分 121 | const key = sub.previousElementSibling.value; //用户输入的密钥 122 | const origin = e.getAttribute("data-origin"); //试探明文 123 | const now = e.getAttribute("data-now"); //试探密文 124 | const res = CryptoJS.AES.decrypt(now, key).toString(CryptoJS.enc.Utf8); 125 | if (res == origin) { 126 | //如果密钥正确 127 | const t = e.innerHTML.replace(/\s/g, ""); 128 | sub.parentNode.innerHTML = CryptoJS.AES.decrypt(t, key).toString( 129 | CryptoJS.enc.Utf8 130 | ); 131 | } else { 132 | alert("密码错误!"); 133 | } 134 | } 135 | ``` 136 | 137 | 比较简单,基本看注释就行() 138 | 139 | `e.innerHTML.replace(/\s/g, "")` 需要注意一下,是通过正则表达式去除了可能的空格和换行的干扰。 140 | 141 | 到这里,这个简易的加密系统就已经完成了。 142 | 143 | 本文中的成品正确的密钥为: `password` 。 144 | -------------------------------------------------------------------------------- /posts/atypical-tour-to-peking.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 非典型北京游记 3 | category: 日志 4 | date: 2025-02-24 09:39:20 5 | tags: [杂谈] 6 | --- 7 | 8 | 研一寒假结束前去了趟北京。对于宅了一整个本科最远只从合肥去到上海的我,算是一次颇为新鲜的旅行。 9 | 10 | 契机是 2024 年底跨年时在合肥包河万象汇听了 [MySalt_GirlsBand](https://space.bilibili.com/3537123244247191) 的演出,当时就喜欢上了这支北京乐队。然后得知她们参演 [北京 BanG Dream!新春庆典 SP Live](https://www.bilibili.com/opus/1017453868153307139),正好我也想听 Poppin'Party 的 God Knows...,于是就决定去北京看 live。 11 | 12 | 来都来了,那自然是多规划几天。确定一下夕发朝至的火车票价格可以接受(往返约 500 元),再稍微对北京的景点和 ~~二次元周边店~~ 做了一点攻略后,就规划了三个白天的行程:看景点、逛街、看 live。 13 | 14 | 15 | 16 | ## Day 0 17 | 18 | 21:40 运转 Z227 次前往北京丰台。前身是 Z74 次,京沪十三猪之一。 19 | 20 | > 京沪十三猪指 2004 年 4 月 18 日中国铁路第五次大提速后在京沪铁路上运行的 12 对直达特快列车以及 1 对按直达特快标尺开行的特快列车的合称,采用 AC380V 国产 25T 车底或进口 BSP 25T 车底,由东风 11G 型内燃机车牵引。 21 | 22 | 硬卧体验让我有些紧张,在火车上看了《PERFECT BLUE》。第一次看今敏的作品,深感其想象力之丰富。尝试入睡时总在半梦半醒间反复。窗外的华北平原在夜色中显得格外辽阔,偶尔有零星灯光掠过。这般景象倒应了“星垂平野阔”的意境,只是当列车以百余公里的时速疾驰时,不足一公里宽的黄河不过十几秒便呼啸而过,难有“月涌大江流”的实感。 23 | 24 | 本想根据手机时间提示看日出,但天刚蒙蒙亮便撑不住补觉了。07:46 抵达北京丰台站。 25 | 26 | ## Day 1 27 | 28 | 行程路线:北京丰台站 -> (地铁 10 号线转地铁 4 号线) -> 颐和园 -> (公交 74 路转地铁 12 号线转公交专 85 路) -> 铁道博物馆(东郊) -> (步行) -> 电影博物馆 -> (公交专 116 路转地铁 15 号线转地铁 5 号线) -> 立水桥地铁站附近住宿。 29 | 30 | 北京地铁的拥挤程度很高,尤其我还选择了早高峰的 10 号线。坐上地铁后,连着几站,都看见外面站台的人上不来车。可能因为我宅在安徽太久,一下子看见地铁上这么多北方面相的人有点不安 desu(单纯觉得不熟悉不适应)。 31 | 32 | 到颐和园地铁站后,本来打算吃顿肯德基早餐,一看是景点餐厅收费标准,一个套餐三十多,遂放弃。便利店买了一瓶拿铁和一瓶能量饮料,进园。不过果然头一天晚上硬卧休息不是很足,走路都轻飘飘的,好几次差点崴了脚。冬季的颐和园虽然没有夏天的绿意,但是也别有一番风味。 33 | 34 | ![summer-palace](atypical-tour-to-peking/20250224-summer-palace.jpeg) 35 | 36 | 中午路过凤凰汇,华润系商场,还是可以的,卫生间很干净。来都来了,尝了一顿小大董的北京烤鸭,鸭皮实在太肥了,一个人吃半只勉强。 37 | 38 | 下午参观两座博物馆: 39 | 40 | ![railway](atypical-tour-to-peking/20250224-railway.png) 41 | 42 | _环形铁路将我们分离,两个景点间嗯走了半个多小时_ 43 | 44 | 铁道博物馆有很多实物火车及介绍,高铁馆好像还在装修。电影博物馆就实在比较普通,罗列了很多电影和剧情梗概,但是对没看过人来说,实在是没什么吸引力,并没有相应电影在电影史上的地位和影响力的介绍。另外,电影博物馆的咖啡/奶茶也卖得很贵,一杯生酪拿铁 45 元,喝不起。 45 | 46 | 不过这次去电影博物馆主要是奔着 IMAX GT 的荧幕来的,看了《哪吒 2》,虽然可能是对电影预期太高了,感觉比较普通。IMAX 介绍片头声画效果震撼,虽然之前在上海 MOViE MOViE 也看过一块二代 IMAX COLA 屏,与此相比也黯然失色。屏幕越大,视场角越大,越能沉浸感受到电影带来的震撼。 47 | 48 | 去酒店的路上愈发感受到北京的点状商业发展,街景虽大气却单调,实非适宜徒步探索之地。挤着晚高峰的 5 号线到了立水桥站,虽然后面只有三站就到终点站了,但是这三站就是传说中的天通苑,所以直到我下车时地铁还是被灌满的。 49 | 50 | 入住百元级廉价酒店,隔音欠佳但总体达标。出门吃了个麦当劳,现在似乎是限时的薯饼全天供应,薯饼太好吃了,我老想吃薯饼了。在商场还看到了合肥品牌詹记,稍感亲切。 51 | 52 | ## Day 2 53 | 54 | 清晨的北京对游客而言略显冷清。早餐又吃了一顿麦当劳,美式加整袋白砂糖。提完神后也快到午餐时间了,去吃了南门涮肉(河边店)。 55 | 56 | 十点半到的南门涮肉,没啥人,要是去上海的网红店怕不是一开门就一堆提前排队的进去了,江浙沪人均不用上班()。麻酱调的很好吃,上面放了点香菜,拌开很香。本身就是标准的涮肉,试着先下了一盘羊尾油,好像是让锅润了一点。牛羊肉份量实在,各下了一盘就吃饱了。 57 | 58 | 之后的规划是转一些周边店。王府井 in88 -> 崇文门搜秀 -> animate -> 去附近商场看了一场《你的颜色》电影,晚餐老乡鸡 -> 北投,再吃一顿庆丰包子。 59 | 60 | 虽然是周五,但各商场人并不很多。我今天去的大部分店都比较普通,像是飞社长这种全国连锁店货感觉还没合肥好。电波宝箱很可以,in88 有一家,北投也有一家。这家店有很多京阿尼相关的 ip,也有很多限定的周边,比如中二病也要谈恋爱的限定原作插画集,冰菓、幸运星、京吹、利兹与青鸟等文件夹和盲抽。凉宫春日相关的从角川书库出的到京阿尼出的都有,第二家店还有个可爱的凉宫春日趴趴(展示用)。 61 | 62 | 在第一家花 15 元买了个魔法少女小圆的小文件夹盲抽,抽到了杏子,可爱。后来把这个小文件夹用来放这次北京之旅的各种门票和影票了。 63 | 64 | 在第二家花 75 元买了个佐佐木的立牌,字面意义上的很大。 65 | 66 | ![sasaki](atypical-tour-to-peking/20250224-sasaki.jpg) 67 | 68 | 搜秀的各种模型手办也值得一提,IP 比较广,除了日本的 ACGN 还有漫威,DC 等。以及还卖书和游戏卡带之类。 69 | 70 | 除此之外今天没逛到什么特别的周边店,所以下午临时决定去看《你的颜色》。看之前去旁边的多乐之日买了个日式芋泥包,好吃。电影本身也推荐看,青春电影。 71 | 72 | 这天回酒店路上看到 5 号线的人流直接玉玉了,遂决定换 430 路公交,虽然多坐了半个小时,但是有座。 73 | 74 | ## Day 3 75 | 76 | Live 日!早上去银座 mall(话说你为什么也叫 ginza)吃了顿吉野家,然后去国贸看了看地标建筑大裤衩,时间就差不多到了。 77 | 78 | > 全国首次邦邦十团 Cover 超大型 Live Only —— 79 | > 这次是不落下邦邦任何一团的满配 live!🎶 80 | 81 | 真的爽到。118 元爽了七个小时,怎么会有这种好事()。这个 LiveHouse 音响效果很好,大家也都肉眼可见的热情。这次 live 是无限制的,所以很多来打地下艺的老哥,弥漫在空气中的 kirakiradokidoki 气息,目眩神迷。 82 | 83 | 第一个上台的是 cover PPP 的南瓜飞行器(据说都是北大✌),经典选曲,最后还让全场一起唱 God Knows...我是唱爽了也 call 爽了,结果这个团结束就有点累了,之后频频跑出去呼吸新鲜空气(。)以及这次演出确实有老哥在现场晕倒了,缺氧大概。 84 | 85 | 上半场最后是 MySalt! cover 的 RAS。RAS 这些曲子简直天生为互动而写的,Unstoppable 开始进入状态,然后 R.I.O.T、Bonfire、Apocalypse、EXPOSE 'Burn out!!!',幻觉来了我草。 86 | 87 | 正好说到蝶团。下半场的蝶意外的是小提琴独奏,[西便门拉弦子的](https://space.bilibili.com/349010135) 是英国皇家音乐学院交响乐团首席。 88 | 89 | Cover MyGO!!!!! 的是 LinGo(今天的所有主唱都很拼命)。体力恢复了一点于是我跑到 LiveHouse 一楼去玩了。影色舞甩棒子,轮符雨也打爽了。结束后喊安可喊出来「僕は…」真的很激动,我一直是 ygfn 粉丝啊。 90 | 91 | 想感叹什么但感叹不出。以往来这种活动往往结束后都会更加的空虚,但这次却不是。只能感叹自己真是幸运。 92 | 93 | 回程的卧铺火车(Z225)睡得很好,早上醒来的时候天已经亮了。 94 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path' 2 | import Terser from '@rollup/plugin-terser' 3 | import Vue from '@vitejs/plugin-vue' 4 | import mdLinkAttrPlugin from 'markdown-it-link-attributes' 5 | import { visualizer } from 'rollup-plugin-visualizer' 6 | import AutoImport from 'unplugin-auto-import/vite' 7 | import IconsResolver from 'unplugin-icons/resolver' 8 | import Icons from 'unplugin-icons/vite' 9 | import { 10 | NaiveUiResolver, 11 | VueUseComponentsResolver, 12 | } from 'unplugin-vue-components/resolvers' 13 | import Components from 'unplugin-vue-components/vite' 14 | import Markdown from 'unplugin-vue-markdown/vite' 15 | import { defineConfig } from 'vite' 16 | import Pages from 'vite-plugin-pages' 17 | 18 | import { VitePWA } from 'vite-plugin-pwa' 19 | import vsharp from 'vite-plugin-vsharp' 20 | import Windicss from 'vite-plugin-windicss' 21 | import BuildPosts from './build/buildPosts' 22 | import PartialEvaluator from './partial-evaluate' 23 | 24 | const markdownWrapperClasses = 'md-blog m-auto text-left' 25 | 26 | export default defineConfig({ 27 | resolve: { 28 | alias: { 29 | '~/': `${resolve(__dirname, 'src')}/`, 30 | }, 31 | }, 32 | plugins: [ 33 | // vue 官方插件,用来解析 sfc 34 | Vue({ 35 | include: [/\.vue$/, /\.md$/], 36 | }), 37 | // markdown 编译插件 38 | Markdown({ 39 | wrapperClasses: markdownWrapperClasses, 40 | markdownItSetup(md) { 41 | md.use(mdLinkAttrPlugin, { 42 | attrs: { 43 | target: '_blank', 44 | rel: 'noopener', 45 | }, 46 | }) 47 | }, 48 | }), 49 | // 自动构建文件 50 | BuildPosts(), 51 | // 文件路由 52 | Pages({ 53 | extensions: ['vue', 'md'], 54 | }), 55 | // windicss 插件 56 | Windicss({ 57 | safelist: markdownWrapperClasses, 58 | }), 59 | // https://icones.netlify.app/ 60 | Icons({ 61 | autoInstall: true, 62 | }), 63 | // 组件自动按需引入 64 | Components({ 65 | dts: resolve(__dirname, './src/types/components.d.ts'), 66 | resolvers: [ 67 | IconsResolver(), 68 | NaiveUiResolver(), 69 | VueUseComponentsResolver(), 70 | ], 71 | }), 72 | // api 自动按需引入 73 | AutoImport({ 74 | dts: './src/types/auto-imports.d.ts', 75 | imports: ['vue', 'vue-router', '@vueuse/core'], 76 | dirs: [ 77 | './src/composables', 78 | ], 79 | }), 80 | // 部分求值插件 81 | PartialEvaluator({ 82 | silent: true, 83 | components: { 84 | Input: { 85 | loading: undefined, 86 | showCount: false, 87 | maxlength: undefined, 88 | pair: false, 89 | type: '\'text\'', 90 | }, 91 | Tag: { 92 | disabled: false, 93 | checkable: false, 94 | closable: false, 95 | }, 96 | Rate: { 97 | allowHalf: true, 98 | clearable: false, 99 | }, 100 | }, 101 | }), 102 | // Terser 103 | Terser(), 104 | // PWA 105 | VitePWA({ 106 | registerType: 'autoUpdate', 107 | workbox: { 108 | globPatterns: ['**/*.{js,css,ico,svg}'], 109 | // https://github.com/vite-pwa/vite-plugin-pwa/issues/120 110 | navigateFallback: null, 111 | }, 112 | includeAssets: ['favicon.ico', 'apple-touch-icon.png'], 113 | manifest: { 114 | name: 'llyのblog', 115 | short_name: 'llyのblog', 116 | description: '我的个人博客,写点想写的', 117 | lang: 'zh-CN', 118 | theme_color: '#ffffff', 119 | icons: [ 120 | { 121 | src: 'pwa-192x192.png', 122 | sizes: '192x192', 123 | type: 'image/png', 124 | }, 125 | { 126 | src: 'pwa-512x512.png', 127 | sizes: '512x512', 128 | type: 'image/png', 129 | }, 130 | ], 131 | }, 132 | }), 133 | // 压缩图片 134 | vsharp({ 135 | excludePublic: [ 136 | 'public/*', 137 | ], 138 | includePublic: [ 139 | 'public/images/*.png', 140 | 'public/images/*.jpg', 141 | ], 142 | }), 143 | // 打包体积分析 144 | visualizer(), 145 | ], 146 | css: { 147 | transformer: 'lightningcss', 148 | }, 149 | build: { 150 | minify: 'terser', 151 | rollupOptions: { 152 | output: { 153 | experimentalMinChunkSize: 10_000, 154 | }, 155 | }, 156 | }, 157 | }) 158 | -------------------------------------------------------------------------------- /posts/top-and-bottom-type.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 一次类型体操 & TypeScript 中的特殊类型 3 | date: 2024-03-13 17:33:33 4 | tags: [类型系统, typescript] 5 | category: web 6 | --- 7 | 8 | 接上篇文章,我们在实现了 APlayer 的 Tree-Shaking 之后,遇到了一个新的问题:我们成功为播放器的某些功能拆分出了对应的插件,通过 `APlayer.use(plugin)` 的方式进行加载。但是有的插件会为 `APlayer` 注入新的方法,我们希望在加载插件后能够正确的提示出这些方法;并且在不加载插件的时候,不会提示出这些方法。即,`use` 操作需要返回一个新的 `APlayer` 类型,这个类型包含了插件注入的方法。 9 | 10 | 本文先介绍一下这个功能的实现,然后再讨论一下 TypeScript 中的 `unknown`, `never`, `void` 和 `any` 等特殊类型。 11 | 12 | 参考: 13 | 14 | - (推荐阅读)简单科普底类型(Bottom Type)、单元类型(Unit Type)以及顶类型(Top Type): 15 | - (官方)TypeScript: Type Compatibility: 16 | 17 | 18 | 19 | ## 功能实现 20 | 21 | 首先,插件是一个接收 `APlayer` 类型的参数的函数,它会在 `APlayer` 上注入一些新的方法。我们可以通过 TypeScript 的泛型来表示这个插件: 22 | 23 | ```typescript 24 | type Plugin

= (player: APlayer) => void; 25 | export const APlayerFixedModePlugin: Plugin; 26 | export const APlayerHlsPlugin: Plugin; 27 | export const addMusicPlugin: Plugin<{ 28 | list: { 29 | add: (audios: Audio[] | Audio) => void; 30 | } 31 | }>; 32 | ``` 33 | 34 | 不难想到 `APlayer` 类型也应该是一个泛型: 35 | 36 | ```typescript 37 | export type APlayer = { 38 | init(options: APlayerOptions): APlayer; 39 | use

(plugin: Plugin

): APlayer; 40 | play(): void; 41 | // ... other methods 42 | } & T 43 | ``` 44 | 45 | 每次 `use` 操作都将插件提供的类型 `P` 与当前 `APlayer` 的泛型参数 `T` 进行合并。通过 `APlayer = {...} & T` 的方式,我们将 `APlayer` 自带方法的类型与插件提供方法的类型进行了合并。 46 | 47 | 这里功能已经实现了,但还遗留了一些问题: 48 | 49 | - 这里泛型的默认值为什么选择了 `unknown`? 50 | - `APlayer` 的泛型参数 `T` 与 `P` 的合并操作是什么意思? 51 | 52 | 后文继续讨论。 53 | 54 | ## TypeScript 中的特殊“空”类型 55 | 56 | ### 概念:AnyScript 57 | 58 | 相信大家都看过那张 JavaScript 的各种 falsy 值相互之间是否 `==` 相等的表格 meme,现在 TypeScript 出现了,“空”类型更多了(x 59 | 60 | 先明确一些重要的概念: 61 | 62 | - 子类型几乎可以看成是一种 `assignable to` 的关系,即 `A` 是 `B` 的子类型几乎等价任何需要 `B` 类型值的地方都可以使用 `A` 类型的值。 63 | - `any` 类型是特例,可以赋值给任何类型(除 `never`)或者被任何类型赋值。相当于 TypeScript 开的一个后门,后文不再考虑。 64 | 65 | 嘛,毕竟赋值/隐式类型转换就是我们最常见的操作。在不考虑 `any` 之后,子类型关系就形成了一个链条,如果 `A` 是 `B` 的子类型,`B` 是 `C` 的子类型,那么 `A` 也是 `C` 的子类型。这种关系叫做偏序关系。 66 | 67 | ### 类型即集合 68 | 69 | 我们可以进一步形式化这一偏序关系: 70 | 71 | - 考虑每个类型都对应一个由它的所有可取值组成的集合,那么 `A` 是 `B` 的子类型,就意味着 `A` 的集合是 `B` 的集合的子集。 72 | - 这等价于 `assignable to` 关系成立。 73 | - 类型的 Intersection 和 Union 操作,就是对应可取值集合的交和并操作。 74 | 75 | 听起来还是有点抽象,我们举几个例子: 76 | 77 | ```typescript 78 | // 第一种情况:B 是 A 的子类型 79 | type A = { a: number }; 80 | type B = { a: number, b: string }; 81 | 82 | // 第二种情况:A 是 B 的子类型 83 | type A = 'a'; 84 | type B = 'a' | 'b'; 85 | ``` 86 | 87 | 好像看起来更抽象了:为什么同样是 `B` 的范围看起来比 `A` 更广,但是一种情况下 `B` 是 `A` 的子类型,另一种情况下 `A` 是 `B` 的子类型呢? 88 | 89 | 一方面可以用最基本的 `assignable to` 概念判断。第一种情况中,任何需要 `A` 类型的地方都可以用 `B` 类型的值来代替,因为 `A` 要求具备 `a: number` 属性,而 `B` 满足这一要求。第二种情况同理判断任何需要 `B` 类型的地方都可以用 `A` 类型的值来代替。 90 | 91 | 另一方面,我们也可以用集合的观点来看待问题。第二种情况此时就变得非常显然了,`type B = A | 'b'`,这是集合的并运算,所以 `A` 是 `B` 的子集(子类型)。问题是怎么理解第一种情况中 `B` 是 `A` 的子集(子类型)呢?我们重新表示第一种情况的代码: 92 | 93 | ```typescript 94 | type A = { 95 | a: number; 96 | b?: any; 97 | c?: any; 98 | [any_string_here]?: any 99 | ... 100 | } 101 | 102 | type B = { 103 | a: number; 104 | b: string; 105 | c?: any; 106 | [any_string_here]?: any 107 | ... 108 | } 109 | ``` 110 | 111 | 注意 TypeScript/JavaScript 的结构体本身是一个对象,所以其实隐含了所有其他未被注明的属性都是可选的。这么一看,`B` 可取值集合显然是 `A` 的子集了,因为 `A` 没有对 `b` 属性做出要求:`A` 的可取值相当于 `[number, any, any, ...]`,而 `B` 的可取值相当于 `[number, string, any, ...]`。可以想象,不同结构类型的 Intersection 运算就是将所有注明的属性取交集,所得结果也会是任意原来类型的子类型。到这里,上文遗留的第二个问题已经解决了。 112 | 113 | ### unknown, never, void 114 | 115 | 理解了类型和集合的对应之后,我们终于可以解决遗留的第一个问题,开始讨论 TypeScript 中的 `unknown` 和 `never` 了。 116 | 117 | - 空集是所有集合的子集。对应 `never` 是所有类型的子类型。因此 `never` 又叫底类型。 118 | - 任何类型都是 `unknown` 的子类型,因此 `unknown` 又叫顶类型。 119 | 120 | `unknown` 类型相当于没有任何约束,任何值都是 `unknown` 类型。所以,在我们对插件一无所知的时候,可以使用 `APlayer` 类型。随着增加新的插件 `Plugin

`,我们会有 `APlayer = APlayer

`(`unknown & P = P` 是顶类型,或者说全集具有的性质)。 121 | 122 | 从集合的角度,我们还可以发现,空集的大小为 0,所以 `never` 没有实例。因此,它可以表示一个函数永远不会返回值(死循环或异常),或者一个变量永远不会被赋值。 123 | 124 | ```typescript 125 | let bar: never = (() => { 126 | throw new Error('Throw my hands in the air like I just dont care'); 127 | })(); 128 | ``` 129 | 130 | 一个函数除了正常情况(有特定返回值)和永远不返回(`never`)之外,还可能我们并不关心它的返回值。通常这可以通过什么都不返回实现(其实是返回 `undefined`)。 131 | 132 | ```typescript 133 | type f = () => void; 134 | let a: f = () => {}; 135 | let b: f = () => 1; 136 | ``` 137 | 138 | 注意不关心(不使用)返回值不等于没有返回值。这就是 `void` 类型的特殊性,在一个标注为需要返回值 `void` 的函数的地方,我们使用返回值为任意值的函数都是可以的(如上面的 `b`)。 139 | -------------------------------------------------------------------------------- /posts/digital-lab.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 数电计组实验 Vscode 配置指南 3 | date: 2022-03-19 21:18:29 4 | tags: [笔记] 5 | category: 笔记 6 | tocbot: true 7 | --- 8 | 9 | 本篇博客将带来优雅的 Vscode 编写数字电路实验 / 计算机组成原理实验的 Verilog 一键式配置方案,让你编写代码全程远离 Vivado(~~新建工程还是要见一面的~~) 10 | 11 | 主要还是介绍一些名词和工具,读者看上去了哪些可以自己挑 12 | 13 | ![image-20220319212215367](./digital-lab/image-20220319212215367.png) 14 | 15 | 16 | 17 | ## Vscode 插件 18 | 19 | 首先可能需要连接远程的 Vlab 服务器,这个时候需要使用 Vscode-remote 插件 20 | 21 | ![image-20220319212513260](./digital-lab/image-20220319212513260.png) 22 | 23 | 这个插件主要作用就是能在本地通过 SSH 连接操作 Vlab 虚拟机 24 | 25 | 具体配置过程不再赘述,配置完之后可以很方便的浏览远程文件 26 | 27 | ![image-20220319212941961](./digital-lab/image-20220319212941961.png) 28 | 29 | 这个时候已经能愉快的在 Vscode 上写 Verilog 了,但是仅仅能写肯定不够,我们还需要一个 linter(检查语法) 还有一个 formatter(代码格式化) 30 | 31 | 这里推荐一个插件:Digital IDE 32 | 33 | ![image-20220319212315831](./digital-lab/image-20220319212315831.png) 34 | 35 | 插件来自 [ysy-phoenix](https://github.com/ysy-phoenix) 的推荐,在此感谢 36 | 37 | 配置完之后应该已经默认有代码格式化和端口的各种提示信息了,上图的代码就是这个插件格式后的成果 38 | 39 | 但 linter 还需要额外配置,这里我们打开插件的设置: 40 | 41 | ![image-20220319213207916](./digital-lab/image-20220319213207916.png) 42 | 43 | 个人感觉 Verilator 更好用,当然 Vlab 虚拟机上默认是没有自带的,不过 Ubuntu 通过包管理器安装还是很方便的: 44 | 45 | ```bash 46 | apt update // 这一步为了确保软件包是最新的 47 | apt install verilator // 安装 48 | ``` 49 | 50 | 两下就好了,之后写代码时一保存文件就可以自动帮你检查语法错误了: 51 | 52 | ![image-20220319213412728](./digital-lab/image-20220319213412728.png) 53 | 54 | (这里报错会显示在一行上是因为装了另一个插件 Error Lens,很美观,亲测好用) 55 | 56 | ## 干掉 Vivado 之仿真篇 57 | 58 | 仿真需要看波形,这个时候一个推荐是 `gtkwave`,Ubuntu 下同样通过包管理器安装即可 59 | 60 | 因为这个不能在没有图形界面的 Vlab 上运行,所以下文给出的仿真推荐都是配在本地环境的: 61 | 62 | ### Icarus Verilog 63 | 64 | 大名鼎鼎的一个仿真工具,简称 iverilog, 如果你用 Ubuntu 的包管理器装它还会自动帮你装上 gtkwave(~~捆绑消费~~) 65 | 66 | Digital IDE 自带了对于 Icarus Verilog 的快速仿真支持,见 [它的文档](https://zhuanlan.zhihu.com/p/365805011) 67 | 68 | ### Verilator 69 | 70 | 这里还是要安利 Verilator,因为用它做仿真可以 **不写 Verilog** ,Verilator 会生成高层次的 C++ 代码模拟模块的行为,然后你只需要用 C++ 编写 top 模块进行测试就行了 71 | 72 | 很大的好处在于 C++ 作为高级语言很明显比 Verilog 的 testbench 写起来灵活 73 | 74 | Ubuntu 包管理器装的版本比较旧,如果想要最新版可以自己下载它的 GitHub 仓库按照说明编译,不过这样就麻烦一点了 75 | 76 | 具体使用方式可以参考 [这篇文章](http://www.sunnychen.top/2019/07/25/%E8%B7%A8%E8%AF%AD%E8%A8%80%E7%9A%84Verilator%E4%BB%BF%E7%9C%9F%EF%BC%9A%E4%BD%BF%E7%94%A8%E8%BF%9B%E7%A8%8B%E9%97%B4%E9%80%9A%E4%BF%A1/) ,~~对着复制粘贴就行~~ 77 | 78 | 如果你发现 Vscode 找不到头文件 `verilated.h` ,那就找到这个设置(直接在 Vscode 的设置里搜索即可),路径添加以下两个中的一个(根据你自己的情况而定,自己看哪个目录能进,如果是包管理器装的应该是下面那个路径) 79 | 80 | ![image-20220319214751930](./digital-lab/image-20220319214751930.png) 81 | 82 | 然后就可以愉悦的编写 C++ 代码仿真了,以下是一个对 ALU 模块 100% 覆盖率的测试示例: 83 | 84 | ![image-20220319215151766](./digital-lab/image-20220319215151766.png) 85 | 86 | 根据上面那篇链接里教程的步骤,即可判断仿真结果是否正确,并生成波形 87 | 88 | ## 干掉 Vivado 之比特流 89 | 90 | 写完代码我们还需要 Vivado 这个~~工具人~~帮我们生成比特流 91 | 92 | 但每次打开 VNC 操作 Vivado 显然太过笨重,这个时候我们可以利用 Vivado 自带的 TCL 命令行工具来构建项目 93 | 94 | 这里直接贴一个 Vlab 能用的脚本(如果在本地跑可能需要一些修改)[作者链接](https://github.com/WuTianming) 95 | 96 | ```bash 97 | #!/bin/bash 98 | 99 | echo "Initiating project build ..." 100 | 101 | ProjName=$(find . -type f -iname "*.xpr") 102 | if [ -z "$ProjName" ]; then 103 | echo "xpr file not found. Exiting" 104 | exit 1 105 | fi 106 | echo "Found project file ${ProjName}." 107 | 108 | echo "Creating build script ..." 109 | cat - > automation_genbitstream.tcl << EOF 110 | set_param general.maxThreads 2 111 | open_project ${ProjName} 112 | 113 | reset_run synth_1 114 | launch_run synth_1 115 | wait_on_run synth_1 116 | open_run synth_1 117 | # report_timing_summary 118 | launch_run -to_step write_bitstream impl_1 119 | wait_on_run impl_1 120 | # open_run impl_1 121 | # report_timing_summary 122 | # report_utilization > utilization.txt 123 | 124 | quit 125 | EOF 126 | 127 | cat - > wrapper.tcl << EOF 128 | if {[catch {source automation_genbitstream.tcl} errorstring]} { 129 | puts "Error - $errorstring" 130 | exit 1 131 | } 132 | quit 133 | EOF 134 | 135 | cat - > build_remote.sh << EOF 136 | #!/bin/bash 137 | 138 | # source /extra/vivado2016/Vivado/2016.3/settings64.sh 139 | source /opt/vlab/path.sh 140 | 141 | if ! time vivado2019 -mode tcl -source wrapper.tcl; then 142 | grep --color=always "ERROR" vivado.log 143 | fi 144 | rm -f *.log *.jou 145 | printf "\a" 146 | EOF 147 | 148 | echo "Running job ..." 149 | bash build_remote.sh 150 | echo "Build done." 151 | 152 | rm automation_genbitstream.tcl wrapper.tcl build_remote.sh 153 | ``` 154 | 155 | 放在和 xpr 文件同一个目录(也就是项目目录下即可) 156 | 157 | 怎么运行脚本这里不再赘述 158 | 159 | ![image-20220319220158821](./digital-lab/image-20220319220158821.png) 160 | 161 | 可以很漂亮的在命令行里得到结果并看到各种 report 162 | 163 | 具体设置可以自己看脚本被注释掉的一些部分 164 | 165 | 至此我们成功构建了一套还算趁手的 Verilog 开发工具链 166 | -------------------------------------------------------------------------------- /posts/github-actions-ci.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GitHub Actions 持续集成体验 3 | date: 2022-08-23 19:10:20 4 | tags: [笔记] 5 | category: web 6 | --- 7 | 8 | 咕了快小半年,今天更新一期关于 GitHub Actions 在项目部署测试中的应用 9 | 10 | ## 介绍 11 | 12 | 官方介绍: 13 | 14 | > GitHub Actions 是一个持续集成和持续交付 (CI/CD) 平台,可用于自动执行构建、测试和部署管道。您可以创建工作流程来构建和测试存储库的每个拉取请求,或将合并的拉取请求部署到生产环境。 15 | GitHub Actions 不仅仅是 DevOps,还允许您在存储库中发生其他事件时运行工作流程。例如,您可以运行工作流程,以便在有人在您的存储库中创建新问题时自动添加相应的标签。 16 | GitHub 提供 Linux、Windows 和 macOS 虚拟机来运行工作流程,或者您可以在自己的数据中心或云基础架构中托管自己的自托管运行器。 17 | 18 | 19 | 20 | 概括一下其实就是 GitHub 给你送了台虚拟机的免费使用权,你可以利用它在项目有新 push, pull request 时或者定时执行某些构建,测试,部署的任务 21 | 22 | - 构建:如果项目需要构建出 release 版本,可以使用它自动发布 23 | - 测试:运行测试脚本,监测项目的可用性及正确性 24 | - 部署:例如与 GitHub Pages 配合使用,可以自动将静态网页部署到 GitHub Pages 上 25 | 26 | ## 构建与部署 27 | 28 | 首先介绍下 GitHub Actions 的 YAML 格式,需要包含: 29 | 30 | - 触发事件,决定什么时候运行虚拟机,可选 push/pull request/手动运行等 31 | - 运行流程,决定具体虚拟机内执行的程序 32 | 33 | 一个项目可能需要多个 actions,每个 actions 都是 `.github/workflows` 下的一个文件,触发事件和运行流程都彼此独立 34 | 35 | ```yaml 36 | name: build 37 | 38 | on: 39 | push: 40 | branches: 41 | - posts 42 | 43 | jobs: 44 | build: 45 | runs-on: ubuntu-latest 46 | 47 | steps: 48 | - uses: pnpm/action-setup@v2.2.2 49 | with: 50 | version: latest 51 | 52 | - uses: actions/checkout@v2 53 | 54 | - name: Set up Python 3.8 55 | uses: actions/setup-python@v2 56 | with: 57 | python-version: 3.8 58 | 59 | - name: Run build script 60 | run: | 61 | bash build.sh 62 | 63 | - name: Push 64 | uses: s0/git-publish-subdir-action@develop 65 | env: 66 | REPO: self 67 | BRANCH: main 68 | FOLDER: Q-Blog/dist 69 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 70 | 71 | ``` 72 | 73 | 例如以上是一个 workflow 文件,该 actions 的功能是在仓库内的博客 md 文件更新后,自动构建生成静态网页并 push 到 main 分支(随后这一 push 会由 GitHub Pages 负责自动更新静态网页) 74 | 75 | - 触发事件:posts 分支的 push 请求 76 | - 运行流程:一个 actions 可以由几个 job 组成,这些 job 可以根据需求并行或者串行,这里只有一个 job 77 | 78 | 具体来说,一个 job 由多个 step 串行组成,以下具体分析 79 | 80 | ```yaml 81 | - uses: pnpm/action-setup@v2.2.2 82 | with: 83 | version: latest 84 | ``` 85 | 86 | 通过 uses, 可以方便地使用别人编写的用于实现特定功能的 actions 87 | 88 | with 相当于传入需要的参数 89 | 90 | 别人的 actions 可以在 GitHub 提供的 [marketplace](https://github.com/marketplace?type=actions) 找到 91 | 92 | 例如这一 step 是配置 node 和 pnpm 环境 93 | 94 | 同理 95 | 96 | ```yaml 97 | - uses: actions/checkout@v2 98 | 99 | - name: Set up Python 3.8 100 | uses: actions/setup-python@v2 101 | with: 102 | python-version: 3.8 103 | ``` 104 | 105 | 分别使得 actions 可以访问本仓库文件以及设置起 python 和 pip 的环境 106 | 107 | 之后的 steps 则是构建出静态文件并 push 到 main 分支 108 | 109 | ## 测试 110 | 111 | GitHub Actions 的另一大常见用途便是用于测试,我们经常能看见别人的开源项目会有一个 ![test passing](https://github.com/liuly0322/l-plugin/actions/workflows/test.yml/badge.svg?branch=main) 的标识,甚至还有测试覆盖率,这都可以通过 GitHub Actions 实现,例如上面的图标表示的就是某个 workflow 上次运行的测试是否通过 112 | 113 | 例如这个暑假摸了一个 QQ 机器人的插件 (js 写的),就尝试配合 JS 的 mocha 测试框架玩了下 GitHub Actions 用于 push 后自动测试 114 | 115 | 只需要将测试脚本作为一个 step 执行即可: 116 | 117 | ```yaml 118 | - name: run test script 119 | run: docker exec yunzai-bot sh -c "cd plugins/l-plugin/ && pnpm run test" 120 | ``` 121 | 122 | mocha 框架可以指定测试样例(可以嵌套),并最终生成报告 123 | 124 | ```js 125 | describe('骰子', function () { 126 | describe('#r', function () { 127 | it('应该返回 114514 和 1919810 之间的一个随机数', async function () { 128 | const res = await command.run('r 114514 1919810') 129 | const num = Number(res.pop().split(':').pop()) 130 | assert(Number.isInteger(num) && num >= 114514 && num <= 1919810) 131 | }) 132 | }) 133 | describe('#roll', function () { 134 | it('应该返回 a 或 b', async function () { 135 | const res = await command.run('roll a b') 136 | const choice = res.pop().pop().split(':').pop() 137 | assert(choice === 'a' || choice == 'b') 138 | }) 139 | }) 140 | }) 141 | ``` 142 | 143 | 通过 assert 断言来具体测试每个样例,通过全部样例就会这样显示 144 | 145 | ```plaintext 146 | 塔罗牌 147 | ✔ 应该返回一条牌面信息和对应图片 (232ms) 148 | 149 | 每日一题 150 | ✔ 应该返回一张图片和对应 url (3909ms) 151 | 152 | 求签 153 | ✔ 应该返回一条或两条签文消息 (72ms) 154 | 155 | 骰子 156 | #r 157 | ✔ 应该返回 114514 和 1919810 之间的一个随机数 (51ms) 158 | #roll 159 | ✔ 应该返回 a 或 b (47ms) 160 | 161 | 吃什么 162 | #今天吃什么 163 | ✔ 应该返回随机五个食物 (49ms) 164 | #咱今天吃什么 165 | ✔ 应该返回加入的特色菜 (105ms) 166 | 167 | Markdown 168 | ✔ 应该返回一张 Markdown 图片 (145ms) 169 | 170 | Tex 171 | ✔ 应该返回一张 Tex 图片 (967ms) 172 | 173 | 174 | 9 passing (6s) 175 | ``` 176 | 177 | 非常方便(~~而且挺好看的~~ 178 | 179 | 可以在 Actions 界面选择 workflow 后再选择获取对应的图标 URL,实时显示该 workflow 的通过状态,再贴到自己的 `README.md` 里就大功告成了 -------------------------------------------------------------------------------- /posts/bangumi_anime_list.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Anime List! 3 | date: 2024-02-23 22:43:15 4 | tags: [杂谈, anime] 5 | category: web 6 | --- 7 | 8 | 想做 Anime List 很久了,之前也考虑过 Notion 的方案,但是本人平常也不用 Notion,不想为了这个事情多用一个平台。最近发现自己使用的 [bangumi](https://bgm.tv) 有 API,于是就想到了用它来做 Anime List。 9 | 10 | 11 | 12 | 目前在博客导航栏选择动画即可进入 Anime List 页面。 13 | 14 | ![Anime List](bangumi_anime_list/my_anime_list.png) 15 | 16 | 编码过程得到了 windicss 这一原子 CSS 框架和 Copilot 的大力帮助。 17 | 18 | ## Copilot 19 | 20 | 你可以直接往代码里粘贴 [bangumi 接口](https://bangumi.github.io/api/) 的返回数据示例,然后敲一个 `interface Anime`,后面就是 Copilot 发挥了。 21 | 22 | ```typescript 23 | interface Anime { 24 | updated_at: string 25 | comment: string 26 | tags: { name: string; count: number }[] 27 | subject: { 28 | date: string 29 | images: { 30 | small: string 31 | grid: string 32 | large: string 33 | medium: string 34 | common: string 35 | } 36 | name: string 37 | name_cn: string 38 | short_summary: string 39 | tags: { name: string; count: number }[] 40 | score: number 41 | type: number 42 | id: number 43 | eps: number 44 | volumes: number 45 | collection_total: number 46 | rank: number 47 | } 48 | subject_id: number 49 | vol_status: number 50 | ep_status: number 51 | subject_type: number 52 | type: number 53 | rate: number 54 | private: boolean 55 | } 56 | ``` 57 | 58 | ## windicss 59 | 60 | ```html 61 | 67 |

68 | 73 |
74 |

75 | {{ anime.subject.name_cn || anime.subject.name }} 76 |

77 |

78 | {{ anime.subject.short_summary }}…… 79 |

80 |
81 |
82 |
83 |
84 |

85 | {{ anime.comment }} 86 |

87 |
88 | 89 | 90 | {{ timeToDate(anime.updated_at) }} 91 | 92 |
93 |
94 | 95 | ``` 96 | 97 | 写个样式非常方便,而且也可以 Copilot 生成类名( 98 | 99 | ## 分页 100 | 101 | bangumi 的这个 API 是分页的,出于性能考虑可以写一个滚动加载。 102 | 103 | ```typescript 104 | const animeList = ref([] as Anime[]) 105 | 106 | const loading = ref(true) 107 | const pageSize = 12 108 | let page = 0 109 | 110 | const fetchAnimeList = async () => { 111 | if (!loading.value) return 112 | const offset = page * pageSize 113 | try { 114 | const res = await fetch( 115 | `https://api.bgm.tv/v0/users/undef_baka/collections?subject_type=2&type=2&limit=${pageSize}&offset=${offset}`, 116 | ) 117 | if (!res.ok) { 118 | throw new Error('Network response was not ok') 119 | } 120 | const data = await res.json() 121 | const totalSize = data.total 122 | animeList.value = animeList.value.concat(data.data) 123 | if (offset + data.data.length >= totalSize) { 124 | loading.value = false 125 | } 126 | page++ 127 | } catch (error) { 128 | loading.value = false 129 | } 130 | } 131 | 132 | // 节流 133 | let ticking = false 134 | 135 | async function updateOnScroll(event: Event) { 136 | if (ticking) return 137 | ticking = true 138 | const element = event.target as HTMLElement 139 | // 这里在处理兼容问题,正常情况元素滚动用元素,页面滚动用 document 就行 140 | const isBottom = document.documentElement.scrollTop ? document.documentElement.scrollHeight - document.documentElement.scrollTop <= document.documentElement.clientHeight + 100 : 141 | element.scrollHeight - element.scrollTop <= element.clientHeight + 100 142 | if (isBottom) { 143 | await fetchAnimeList() 144 | } 145 | ticking = false 146 | } 147 | 148 | onMounted(async () => { 149 | await fetchAnimeList() 150 | nextTick(() => { 151 | const element = document.querySelector('.n-layout-content .n-scrollbar-container') 152 | element?.addEventListener('scroll', updateOnScroll) 153 | document.addEventListener('scroll', updateOnScroll) 154 | }) 155 | }) 156 | 157 | onUnmounted(() => { 158 | const element = document.querySelector('.n-layout-content .n-scrollbar-container') 159 | element?.removeEventListener('scroll', updateOnScroll) 160 | document.removeEventListener('scroll', updateOnScroll) 161 | }) 162 | ``` -------------------------------------------------------------------------------- /partial-evaluate/replacement.ts: -------------------------------------------------------------------------------- 1 | import type { NodePath } from '@babel/traverse' 2 | import type { MemberExpression, ObjectProperty, RestElement, VariableDeclarator } from '@babel/types' 3 | import babel from '@babel/core' 4 | import t from '@babel/types' 5 | import logger from './log' 6 | 7 | function isValidMemberExpression(node: NodePath, constObjectName: string) { 8 | if (constObjectName === 'this') 9 | return node.get('object').isThisExpression() 10 | return node.get('object').isIdentifier({ name: constObjectName }) 11 | } 12 | 13 | function isValidDestructure(node: NodePath, constObjectName: string) { 14 | if (constObjectName === 'this') 15 | return node.get('init').isThisExpression() 16 | return node.get('init').isIdentifier({ name: constObjectName }) 17 | } 18 | 19 | /** 20 | * Replace `props.prop` with given const 21 | * @param node Root node of function body needs to be optimized 22 | * @param constObjectName Name of the object that contains the constants 23 | * @param consts Map of constants 24 | */ 25 | export function replaceMemberExpression(node: NodePath, constObjectName: string, consts: Record) { 26 | const memberExpressions: NodePath[] = [] 27 | const potentialOptimizeProps: Set = new Set() 28 | node.traverse({ 29 | MemberExpression(nodePath) { 30 | if (isValidMemberExpression(nodePath, constObjectName)) { 31 | memberExpressions.push(nodePath) 32 | potentialOptimizeProps.add(nodePath.toString().split('.')[1]) 33 | } 34 | }, 35 | }) 36 | memberExpressions.forEach((nodePath) => { 37 | const propNode = nodePath.get('property').node 38 | if (propNode.type !== 'Identifier') 39 | return 40 | const propName = propNode.name 41 | if (propName in consts) { 42 | nodePath.replaceWithSourceString(`${consts[propName]}`) 43 | potentialOptimizeProps.delete(propName) 44 | } 45 | }) 46 | logger.log(`Potential optimize member expression: ${Array.from(potentialOptimizeProps).join(', ')}`) 47 | } 48 | 49 | function replaceDestructureWithOneConst( 50 | root: NodePath, 51 | property: NodePath, 52 | consts: Record, 53 | potentialOptimizeProps: Set, 54 | ) { 55 | if (!property.isObjectProperty()) 56 | return 57 | const key = property.get('key') 58 | if (!key.isIdentifier()) 59 | return 60 | const propName = key.node.name 61 | if (propName in consts) { 62 | const value = babel.template.expression.ast(`${consts[propName]}`) 63 | const newVariableDeclarator = t.variableDeclarator(t.identifier(propName), value) 64 | root.replaceWith(newVariableDeclarator) 65 | } 66 | else { 67 | potentialOptimizeProps.add(propName) 68 | } 69 | } 70 | 71 | function replaceDestructureWithMultiDeclarations( 72 | root: NodePath, 73 | properties: NodePath[], 74 | consts: Record, 75 | potentialOptimizeProps: Set, 76 | ) { 77 | const deleteProperties: NodePath[] = [] 78 | properties.forEach((property) => { 79 | if (property.isObjectProperty()) { 80 | const key = property.get('key') 81 | if (key.isIdentifier()) { 82 | const propName = key.node.name 83 | if (propName in consts) 84 | deleteProperties.push(property) 85 | else 86 | potentialOptimizeProps.add(propName) 87 | } 88 | } 89 | }) 90 | const newVariableDeclarators: VariableDeclarator[] = deleteProperties.map((property) => { 91 | const key = property.get('key') 92 | if (!key.isIdentifier()) 93 | throw new Error('Unexpected property key type') 94 | const propName = key.node.name 95 | const value = babel.template.expression.ast(`${consts[propName]}`) 96 | return t.variableDeclarator(t.identifier(propName), value) 97 | }) 98 | deleteProperties.forEach(property => property.remove()) 99 | root.insertAfter(newVariableDeclarators) 100 | } 101 | 102 | /** 103 | * replace `const { prop } = props` with given const 104 | * @param node Root node of function body needs to be optimized 105 | * @param constObjectName Name of the object that contains the constants 106 | * @param consts Map of constants 107 | */ 108 | export function replaceDestructure(node: NodePath, constObjectName: string, consts: Record) { 109 | const potentialOptimizeProps: Set = new Set() 110 | node.traverse({ 111 | VariableDeclarator(nodePath) { 112 | if (!isValidDestructure(nodePath, constObjectName)) 113 | return 114 | const id = nodePath.get('id') 115 | if (!id.isObjectPattern()) 116 | return 117 | const properties = id.get('properties') 118 | if (properties.length === 1) 119 | replaceDestructureWithOneConst(nodePath, properties[0], consts, potentialOptimizeProps) 120 | else 121 | replaceDestructureWithMultiDeclarations(nodePath, properties, consts, potentialOptimizeProps) 122 | }, 123 | }) 124 | logger.log(`Potential optimize destructure: ${Array.from(potentialOptimizeProps).join(', ')}`) 125 | } 126 | -------------------------------------------------------------------------------- /posts/re-deriv.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 正则表达式求导 3 | date: 2025-01-24 13:08:18 4 | tags: [笔记, 算法] 5 | category: 笔记 6 | --- 7 | 8 | 是的,正则表达式也可以有求导!这一技巧在生成正则表达式(或拓展正则表达式)对应的 DFA/scanner 时非常有用。本文作为学习笔记,记录了正则表达式求导的定义和作用。 9 | 10 | 参考: 11 | 12 | - [Regular-expression derivatives reexamined](https://www.khoury.northeastern.edu/home/turon/re-deriv.pdf), Scott Owens, John Reppy, Aaron Turon, 2009 13 | 14 | 15 | 16 | ## 正则表达式 17 | 18 | 在编程上,正则表达式首先是一个 `(s: string) -> bool` 的函数,即判断字符串是否符合某种模式。我们把这件事形式化一下: 19 | 20 | - 字母表 $\Sigma$ 21 | - 字母表上的字符 $a, b, c, \ldots$ 可以构成字符串,如 $abc, bca, \ldots$,字符串记作 $u, v, w, \ldots$ 22 | - 特别地,空字符串记作 $\varepsilon$,长度为 0 23 | - $\Sigma$ 上所有长度有限的字符串构成的集合 $\Sigma^*$ 24 | - 一个 *语言* 是一个 $\Sigma^*$ 的子集,即 $L \subseteq \Sigma^*$ 25 | 26 | 那么正则表达式的函数签名转换成数学语言: 27 | 28 | - 给定一个正则表达式 $r$,它所有 *接受*(即返回 `true`,表示匹配)的字符串构成的语言记作 $L(r)$ 29 | - $L(r) = \{ s \in \Sigma^* \mid r(s) = \text{true} \}$ 30 | 31 | 上面的 $L(r)$ 定义对所有语言都适用,也就是从数学上形式化了 `(s: string) -> bool` 这件事。那么正则语言又特殊在哪里呢? 32 | 33 | 这就需要了解正则表达式的构造过程。因为一个正则表达式对应一个正则语言,对应的正则语言也被构造。本文采用一种拓展正则表达式(增加了布尔运算)的定义,读者在下文中只需要将正则表达式理解成一般的代数表达式即可。 34 | 35 | $$ 36 | \begin{align*} 37 | L(\emptyset) &= \emptyset \\ 38 | L(\varepsilon) &= \{ \varepsilon \} \\ 39 | L(a) &= \{ a \} \\ 40 | L(r_1 \cdot r_2) &= \{ u\cdot v \mid u \in L(r_1), v \in L(r_2)\} \\ 41 | L(r^*) &= \{ u_1 \cdot u_2 \cdot \ldots \cdot u_n \mid n \ge 0, u_i \in L(r)\} \\ 42 | L(r_1 + r_2) &= L(r_1) \cup L(r_2) \\ 43 | L(r_1\ \&\ r_2) &= L(r_1) \cap L(r_2) \\ 44 | L(\lnot r_1) &= \Sigma^* - L(r_1) 45 | \end{align*} 46 | $$ 47 | 48 | 等号左边是正则表达式,右边是对应的正则语言。$r_1 \cdot r_2$ 是字符串拼接;等号左侧的其他运算依次称为闭包、并、交、补;等号右侧的运算就是集合运算。 49 | 50 | 可以发现,正则语言必然从 $\emptyset, \{\varepsilon\}, \{a\}$ 出发,通过(集合的)拼接、闭包、并、交、补等操作递归构造出来。 51 | 52 | ## 语言的导数 53 | 54 | 正则表达式求导的核心思想是:对于一个正则表达式 $r$,我们可以定义一个 *导数*,表示 $r$ 在字符(串)$u$ 上的变化。这个变化是指,如果 $r$ 匹配的字符串中,第一个字符(串)是 $u$,那么求导后匹配的字符串是去掉第一个字符(串)后的剩余部分。这里的求导实际就是状态转移,我们希望通过求导得到所有可能的状态,从而构造出 DFA。 55 | 56 | 换言之,求导操作相当于尝试“消耗”一个字符后,求剩余部分需要满足的正则表达式。 57 | 58 | 为了形式化这件事,我们先定义语言的导数:对于语言 $L$ 和字符串 $u \in \Sigma^*$: 59 | 60 | $$\partial_u L = \{ v \mid u\cdot v \in L \}$$ 61 | 62 | 这个定义的意思是,$L$ 中所有以 $u$ 开头的字符串,去除 $u$ 后剩下的部分构成的集合。这个定义是可以对任意语言成立的。 63 | 64 | 举例说明,$S=\{apple,banana,apricot,cherry\}$,那么 $\partial_{ap} S = \{ple,ricot\}$。 65 | 66 | 我们让 $\partial_u$ 可以作用在正则表达式 $r$ 上,即: 67 | 68 | $$ 69 | L(\partial_u r) = \partial_u L(r) 70 | $$ 71 | 72 | (这里其实是个同构,但我们不深究这个问题,只要知道正则表达式的导数由语言的导数定义即可) 73 | 74 | 那么根据这个定义,我们来推导下正则表达式的求导吧。 75 | 76 | ## 正则表达式的导数 77 | 78 | 首先是对某个字符求导。这里空集表示不接受任何字符串的正则表达式(和接受空字符串的 $\varepsilon$ 不同),对应正则语言的 $\emptyset$。 79 | 80 | (所以对正则表达式 $r, s$,如果 $r=\emptyset$,那么 $r \cdot s = \emptyset$,其他运算同理) 81 | 82 | 另外引入记号 83 | 84 | $$ 85 | \nu (r)= \begin{cases} 86 | \varepsilon & if\ \varepsilon \in L(r) \\ 87 | \emptyset & otherwise.\end{cases} 88 | $$ 89 | 90 | 例如: 91 | 92 | $$ 93 | \begin{align*} 94 | \nu(a) &= \emptyset \\ 95 | \nu(a^*) &= \varepsilon \\ 96 | \end{align*} 97 | $$ 98 | 99 | 不难发现 $\nu$ 可以简单递归定义。那么可以验证正则表达式的导数(对字符): 100 | 101 | $$ 102 | \begin{align*} 103 | \partial_a \varepsilon &= \emptyset \\ 104 | \partial_a a &= \varepsilon \\ 105 | \partial_a b &= \emptyset, \text{ if } a \ne b \\ 106 | \partial_a \emptyset &= \emptyset \\ 107 | \partial_a (r_1 \cdot r_2) &= \partial_a r_1 \cdot r_2 + \nu(r_1) \cdot \partial_a r_2 \\ 108 | \partial_a (r^*) &= \partial_a r \cdot r^* \\ 109 | \partial_a (r_1 + r_2) &= \partial_a r_1 + \partial_a r_2 \\ 110 | \partial_a (r_1\ \&\ r_2) &= \partial_a r_1\ \&\ \partial_a r_2\\ 111 | \partial_a (\lnot r) &= \lnot (\partial_a r) 112 | \end{align*} 113 | $$ 114 | 115 | 对字符串求导是递归定义的: 116 | 117 | $$ 118 | \begin{align*} 119 | \partial_{\varepsilon} r &= r \\ 120 | \partial_{ua} r &= \partial_a (\partial_u r) 121 | \end{align*} 122 | $$ 123 | 124 | ## DFA 构造 125 | 126 | ### 朴素算法 127 | 128 | 考虑字母表 $\Sigma = \{a, b, c\}$,正则表达式 $r = a\cdot b + a\cdot c$。 129 | 130 | ![dfa](./re-deriv/20250124dfa.png) 131 | 132 | 上图是完整的构造过程。概括来说,就是从原正则表达式对应的初始状态出发,对每个字符求导,得到下一个状态,直到没有新的状态产生。 133 | 134 | 这里状态(正则表达式)相等的定义是,两个正则表达式对应的语言相等。这个定义是合理的,因为我们的目标是构造 DFA,而 DFA 的状态是根据语言的不同而不同的。 135 | 136 | 单纯从状态转移的角度,我们可以看出这一定是最简 DFA,但是这个简单的算法也存在一些问题,足以让它不可用: 137 | 138 | - Unicode 有超过 100 万个字符(code point),对每个字符求导是不实际的; 139 | - 判定正则表达式相等复杂度甚至是 nonelementary 的(超过指数级); 140 | - 只能将一个正则表达式转换为 DFA 141 | 142 | ### 正则表达式弱相等 143 | 144 | 为了解决上述问题,我们引入 *正则表达式弱相等* 的概念。两个正则表达式 $r, s$ 弱相等,记作 $r \approx s$,我们希望判断弱相等的开销较低,从而把它用于状态检查和合并。 145 | 146 | 为了保证算法正确,我们仍然希望 $r \not= s \Rightarrow r \not\approx s$,即如果两个状态不相等,我们不会把它们合并。另一个方向则无所谓。 147 | 148 | 写成逆否命题就是:$r \approx s \Rightarrow r = s$。 149 | 150 | 一个直观的想法就是,我们定义一套规则,能快速判断 $r \approx s$。这里给出一套规则: 151 | 152 | ![rules](./re-deriv/20250124rules.png) 153 | 154 | Brzozowski 证明了仅用 (*) 标记的规则就足以保证状态数有限。而根据实践,采用完整的规则集合,可以在实际应用中保证大多数时候都是最简 DFA。 155 | 156 | 一个实践中的做法是,对每个正则表达式,都保存它的 *标准形式*,即按照规则集合化简后的形式。这样,我们就可以用标准形式来判断弱相等。 157 | 158 | 这可以在构造函数中实现,示例:`mkNegate(r)` 会检查 $r$ 是否为 $¬s$,若是则返回 $s$(应用双重否定律 $¬¬s ≈ s$)。 159 | 160 | 为了使用交换律和结合律,则可以规定词法排序,例如对于 $r + s$ 还是 $s + r$,规定使用字典序。 161 | 162 | 其他有效的加速策略包括将正则表达式作为 key 映射到 DFA 状态等。 163 | 164 | ### 字符集 165 | 166 | 传统的 DFA 构造和基于求导的 DFA 构造都会遇到字母表太大,完整迭代效率太低的问题:一般情况状态数都远小于字母表大小(Unicode 字母表大小大于 100 万),所以我们希望能够对字母表进行压缩。这可以通过引入 *字符集合* 来实现。 167 | 168 | 简单来说就是基本字符不再是 $a, b, c$,而是某个集合。之后一些运算会相应地变成集合运算。 169 | 170 | ### 正则向量 171 | 172 | 原文是说,为了让 scanner generator 能并行处理多个正则表达式,我们可以引入 *正则向量* 的概念。正则向量是一个正则表达式的集合,我们可以对正则向量进行求导,得到一个新的正则向量。 173 | 174 | $$ 175 | \partial_u (r_1, r_2, \ldots, r_n) = (\partial_u r_1, \partial_u r_2, \ldots, \partial_u r_n) 176 | $$ 177 | 178 | 接受状态的定义是,$r$ 中的某个正则表达式接受空字符串;反之,拒绝状态是所有正则表达式都拒绝。 179 | 180 | 但我感觉这里就是处理几个正则表达式并起来,好像实践中很少有并行这么干的情况。 181 | 182 | ## 总结 183 | 184 | 挺好玩。 185 | -------------------------------------------------------------------------------- /posts/sudoko-astar.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 数独的 A* 算法及其实现 3 | date: 2021-06-05 22:19:04 4 | tags: [算法, C/C++] 5 | category: 算法 6 | mathjax: true 7 | tocbot: true 8 | --- 9 | 10 | 接上一篇博客:[数独的模拟逻辑解法的实现](http://home.ustc.edu.cn/~liuly0322/blog/2021/06/03/sudoko-iddfs/),本篇博客将介绍数独的 A\* 算法求解。 11 | 12 | ## A\* 算法 13 | 14 | ### 原理分析 15 | 16 | A\* 算法是游戏中寻找路径的一种常见解法,它能在保证找到最短路径的同时,以一种较为节省计算资源开销的方式达到这一目的。关于理论分析,可以见斯坦福计算机系写的一篇 [算法介绍](https://blog.csdn.net/denghecsdn/article/details/78778769),或者见它的 [中文译文](https://dev.gameres.com/Program/Abstract/Arithmetic/AmitAStar.mht),这个网页似乎有点兼容性问题,所以你也可以查看 [csdn 的转载](https://blog.csdn.net/b2b160/article/details/4057781)。 17 | 18 | 19 | 20 | ~~大概只有在这个时候我才能感觉到中文互联网 csdn 无限套娃的一点好处~~ 21 | 22 | 概括一点来说,A\* 算法结合了 **Dijkstra 算法** 和 **最佳优先搜索算法** 的优点。二者都从普通的广度优先搜索演化而来,其中前者是按照离起始点的有权路径距离来进行搜索的,能确保找到最短路径,而后者采取了贪心策略,估计了当前点和目标点的距离,一般能较快的找到终点。这种贪心策略被称为 **启发式方法**。 23 | 24 | 可以认为,Dijkstra 算法按照点的 $g(n)$ 值作为依据进行搜索,$g(n)$ 是距离原点距离(可以带权)。 25 | 26 | 而最佳优先搜索算法按照点的 $h(n)$ 值作为搜索依据,$h(n)$ 是从结点到目标点的距离(只能估计)。 27 | 28 | 而 A\* 算法就是对这二者进行综合,它进行搜索的依据是点的 $f(n)=g(n)+h(n)$ 值。这样一来,可以在保证找到最短路径的同时尽可能减少计算开销。 29 | 30 | 以上,在进行寻路算法时,我们使用了 **距离**一词,由于不同结点之间的“距离”可能是不同的,也就是这些点可以被抽象成有权图,因此,有时,也会使用用 **代价** 一词来表示有权的距离。此外,对于简单的二维平面迷宫问题,选取 $f(n)=g(n)+h(n)$ 是既合理又比较快速的,但是更一般情况,只需要 $f(n)$ 与 $g(n)$ 和 $h(n)$ 都具有正相关性即可,根据 $g(n)$ 和 $h(n)$ 的权重的大小,A\* 算法会表现出结果更精准或运算更快,也就是更向 Dijkstra 算法或最佳优先搜索算法退化的特性。 31 | 32 | ### 具体实现 33 | 34 | 这里引用 Red Blob Games[这篇文章](https://www.redblobgames.com/pathfinding/a-star/introduction.html) 里的 Python 代码(~~为什么,因为他写的实在是太好了~~)。 35 | 36 | 顺便一提,作者的博客有很多很有意思的可视化内容,有兴趣可以自己去玩玩。 37 | 38 | ```python 39 | frontier = PriorityQueue() 40 | frontier.put(start, 0) 41 | came_from = dict() 42 | cost_so_far = dict() 43 | came_from[start] = None 44 | cost_so_far[start] = 0 45 | 46 | while not frontier.empty(): 47 | current = frontier.get() 48 | 49 | if current == goal: 50 | break 51 | 52 | for next in graph.neighbors(current): 53 | new_cost = cost_so_far[current] + graph.cost(current, next) 54 | if next not in cost_so_far or new_cost n2.cost; 102 | } 103 | //构造函数 104 | SudokuNode(); 105 | SudokuNode(char src[][9]); 106 | //运算函数 107 | static void mark_cell(int cell, bool* mark, char* num); //标记 cell 可放数字 108 | int fill(int cell, int num_fill); //填数,返回 0 代表无解 109 | void cal_cost(int increase); //计算 cost_to_end 和 cost 总 110 | void get_current_num(char to_num[][9]); //将当前结点信息输出 111 | }; 112 | ``` 113 | 114 | 部分变量意义参见 [上一篇博客](http://home.ustc.edu.cn/~liuly0322/blog/2021/06/03/sudoko-iddfs/)。具体函数实现此处不表。 115 | 116 | A\* 搜索函数如下所示: 117 | 118 | ```cpp 119 | int Sudoku::search_astar() { 120 | SudokuNode current(sudoku_num), next; 121 | std::priority_queue frontier; 122 | frontier.push(current); 123 | while (!frontier.empty()) { 124 | current = frontier.top(); 125 | frontier.pop(); 126 | if (current.num_now == 81) { 127 | current.get_current_num(sudoku_solve); 128 | return 1; 129 | } 130 | // 接下来需要遍历所有可能节点。由于相互的连通性,只要从最少可能的找即可 131 | int min = 127, min_index; 132 | for (int i = 0; i < 81; i++) { 133 | if (current.num_can_put[i] < min) { // 记录最小值 134 | min = current.num_can_put[i]; 135 | min_index = i; 136 | } 137 | } 138 | // 对这个格子生成所有可能的新节点,并加入优先队列 139 | for (int j = 1; j <= 9; j++) { 140 | if (!current.mark[min_index][j]) { 141 | next = current; 142 | if (next.fill(min_index, j)) { 143 | next.cal_cost(min); 144 | frontier.push(next); 145 | } 146 | } 147 | } 148 | } 149 | return 0; 150 | } 151 | ``` 152 | -------------------------------------------------------------------------------- /posts/pwa.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: PWA 补完计划 3 | date: 2024-02-26 19:59:23 4 | tags: [vite, pwa] 5 | category: web 6 | --- 7 | 8 | 这篇文章记录下对博客进行的 PWA(Progressive-Web-App)改造!因为博客是基于 Vite 的,所以理论上来说安装配置一下 [VitePWA](https://github.com/vite-pwa/vite-plugin-pwa) 就 ok 了。 9 | 10 | ## PWA 11 | 12 | PWA 这个词听上去可能很让人陌生,但在国内我们早就熟悉一个很类似的概念了——小程序。 13 | 14 | 15 | 16 | > PWA 是 Progressive Web App 的缩写,是一种 Web App 的新模式,可以让网站具备类似原生 App 的体验。 17 | 18 | 好吧,还是很抽象,那么来看看 PWA 好处都有啥?最重要的一点,PWA 可以让网页自行管理某个作用域(例如 `/`)的所有请求,例如第一次请求后在本地储存一份缓存下来的版本,之后每次访问就不需要再联网了: 19 | 20 | - 允许用户在离线状态下使用,甚至可以作为 app 被安装到多端桌面 21 | - 允许预取资源,只需要预先提供一份网站的资源列表 22 | 23 | 其中预取资源对我们的博客网站来说是非常有用的,可以大大加载访问速度。当然,PWA 还有很多其他特性,比如推送通知等,但这些对于一个静态博客网站来说就不是那么重要了。 24 | 25 | 那么 PWA 有没有什么坏处呢?其实也有,相信大家都遇到过微信小程序提示更新后才能打开的情况,这是因为小程序的缓存机制导致的。PWA 也有类似的问题,如果我们的博客(程序)更新了,用户需要手动/自动刷新才能看到最新内容。本篇文章会具体解释这一更新机制。 26 | 27 | ## VitePWA 28 | 29 | ```shell 30 | pnpm add -D vite-plugin-pwa 31 | ``` 32 | 33 | 配置过程参考了 VitePWA 的 [PWA Minimal Requirements](https://vite-pwa-org.netlify.app/guide/pwa-minimal-requirements.html)、[Automatic reload](https://vite-pwa-org.netlify.app/guide/auto-update.html) 和 [Static assets handling](https://vite-pwa-org.netlify.app/guide/static-assets.html)。此外,[Periodic Service Worker Updates](https://vite-pwa-org.netlify.app/guide/periodic-sw-updates.html) 解释了如何以指定间隔检查更新,[Unregister Service Worker](https://vite-pwa-org.netlify.app/guide/unregister-service-worker.html) 解释了如何在启用后禁用 PWA 功能,也值得看看。 34 | 35 | `index.html` 中需要补充一些元信息: 36 | 37 | ```html 38 | 39 | llyのblog 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ``` 50 | 51 | `vite.config.ts` 中配置: 52 | 53 | ```typescript 54 | import { defineConfig } from 'vite' 55 | import { VitePWA } from 'vite-plugin-pwa' 56 | 57 | export default defineConfig({ 58 | plugins: [VitePWA({ 59 | // 配置自动更新 60 | registerType: 'autoUpdate', 61 | workbox: { 62 | // 配置缓存列表 63 | globPatterns: ['**/*.{js,css,ico,svg}', 'index.html'], 64 | // https://github.com/vite-pwa/vite-plugin-pwa/issues/120 65 | navigateFallback: null, 66 | }, 67 | includeAssets: ['favicon.ico', 'apple-touch-icon.png'], 68 | manifest: { 69 | name: 'llyのblog', 70 | short_name: 'llyのblog', 71 | description: '我的个人博客,写点想写的', 72 | lang: 'zh-CN', 73 | theme_color: '#ffffff', 74 | icons: [ 75 | { 76 | src: 'pwa-192x192.png', 77 | sizes: '192x192', 78 | type: 'image/png', 79 | }, 80 | { 81 | src: 'pwa-512x512.png', 82 | sizes: '512x512', 83 | type: 'image/png', 84 | }, 85 | ], 86 | }, 87 | })], 88 | }) 89 | ``` 90 | 91 | - `registerType: 'autoUpdate'` 配置了 PWA 自动更新; 92 | - `workbox.globPatterns` 配置了所有需要缓存(预取)的文件; 93 | - `includeAssets` 和 `manifest` 是一些元信息和图标相关的配置。 94 | 95 | 图标可以在 [Favicon InBrowser.App](https://favicon.inbrowser.app/tools/favicon-generator) 生成。 96 | 97 | 在 `main.ts` 里需要引入 `registerSW` 以实现自动更新: 98 | 99 | ```typescript 100 | import { registerSW } from 'virtual:pwa-register' 101 | 102 | registerSW({ immediate: true }) 103 | ``` 104 | 105 | 如果不引入这个虚拟模块,每次打开页面后也会检查 `sw.js` 的更新,但是后台静默安装完更新(包括下载新资源并删除旧资源)后并不会与页面产生交互(这里就是刷新页面)。旧 JavaScript 资源的删除会导致我们无法在 SPA 应用中导航去新的页面(因为新的页面的动态路由区块的 JavaScript 资源被删除了),所以**一般情况**需要引入它来实现更新后刷新页面(最后会介绍一种不需要自动刷新页面的更新策略)。 106 | 107 | 此时可以编译出来测测了: 108 | 109 | ```shell 110 | pnpm build 111 | pnpm preview 112 | ``` 113 | 114 | 可以使用 [Lighthouse](https://github.com/GoogleChrome/lighthouse) 的 PWA 测试,也可以关闭 pnpm 的 preview 后测试离线访问。DevTools 的 Network 和 Application 面板对调试也会很有帮助。 115 | 116 | ## PWA 的更新 117 | 118 | 上文提到: 119 | 120 | > 如果我们的博客(程序)更新了,用户需要手动/自动刷新才能看到最新内容。 121 | 122 | 现在可以整理一下更新逻辑了。首先,博客(程序)是指 `sw.js` 和在 `sw.js` 缓存列表(就是 `vite.config.ts` 中配置的 `workbox.globPatterns`)中的文件(而不是所有的静态文件!注意未被缓存的静态文件的更新不必引起 PWA 的更新)。缓存列表中的文件的更新会引起 `sw.js` 的更新,因为 `sw.js` 中保存了所有缓存列表的文件的 hash 值。 123 | 124 | 随后,随着 `index.html` 的打开和 JavaScript 的加载,客户端会去请求 `sw.js`,当发现有更新后会后台静默安装好更新并刷新页面(这里是指上面提到的 `registerSW({ immediate: true })` 配置)。 125 | 126 | 但是,对于我们的博客网页来说,这样的更新存在下列问题: 127 | 128 | - 打开网页可能看到的是旧版内容,比如新更新的博客没刷新出来; 129 | - 强制刷新的体验并不好,包括额外的加载时间和元素滚动位置不被记录等。 130 | 131 | 一个更新策略是应用框架与数据分离。可以把博客想象成一个论坛 app,UI 和动态的帖子数据(这里就是我们的每一篇博客内容)是分离的。只有当 UI 更新时才需要强制刷新,而帖子数据可以做成动态的数据请求,比如请求一个 JSON 文件。帖子的更新通过 JSON 文件的更新来实现,只有 UI 的更新会更改 `sw.js`。 132 | 133 | 为此,我们需要指定这个 JSON 文件不被缓存。也就是不要把它包含在 `workbox.globPatterns` 中。这可以通过检查最后生成的 `sw.js` 文件来确认。 134 | 135 | 上面的策略可以确保看到的是最新博客内容,并且一定程度上缓解了强制刷新的问题。那么,有没有更到位的解决方案呢?确实是有的,但需要牺牲 PWA 的离线能力。 136 | 137 | 道理很简单,我们所加载的所有 JavaScript 文件都是入口的 `index.html` 指定的,所以只要不把 `index.html` 缓存起来,就可以保证每次打开页面都是最新的。分类讨论: 138 | 139 | - 某次更新后第一次打开页面:打包的 JavaScript 和 CSS 的文件名哈希会改变,所以 `index.html` 会去请求未被缓存的新文件,这样请求就通过网络发生。与此同时,后台安装新的 service worker,缓存新版本的资源,并删除旧版本的资源。因为我们现在就处在新版本当中了,所以不再需要刷新页面,旧版本的资源也可以安全删除。 140 | - 某次更新后第二次打开页面:`index.html` 请求的资源都没有更新,可以直接从缓存中加载。 141 | 142 | 同理,对于一些 SSR/SSG 场景,也不需要 HTML 文件被缓存,可以参考这个 [issue](https://github.com/vite-pwa/vite-plugin-pwa/issues/120) 的讨论。 143 | 144 | 需要注意 workbox 的配置: 145 | 146 | ```javascript 147 | workbox: { 148 | // 资源文件或不需要保证实时性的文件可以放在缓存列表中 149 | // 不需要包含 HTML 文件 150 | globPatterns: ['**/*.{js,css,ico,svg}', 'friends.json'], 151 | // https://github.com/vite-pwa/vite-plugin-pwa/issues/120 152 | navigateFallback: null, 153 | }, 154 | ``` 155 | 156 | 再移除 `registerSW({ immediate: true })` 就愉快的收工了。 157 | -------------------------------------------------------------------------------- /posts/set-union.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 集合间并集个数计算 3 | date: 2022-09-09 16:00:49 4 | tags: [C/C++, 算法] 5 | category: 算法 6 | --- 7 | 8 | 来源于西瓜书习题 1.2, 计算若 n 个集合中最多取 k 个集合做并集,则有多少种可能的并集结果 9 | 10 | 题目内容: 11 | 12 | 与使用单个合取式来进行假设表示相比,使用“析合范式”将使得假设空间具有更强的表达能力 13 | 14 | 15 | 16 | 例如: 17 | 18 | 好瓜 $\iff$ ((色泽=\*)$∧$(根蒂=蜷缩)$∧$(敲声=\*))$∨$((色泽=乌黑)$∧$(根蒂=\*)$∧$(敲声=沉闷)) 19 | 20 | 会把“(色泽=青绿)∧(根蒂=蜷缩)∧(敲声=清脆)”以及“(色泽=青绿)∧(根蒂=蜷缩)∧(敲声=清脆)”都分类为“好瓜”。若使用最多包含 k 个合取式的析合范式来表达下表西瓜分类问题的假设空间,试估算共有多少中可能的假设 21 | 22 | 给定的数据:西瓜的色泽有 2 种,根蒂和敲声都有 3 种 23 | 24 | ## 分析 25 | 26 | 显然总并集数的上界:$2^{2*3*3}=262144$,我们只需要枚举 $k$ 使得最后总并集数达到这一上限即可 27 | 28 | 首先明确,当 $k = 1$ 时,合法的单个合取式一共有多少种呢? 29 | 30 | 注意到色泽可以是两种颜色或者通配符,根蒂(敲声)可以是三种根蒂(敲声)或者通配符,因此总数是 $3 * 4 * 4 + 1 = 49$(空集也可以纳入考虑范围) 31 | 32 | 那么,当 $k=n$ 时,我们理论上只需要遍历 $\sum_{i=1}^nC_{48}^{i}$ 种集合的组合,随后去重即可,但这个数字是非常庞大的:$n=9$ 时,仅 33 | 34 | $$C_{48}^{9}=1677106640 = 1.68 \times 10^9$$ 35 | 36 | [知乎上有一篇文章](https://zhuanlan.zhihu.com/p/355235881),作者穷举了所有可能,`用时:8899.29s`,这显然不是一个能够轻易接受的时间 37 | 38 | 因此,我们必须考虑优化 39 | 40 | ## 状态压缩 41 | 42 | 实际上,每一种可能,如(色泽=青绿)$∧$(根蒂=蜷缩)$∧$(敲声=清脆)可以看成一个点,不妨对点进行标号并状态压缩为 0(x) 00(y) 00(z) 形式 43 | - x, y, z 分别代表色泽(取值 0, 1),根蒂(取值 0, 1, 2),敲声(取值 0, 1, 2) 44 | - 所有点的集合就是 $\{0, 1, ..., 26(16+8+2)\}$ 45 | - 每一个合取式对应一个点集 46 | 47 | 同时,对于合取式对应的点集又可以状态压缩:注意到点集中最大编号也只有 26,因此一个点集可以通过移位合并成一个整数,我们最后只要统计有多少个不同的整数(并集)就可以了 48 | 49 | ## 剪枝 50 | 51 | 有些合取式(包含通配符)包含了另外一些合取式,因此如果选取了这种合取式,它的子合取式就没必要再选取了 52 | 53 | 这可以通过状态压缩后的位运算来实现,如果合取式 $a$ 包含了合取式 $b$,有 `a | b = a`,因为 b 不会贡献新的假设 54 | 55 | ## Python 代码 56 | 57 | Python 标准库就对多处理(并行计算)有了比较好的支持,因此这里采用并行计算加速 58 | 59 | ```python 60 | from itertools import product, repeat 61 | from functools import reduce 62 | from multiprocessing import Pool 63 | 64 | def task(sets: list[int], n: int): 65 | """ 66 | 从给定的可选点集列表 sets 中选出 n 个进行取并,返回值是一个集合,包含所有可能得到的点集 67 | 这里的点集都是指点集状态压缩后对应的整数 68 | """ 69 | if len(sets) == n: 70 | return (reduce(int.__or__, sets),) 71 | 72 | chosen = sets[-1] 73 | if (n := (n - 1)) == 0: 74 | return (chosen,) 75 | 76 | sets = [s for s in sets if s | chosen != chosen] 77 | res = set() 78 | for _ in range(len(sets) - n + 1): 79 | res = res.union(ans | chosen for ans in task(sets, n)) 80 | sets.pop() 81 | return res 82 | 83 | def main(k: int, sets: list[int]): 84 | """ 85 | 对于给定的 k,拆分任务,多线程执行 86 | """ 87 | all_ans = set() 88 | # 遍历所有可能的 n <= k,让 n 个点集组合 89 | for n in range(1, k + 1): 90 | p = Pool(15) 91 | paras = zip((sets[:i] for i in range(len(sets), n - 1, -1)), repeat(n)) 92 | values = p.starmap(task, paras) 93 | p.close(); p.join() 94 | 95 | all_ans = set.union(all_ans, *values) 96 | print(f'k = {n} finished... Ans is {len(all_ans)}') 97 | 98 | if __name__ == '__main__': 99 | # 初始化,先建立所有合取式的列表 100 | # 如开头所述,每个合取式被位压缩成整数 101 | state = lambda x, y, z: (x << 4) + (y << 2) + z 102 | sets = [] 103 | for x, y, z in product(range(3), range(4), range(4)): 104 | if x == 2: 105 | sets.append(sets[state(0, y, z)] | sets[state(1, y, z)]) 106 | elif y == 3: 107 | sets.append(sets[state(x, 0, z)] | sets[state(x, 1, z)] | sets[state(x, 2, z)]) 108 | elif z == 3: 109 | sets.append(sets[state(x, y, 0)] | sets[state(x, y, 1)] | sets[state(x, y, 2)]) 110 | else: 111 | sets.append(1 << state(x, y, z)) 112 | # 添加空集 113 | sets = [0] + sets 114 | 115 | # 接受输入 116 | num = int(input()) 117 | main(num, sets) 118 | ``` 119 | 120 | 10 秒左右就可以输出结果,~~比知乎上的答案快了 900 倍~~ 121 | 122 | ## C++ 代码 123 | 124 | C++ 代码虽然没有并行,但本身编译型语言的性能就比较好,在我的设备上大约比上述 15 线程并行的 Python 代码快了一倍 125 | 126 | ```cpp 127 | #include 128 | #include 129 | #include 130 | 131 | using Set = std::unordered_set; 132 | using Vec = std::vector; 133 | 134 | Set task(Vec& sets, int n) { 135 | Set ans; 136 | 137 | if (sets.size() == n) { 138 | int or_acc = 0; 139 | for (auto const& set : sets) { 140 | or_acc |= set; 141 | } 142 | return ans.emplace(or_acc), ans; 143 | } 144 | 145 | int chosen = sets.back(); 146 | if (!--n) 147 | return ans.emplace(chosen), ans; 148 | 149 | // shadowing sets, _sets is a new vec with only useful point sets 150 | Vec _sets; 151 | for (auto const& set : sets) { 152 | if ((set | chosen) != chosen) { 153 | _sets.push_back(set); 154 | } 155 | } 156 | 157 | Set rets; 158 | for (int i = _sets.size(); i >= n; i--) { 159 | rets = task(_sets, n); 160 | for (auto const& ret : rets) { 161 | ans.emplace(ret | chosen); 162 | } 163 | _sets.pop_back(); 164 | } 165 | 166 | return ans; 167 | } 168 | 169 | int main() { 170 | Vec sets; 171 | Set ans; 172 | auto state = [](int x, int y, int z) { return (x << 4) + (y << 2) + z; }; 173 | for (int x = 0; x < 3; x++) { 174 | for (int y = 0; y < 4; y++) { 175 | for (int z = 0; z < 4; z++) { 176 | if (x == 2) { 177 | sets.push_back(sets[state(0, y, z)] | sets[state(1, y, z)]); 178 | } else if (y == 3) { 179 | sets.push_back(sets[state(x, 0, z)] | sets[state(x, 1, z)] | 180 | sets[state(x, 2, z)]); 181 | } else if (z == 3) { 182 | sets.push_back(sets[state(x, y, 0)] | sets[state(x, y, 1)] | 183 | sets[state(x, y, 2)]); 184 | } else { 185 | sets.push_back(1 << state(x, y, z)); 186 | } 187 | } 188 | } 189 | } 190 | sets.push_back(0); 191 | 192 | int k; 193 | std::cin >> k; 194 | 195 | Set rets; 196 | for (int n = 0; n <= k; n++) { 197 | rets = task(sets, n + 1); 198 | for (auto& ret : rets) { 199 | ans.insert(ret); 200 | } 201 | if (n) { 202 | std::cout << "length: " << n << " nums: " << ans.size() 203 | << std::endl; 204 | } 205 | } 206 | } 207 | ``` -------------------------------------------------------------------------------- /posts/specialization.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 部分求值与程序特化 3 | date: 2024-05-18 00:25:40 4 | tags: [部分求值, 编译] 5 | category: web 6 | --- 7 | 8 | 整理自前段时间做的一个技术分享。还是承接两个月前我写的打包体积优化的文章,最后留了一个小坑: 9 | 10 | > ESM 时期这类项目应该是有机会有后续进展的,因为对 ESM 模块而言,静态的导入导出声明使得可以更简单的获取到模块的精确调用模型。值得期待。 11 | 12 | 其实这里说的不太准确,ESM 的好处主要是限制了顶层变量的作用域,再配合模块导入导出声明才能达到追踪变量使用的效果。总之后面自己尝试填了一点这方面的坑,给 Rollup 提的 [PR](https://github.com/rollup/rollup/pull/5443) 也是在干这件事,这个链接里有 Rollup 的 maintainer([@lukastaegert](https://github.com/lukastaegert))整理的这个 PR 的作用和合并过程(其实还挺折磨的,因为一开始没太看懂 Rollup 的算法思想)。 13 | 14 | 所以本篇文章补一下这个 PR 实现背后的部分理论基础,主要是部分求值,和在 JavaScript 打包体积优化的应用。关于 Tree-Shaking 算法细节,和算法中比较重要的,变量的状态的格(代数结构里的格)表示,可能会另开一篇(写在毕设里了,不过未必会整理到博客上),也可能咕咕咕( 15 | 16 | 17 | 18 | > POPL '86 [“Compilers and staging transformations”](https://dl.acm.org/doi/abs/10.1145/512644.512652): 19 | > 20 | > 计算通常可以分为多个阶段,这些阶段通过执行频率或数据可用性来区分。预计算 和 循环不变代码外提(Frequency Reduction) 涉及在程序的不同阶段完成计算,以便尽早完成计算(因此后续步骤需要更少的时间)并且尽可能不要重复计算(以减少总体时间)。 21 | 22 | TL;DR: 编译期和运行时只是程序执行的不同的阶段,编译的过程也是一个**部分执行**程序的过程。熟知 Linux 上编译安装某个程序的常见步骤: 23 | 24 | ```shell 25 | ./configure 26 | make 27 | make install 28 | ``` 29 | 30 | `./configure` 命令用于编译前配置一些选项,例如是否包含某个功能。考虑一段简单的矩阵乘法例子: 31 | 32 | ```c 33 | #include 34 | 35 | #define ROWS 3 36 | #define COLS 3 37 | 38 | void matrix_multiply(int A[ROWS][COLS], int B[COLS][ROWS], int result[ROWS][ROWS]) { 39 | for (int i = 0; i < ROWS; i++) { 40 | for (int j = 0; j < ROWS; j++) { 41 | result[i][j] = 0; 42 | for (int k = 0; k < COLS; k++) { 43 | result[i][j] += A[i][k] * B[k][j]; 44 | } 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | 如果 `./configure` 配置了 `ROWS` 和 `COLS` 为常量,那么 `make` 时编译器就可以进行更针对的优化,例如当 `ROWS` 和 `COLS` 较小时循环展开。在这个例子中,`ROWS` 和 `COLS` 不仅是**配置**,也可以看成程序的**输入**,只是这部分输入在编译期已经确定,或者说被特化。 51 | 52 | ## 部分求值器 53 | 54 | 这一部分说明通过程序编译期输入进行程序优化的潜力。 55 | 56 | 我们把程序的输入分为两部分,一部分是运行时才知道的输入,一部分是编译期就可以知道的输入。 57 | 58 | $$\verb|INPUT| = \verb|INPUT|_{compiletime} \cup \verb|INPUT|_{runtime}$$ 59 | 60 | 存在一个部分求值器,接收一个程序和该程序的编译期输入,据此输出另一个优化后的程序。 61 | 62 | $$P(\verb|program|, \verb|INPUT|_{compiletime}) = \verb|program_optimized|$$ 63 | 64 | 优化前后程序功能等价。 65 | 66 | $$\verb|program|(\verb|INPUT|_{runtime}) = \verb|program_optimized|(\verb|INPUT|_{runtime})$$ 67 | 68 | 假设我们现在有一个语言的解释器 Interpreter 和它要执行的一段脚本 script。解释器本身也是一个程序,它接收脚本 script 和 执行脚本时用户的输入 这两个输入。这两个输入处在不同的阶段,我们把前者看成编译期输入: 69 | 70 | $$P(\verb|Interpreter|, \verb|script|) = \verb|program_optimized|$$ 71 | 72 | 当然需要保证对运行时输入表现相同: 73 | 74 | $$\verb|Interpreter|(\verb|INPUT|_{runtime}) = \verb|program_optimized|(\verb|INPUT|_{runtime})$$ 75 | 76 | 等式左边是用解释器执行这段脚本,接收执行脚本时用户输入。它等价于等式右边的另一个优化后的程序接收执行脚本时用户的输入。相当于我们得到了一个脚本编译后的程序。也就是 **只需要实现一个语言的解释器,它的编译器自然存在**。然而通用的效果良好的部分求值器难以实现,因此还没有实用的“给定一个语言的解释器,得到一个该语言的**高效**的编译器”的方法。但换言之,只要我们将尽可能多的计算提前到编译期,就能提高运行时的性能。如现在模版引擎常常将模版解析部分放在编译期。 77 | 78 | 这里未必需要严格的「编译期」,「运行时」的概念,只要能将 **计算的阶段(Staging)** 提前即可。例如 SSG (Static Site Generation) 和 SSR (Server Side Render) 都是提前渲染了页面,但是 SSG 是在「编译期」,SSR 是服务器实时完成。 79 | 80 | ### 实际应用 81 | 82 | 什么东东能接收一个程序和(一段时间)输入数据,输出一个更高效的程序? 83 | 84 | - JIT 编译器 85 | - PGO 86 | 87 | PGO 听上去不是很部分求值,但其实挺 staging 的。 88 | 89 | ## Tree-Shaking 算法 90 | 91 | 第三方库,如 jQuery,lodash,vue-router 等,可以完全被放到运行时加载(比如以 CDN 形式提供)。然而,随着 ES6 模块机制的提出和打包工具的 Tree-Shaking 优化算法的发展,现在一般会有一个打包的步骤以减小体积。打包也是一种形式的编译,Tree-Shaking 就是在编译期特化(只保留使用到的函数/类/对象)代码,伪代码: 92 | 93 | ```javascript 94 | const included = new Set(); 95 | let needTreeShakingPass = true; 96 | // 迭代直到算法收敛 97 | // 算法收敛意味着一轮迭代中,所有结点的状态都不变,且没有新的结点被包含 98 | While (changed) { 99 | needTreeShakingPass = false; 100 | markTopLevelSideEffectNodesIncluded(graph, included); 101 | for (const node of included) { 102 | // 更新结点状态。例如 if (a) {} 中,如果 a 在上一轮迭代中被标记为有修改,那就要置这里的条件值为 UNKNOWN 103 | const isNodeStateUpdated = UpdateNodeState(node); 104 | // 结点状态更新可能会带来新的结点被包含。 105 | // 若 if (a) {} 中 a 的值从 false 变为 UNKNOWN,就要新包含 块语句 结点 106 | const isNewNodeIncluded = MarkNodeUsedByCurrentNodeIncluded(node, included); 107 | if (isNodeStateUpdated || isNewNodeIncluded) { 108 | needTreeShakingPass = true; 109 | } 110 | } 111 | } 112 | ``` 113 | 114 | Tree-Shaking 实现还有一些细节,例如每个表达式的值都会在判断副作用阶段就被首先尽可能的求出;表达式的值只有已知到未知的变化路径,最多变化一次,以保证算法收敛等,这里不再展开。 115 | 116 | ## 特化第三方库函数 117 | 118 | 然而,Tree-Shaking 算法只能精确到是否包含**某个函数/类/对象**,无法做到对内部属性的精确分析。一部分原因也是因为 JavaScript 中这些都是一等对象(first-class objects),由于 JavaScript 的动态特性很难追踪它们的所有使用。所以 UglifyJS 等工具一般也没办法对类的方法重命名,或者去除对象的某个没有被使用到的属性。但有的时候在包含某个函数/类/对象的粒度并不足够我们使用。例如,Vue 很多第三方组件库都提供了丰富的配置选项,但一个 Vue 组件是整体作为一个对象,因此我们打包时只能选择包含或不包含某个组件,无法部分包含: 119 | 120 | ![naive-pagination](./specialization/naive-pagination.png) 121 | 122 | 上图是 Naive UI 的[分页组件](https://www.naiveui.com/zh-CN/os-theme/components/pagination),即使我们只需要使用「简单分页」这一功能,也不得不去包含一大堆无用的渲染代码。如果某个第三方函数/类/对象的体积成为了打包产物体积的瓶颈,有以下几种可能的解决方法 123 | 124 | ### 拆分单独的函数/功能对象 125 | 126 | 暴露不同的函数好理解(直接不同功能提供不同函数),也可以选择暴露不同的对象以方便组合 e.g. 127 | 128 | ```javascript 129 | export PluginA; 130 | export PluginB; 131 | 132 | export SomeLibraryFunction() { 133 | // ... 134 | const self = { 135 | // 实际执行 136 | run() { 137 | // ... 138 | } 139 | installPlugin(plugin) { 140 | // ... 141 | return self; 142 | } 143 | }; 144 | return self; 145 | } 146 | 147 | // 用户调用 148 | SomeLibraryFunction().installPlugin(PluginA).run(); 149 | ``` 150 | 151 | 此时 PluginB 因为没有被使用,自然不会被包含。 152 | 153 | ### 打包工具静态分析 154 | 155 | 除了本文一开始提到的 PR,Rollup 当前还有另一个 [PR](https://github.com/rollup/rollup/pull/5420) 在推进,是关于消除对象的未使用属性的(2018 年被[提出](https://github.com/rollup/rollup/issues/2201),一直未得到解决)。 156 | 157 | ### Babel 插件 158 | 159 | 对 Vue 组件而言,暂时没有一个通用的静态分析工具自动替换所有已知调用参数(需要处理默认参数等问题)。然而,对于体积瓶颈的组件选项我们可以简单使用 babel 转译,来帮助打包时消除无用分支(其实也可以自己维护一份删除了不需要分支的组件,但这样可能不利于后续功能需求变更)。最后可以达到的效果是作为 Rollup/其他打包工具的插件提供,可以在配置文件中手动指定已知属性的值: 160 | 161 | ```typescript 162 | // 让插件把组件 setup 函数内的 props.disabled 和 render 函数内的 this.disabled 等都替换成 false 163 | // 则对应逻辑分支会被消除 164 | PartialEvaluator({ 165 | components: { 166 | Tag: { 167 | disabled: false, 168 | checkable: false, 169 | closable: false, 170 | }, 171 | }, 172 | }) 173 | ``` 174 | 175 | 比如若引入了一个第三方 Tag 组件,只需要样式而不需要使用它提供的「关闭 Tag」,「选择 Tag」,「禁用 Tag」等功能,就如上配置。 176 | -------------------------------------------------------------------------------- /posts/lc3-bench.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: lc3 评测姬 3 | date: 2021-12-10 22:17:59 4 | tags: [编译] 5 | category: web 6 | mathjax: false 7 | tocbot: true 8 | --- 9 | 10 | 总算把各种期中混杂着复变期末全部结束了,~~摸鱼一会~~ 11 | 12 | 本文将安利一个开源的纯 JS 的 lc-3 模拟器,并介绍自己改的一个 lc-3 评测姬 13 | 14 | ![](./lc3-bench/main.png) 15 | 16 | 17 | 18 | 源项目地址:https://github.com/wchargin/lc3web 19 | 20 | 并有 github-page:https://wchargin.github.io/lc3web/ 21 | 22 | ~~感觉比某大学某 ICS 课程主页安利的某模拟器好用~~ 23 | 24 | ~~JavaScript 是世界上最好的语言~~ 25 | 26 | Web 版的好处就在于甚至不需要安装(好文明),真正的全平台通用 27 | 28 | 大概由以下这些这些 JS 文件组成: 29 | 30 | ```shell 31 | js/ 32 | ├── bootstrap.js 33 | ├── bootstrap.min.js 34 | ├── jquery-1.11.1.min.js 35 | ├── lc3_as.js 36 | ├── lc3_core.js 37 | ├── lc3_hexbin.js 38 | ├── lc3_os.js 39 | ├── lc3_ui.js 40 | ├── lc3_util.js 41 | ├── npm.js 42 | └── Queue.js 43 | ``` 44 | 45 | 上面三个是用到的库,主体逻辑是 `lc3_core.js` ,主体界面交互放在了 `lc3_os.js` ,其余是一些供这两个 JS 调用的函数 46 | 47 | ~~这命名简洁清晰爱了爱了~~ 48 | 49 | 所以阅读源代码主要也就看上面提到的两个 JS 文件。 50 | 51 | ## lc-3 评测姬 52 | 53 | ### 开发动机 54 | 55 | ~~采用一次性 Judge 却不提供提前检验正确性工具的 lab 就好像只能提交一次的 acm, 没有灵魂~~ 56 | 57 | ### 具体实现 58 | 59 | #### 页面组件 60 | 61 | 不想破坏原来页面的美感(?),直接看着写就行了 62 | 63 | ```html 64 |
65 |

lab 评测

66 |

67 | 说明:本程序基于 GitHub 项目 68 | lc3web 69 | 修改而来,旨在满足修读 2021 USTC CS1002A 计算系统概论的同学们的可能的需要。 70 |

71 |

fork 后仓库: lc3web

72 |

73 | 请先在本模拟器中载入汇编代码或者机器码(在左侧的 Assemble 和 Raw 74 | 选项中载入),机器码需要 0011 0000 0000 0000 开头。 75 |

76 |

77 | 然后即可点击下方选择需要评测的实验,目前支持显示测试点正确性以及总执行指令数。评测要求基本参照助教文档。数据是自己随便给的,尽量全一点 78 |

79 |
80 |

选择测试题目

81 |
82 | 限制单次指令数:
87 | lab1 乘数数组: 88 | 94 | lab1 被乘数数组: 95 | 101 | lab2&3 测试数据: 102 | 108 | lab5 测试数据: 109 | 115 |
116 | 131 |
132 |
133 | ``` 134 | 135 | 按照原来的格式写好组件,然后加点允许用户自由设置的输入框即可。评测分别采用三个函数 `bench1(), bench2(), bench3()` 136 | 137 | 主要是因为一共没几个实验,而且输入输出不固定(直接指定寄存器),所以就直接这样高耦合低内聚的写了() 138 | 139 | #### 脚本逻辑 140 | 141 | 不想破坏原来框架的美感,另开一个 `lc3_bench.js` 142 | 143 | 核心是判题逻辑: 144 | 145 | 在 bench 函数预先处理好寄存器数据后,就要运行判题逻辑了,需要: 146 | 147 | 1. 获取设置的单个 case 最多指令数 148 | 2. pc 归位 x3000 149 | 3. 总执行指令数清零 150 | 151 | 随后就逐条指令执行。终止标志是当前即将执行的指令是 Trap 或者是 x0000(与\_\_\_助教的判题逻辑相同) 152 | 153 | ```javascript 154 | function benchTest(f) { 155 | const limit = document.querySelector("#cycleLimit").value; 156 | lc3.pc = 0x3000; 157 | lc3.totalInstruction = 0; 158 | var cnt = 0; 159 | while (true) { 160 | var op = lc3.decode(lc3.getMemory(lc3.pc)); 161 | if ((op.raw >= 61440 && op.raw <= 61695) || op.raw === 0) { 162 | var str = f(); 163 | break; 164 | } 165 | cnt++; 166 | if (cnt > limit) { 167 | alert("有测试样例超过单次最高执行指令数,请检查!"); 168 | return; 169 | } 170 | lc3.nextInstruction(); 171 | } 172 | return str; 173 | } 174 | ``` 175 | 176 | 当评测结束时,传入的回调函数用于处理结果(lc-3 模拟器是一个全局变量,回调函数通过读取这些全局变量的值判断结果是否正确) 177 | 178 | 下面是每个题目判题脚本的编写。以 lab2&3 为例: 179 | 180 | ```javascript 181 | function bench2() { 182 | // r0 是给定的 n, 其余寄存器初始化为 0, 结果存在 r7, 183 | // 计算数列:f(0)=1,f(1)=1,f(2)=2,f(n)=f(n-1)+2*f(n-3) 的第 n 项 184 | 185 | // 设置批处理模式,不更新用户显示的界面(见 lc3_ui.js) 186 | window.batchMode = true; 187 | 188 | // 获取正确答案的函数(因为测试样例没有很多,直接每次 O(n) 算了) 189 | function fib(x) { 190 | var arr = [1, 1, 2]; 191 | for (var i = 3; i <= x; i++) { 192 | arr[i] = (arr[i - 1] + 2 * arr[i - 3]) % 1024; 193 | } 194 | return arr[x]; 195 | } 196 | 197 | // 获取用户填入的测试样例 198 | const testcase = document 199 | .querySelector("#testcase1") 200 | .value.replace(/\s*/g, "") 201 | .split(",") 202 | .map(Number); 203 | 204 | var str = ""; // 最终将显示的结果 205 | var sumInstruction = 0; // 总计命令数(用于计算平均值) 206 | 207 | for (var i = 0; i < testcase.length; i++) { 208 | // 每次测试先初始化寄存器 209 | window.lc3.r = [0, 0, 0, 0, 0, 0, 0, 0]; 210 | window.lc3.r[0] = testcase[i]; 211 | 212 | var ans = fib(testcase[i]); // 获取正确答案 213 | str += `测试数据 F(${testcase[i]}) = ${ans} `; // 理论答案 214 | str += benchTest(bench_res); // 实际测试 215 | } 216 | 217 | // 所有样例评测完毕 218 | str += "平均指令数 " + sumInstruction / testcase.length; // 显示平均指令 219 | alert(str); // 显示最终将显示的结果 220 | window.gExitBatchMode(); // 刷新界面,退出批处理模式 221 | return; 222 | 223 | // 判断结果正确性的函数 224 | function bench_res() { 225 | // 判断结果 226 | var lc3res = window.lc3.r[7]; 227 | sumInstruction += window.lc3.totalInstruction; 228 | if (lc3res == ans) { 229 | return `你的回答正确,指令数 ${window.lc3.totalInstruction} \n`; 230 | } else { 231 | return `你的答案是 ${lc3res} \n`; 232 | } 233 | } 234 | } 235 | ``` 236 | 237 | 大概还可以抽象优化一下,摸了 238 | 239 | Have fun playing 240 | 241 | 不知道下一届还需不需要用到这个(希望人没事) 242 | -------------------------------------------------------------------------------- /posts/csapp-shell.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CSAPP 之 Shell Lab 3 | date: 2022-03-19 22:27:56 4 | tags: [C/C++, os] 5 | category: 笔记 6 | mathjax: true 7 | tocbot: true 8 | --- 9 | 10 | 博客摘几篇自己做的 CSAPP 发发(自认为可能有点参考价值) 11 | 12 | 全部代码可以见 [GitHub 仓库](https://github.com/liuly0322/CSAPP-LABS) 13 | 14 | 本篇是 Shell Lab 15 | 16 | 17 | 18 | 本次实验的要求是实现一个支持任务控制的 Unix shell 程序 19 | 20 | 程序的框架已经给出,只需要补充一些功能性的函数 21 | 22 | 由于整体是一个编程性质的实验,所以这里只在贴上最后结果后,讲一些实验中值得注意的函数 23 | 24 | ## 评测 25 | 26 | 可能由于每次运行的 pid 都有所不同,并且也无法保证 `/bin/ps` 行为相同,本实验没有给出一键测试 shell 正确性的程序 27 | 28 | 但对于每个评测点,都给出了 `tsh` 和 `tshref` 生成运行结果的程序 29 | 30 | 所以只要批量生成结果后手动比较即可 31 | 32 | `tshref` 的结果已经给出,在 `tshref.out` 文件中,下面我们写一个脚本批量生成 `tsh` 的运行结果 (fish 脚本,语法与 posix 有所不同) 33 | 34 | ![](./csapp-shell/5-1.png) 35 | 36 | 然后手动比较文件 37 | 38 | (因为 pid 都在括号内,所以首先用正则表达式把 pid 统一替换成 10000) 39 | 40 | ![](./csapp-shell/5-regex.png) 41 | 42 | 随后比较: 43 | 44 | ```bash 45 | diff 1.out tshref.out > out.diff 46 | ``` 47 | 48 | 查看发现只有 `ps` 运行结果不同,而运行行为达到预期 49 | 50 | ![](./csapp-shell/5-diff.png) 51 | 52 | 因此实验完成 53 | 54 | ## eval 55 | 56 | 补全如下: 57 | 58 | ```cpp 59 | void eval(char* cmdline) { 60 | char* argv[MAXARGS]; 61 | int bg; 62 | pid_t pid; 63 | 64 | bg = parseline(cmdline, argv); 65 | if (argv[0] == NULL) 66 | return; 67 | 68 | if (!builtin_cmd(argv)) { 69 | if ((pid = fork()) == 0) { 70 | setpgid(0, 0); 71 | if (execve(argv[0], argv, environ) < 0) { 72 | printf("%s: Command not found\n", argv[0]); 73 | exit(0); 74 | } 75 | } 76 | 77 | // 此处是父进程 78 | addjob(jobs, pid, (bg == 1 ? BG : FG), cmdline); 79 | if (!bg) { 80 | waitfg(pid); 81 | } else { 82 | printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline); 83 | } 84 | } 85 | 86 | return; 87 | } 88 | ``` 89 | 90 | 和书上给出的例程差不多,主要区别如下: 91 | 92 | - `setpgid(0, 0);` 使得能正常接受别的 `shell` 发送的终止信号,否则自己的所有子进程都会被终止 93 | - 前台进程的阻塞用的是 `waitfg`,后面实现 `fg` 命令也需要用到这一函数 94 | 95 | 理论上来说这里需要考虑屏蔽信号,实际上,现代计算机多核 CPU 并行运行各个进程,在进程数小的情况下很难因为并发遇到执行时序的问题,不过严谨考虑还是加锁为好,这里作为一个 toy 程序就没加了 96 | 97 | ## do_bgfg 98 | 99 | 这里的 `do_xx` 似乎是这种解释器程序普遍的命名习惯,代表执行什么什么内置指令 100 | 101 | `bg` 和 `fg` 要实现的是对进程运行状态的转换 102 | 103 | ```cpp 104 | void do_bgfg(char** argv) { 105 | int id; 106 | struct job_t* job; 107 | 108 | // 这里根据参数判断合法性,获取 job 109 | ...... 110 | 111 | job->state = (argv[0][0] == 'b' ? BG : FG); 112 | kill(-job->pid, SIGCONT); 113 | if (argv[0][0] == 'b') 114 | printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline); 115 | else 116 | waitfg(job->pid); 117 | 118 | return; 119 | } 120 | ``` 121 | 122 | 注意前台进程需要等待即可 123 | 124 | ## waitfg 125 | 126 | 很实用的 `helper` 函数 127 | 128 | ```cpp 129 | void waitfg(pid_t pid) { 130 | struct job_t* job = getjobpid(jobs, pid); 131 | while (job->state == FG) { 132 | sleep(1); 133 | } 134 | return; 135 | } 136 | ``` 137 | 138 | 按照实验说明的推荐,采用轮询加上休眠的方式即可,这样对这一进程的负担也比较小 139 | 140 | ## sigint & sigtstp 141 | 142 | 接下来是几个信号处理时的异步回调函数 143 | 144 | ```cpp 145 | void sigint_handler(int sig) { 146 | pid_t f_pid = fgpid(jobs); 147 | if (f_pid) { 148 | kill(-f_pid, sig); 149 | } 150 | return; 151 | } 152 | ``` 153 | 154 | ```cpp 155 | void sigtstp_handler(int sig) { 156 | pid_t f_pid = fgpid(jobs); 157 | if (f_pid) { 158 | kill(-f_pid, sig); 159 | } 160 | return; 161 | } 162 | ``` 163 | 164 | 这两个函数比较类似,接收到键盘的终止 / 暂停信号后发送给前台进程的进程组,所以用 `-f_pid` 165 | 166 | ## sigchld 167 | 168 | 这一部分用于处理子进程的中断 / 暂停信号 169 | 170 | ```cpp 171 | void sigchld_handler(int sig) { 172 | pid_t pid; 173 | int status; 174 | 175 | while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { 176 | if (WIFSTOPPED(status)) { // 暂停信号 177 | printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, 178 | WSTOPSIG(status)); 179 | getjobpid(jobs, pid)->state = ST; 180 | } else { 181 | if (WIFSIGNALED(status)) { // 退出信号 182 | printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), 183 | pid, WTERMSIG(status)); 184 | } 185 | // 子进程退出信号,以及正常运行结束 186 | deletejob(jobs, pid); 187 | } 188 | } 189 | return; 190 | } 191 | ``` 192 | 193 | 注意由于 unix 信号的阻塞机制,这里需要用 `while` 处理所有的僵死进程 194 | 195 | ## debug 细节 196 | 197 | 由于是编程实验,这一部分记录实验过程中遇到的有意思的问题以及解决方案 198 | 199 | ### 信号处理 200 | 201 | 在 `sigchld_handler` 函数中,一开始仿照书本 (注:第二版书),采用的条件是 202 | 203 | ```cpp 204 | while ((pid = waitpid(-1, &status, 0)) > 0) { 205 | ...... 206 | } 207 | ``` 208 | 209 | 但是会出现奇怪的 bug,终止进程输出的提示均为 `[0] (0)` 210 | 211 | 实际上书本的写法是有些问题的:这样做虽然会回收所有的僵死进程,但是 `waitpid` 的默认行为会不断等待活跃进程,直到活跃进程僵死才会返回 212 | 213 | 而我们虽然希望信号处理函数能够回收所有的进程,但我们也不希望信号处理处理函数会一直阻塞,直到所有进程运行完毕才继续执行,这样的话之后连前台进程都不存在了,自然也就获取不到前台进程的 `pid` 了,故 shell 的显示就会出现问题。理想情况下,应该是一次信号处理函数处理完当前所有僵死进程后就退出 214 | 215 | 换言之,这里的 `waitpid` 应该是一个同步函数而非异步函数 216 | 217 | (吐槽一下,高级语言的同步异步函数写多了再看 C 语言这种面向底层的语言确实有点小头疼) 218 | 219 | 为了修正这一问题,可以通过设置 `waitpid` 的 `options` 参数改变 `waitpid` 的默认行为 220 | 221 | 修改后如下: 222 | 223 | ```cpp 224 | while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { 225 | ...... 226 | } 227 | ``` 228 | 229 | `WNOHANG` 选项使得这一函数变为同步函数,如果没有僵死进程就立刻返回 230 | 231 | `WUNTRACED` 选项使得这一函数能够处理暂停的进程 232 | 233 | ### printf 234 | 235 | 可能在阅读上面代码时,读者会觉得 `sigchld_handler` 的信号处理很不优雅, `printf` 这种根据信号类型来打印的函数为什么不放在具体的信号处理函数里,而是选择放在一个大的回收子进程的函数内呢? 236 | 237 | 从功能上来说,是因为如果 shell 中运行的子进程收到终止 / 暂停信号而终止,我们希望 shell 程序也能提示用户。而信号处理函数只能处理 shell 进程自身收到的信号,适用范围就窄了。如果把打印写在信号处理函数内,信号依旧会得到处理,但是会在 `test16` 中因为没有子进程终止信号的提示,输出与 `tshref` 不一致 238 | 239 | 值得一提的是,这里有一个很有意思的安全问题,由信号处理函数中的 `printf` 引发。我们现在所写的 `sigchld_handler` 事实上也是不安全的,C 语言中信号处理函数中能安全调用的函数是有限的,可以在 [这个网站](https://man7.org/linux/man-pages/man7/signal-safety.7.html) 查阅 240 | 241 | 而 `printf` 函数并不在此列,这是因为 `printf` 为了确保线程安全会在写入到文件(这里是写入到标准输出这一文件描述符)时给文件加一个锁,但是注意:这个 `shell` 程序在主体控制流中也有 `printf` 函数的调用(例如打印进程的提示消息),考虑现在发生了这样的一个调用,并且在从打印提示消息到给文件描述符解锁的过程中,恰好程序收到一个终止信号,于是信号处理函数被调用,进程会阻塞在给文件描述符解锁之前,转而去执行信号处理函数中的 `printf` ,而这次的调用在执行到准备打印时,却会发现标准输出被加锁了(因为还没能成功解锁),故会停下来等候。但信号处理函数已经阻塞了进程执行正常控制流,自然也就一直等不到谁能给标准输出解锁了:这就造成了程序的死锁 242 | 243 | 严格来说,我们这个 "toy shell" 目前还是一个非常不健壮的状态。这种类似的信号处理更合理也更普遍的方式是使用管道来完成,不过这就大大超出了本实验的范畴,故不在此介绍 244 | 245 | 至此,Shell Lab 的实验圆满结束 246 | -------------------------------------------------------------------------------- /posts/sudoko-iddfs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 数独的模拟逻辑解法的实现 3 | date: 2021-06-03 23:38:03 4 | tags: [算法, C/C++] 5 | category: 算法 6 | mathjax: true 7 | tocbot: true 8 | --- 9 | 10 | 本文将主要介绍人工解数独时采用的唯余法的算法实现,并给出将其改造成迭代加深算法时,解所在最低层数的确定。 11 | 12 | ## 唯余法 13 | 14 | > 当数独谜题中的某一个宫格,因为所处的列、行及九宫格中,合计已出现过不同的 8 个数字,使得这个宫格所能填入 的数字,就只剩下那个还没出现过的数字时,我们称这个宫格有唯余解。 15 | 16 | 可以看到,唯余法就是利用排除法得出数独某个位置可能的唯一解。但在人工用这种方法解数独时,可能会出现以下问题: 17 | 18 | 1. 只剩唯一解的数独格子难以直接观察得到。采用唯余法观察时,行,列,宫格的重复性都要考虑,~~对人的视力提出了巨大的挑战~~。 19 | 2. 可能,而且很可能不存在能直接确定答案的数独格子。比如可能排除到最后,这个格子还剩 4 和 6 能填,确定不了具体需要填哪一个数。 20 | 21 | 22 | 23 | 但以上两点在计算机算法实现时却可以比较好的解决。对于第一点,计算机可以穷举每一个格子,对于第二点,计算机可以穷举每一个可能性,这就为我们采用唯余法编写程序提供了可行性。 24 | 25 | ## 算法实现 26 | 27 | ### 基本思路 28 | 29 | 采用递归的思路编写程序。这里采用 DFS 遍历所有格子。递归函数 int 类型,有解返回 1,无解返回 0 30 | 31 | ![流程图](https://i.loli.net/2021/06/03/ikPVKEsLBhedqo4.jpg) 32 | 33 | 此外,内层每次递归时也要对操作进行回溯,此处不表。 34 | 35 | 这里列出流程所必需的变量: 36 | 37 | - `char sudoko_solve[9][9]`:记录着当前的解 38 | - `char num_can_input[81]`:记录着每个格子还可以放多少数字。0 表示无解,取最大值 127 表示格子已经被填入数字 39 | - `bool mark[81][10]`:记录每个格子是否允许放 1 到 9 的数字。0 空出来,是为了代码简洁性。 40 | 41 | 在进入正式流程之前,先要对这些数字预处理。这里略去过程。 42 | 43 | `sudoko_solve[9][9]` 对于已有数字为该数字,否则为 0。 44 | 45 | 可能会注意到,这里存在 $9*9$ 和 81 两种数据记录的方法。这是由笔者别的模块的函数写法决定的。 46 | 47 | 转换关系:对于坐标 $(x,y)$,对应的后者数字为 $9*x+y$。反之,$x = cell / 9, y = cell \% 9$。 48 | 49 | ### 代码实现 50 | 51 | 根据上面的说明写出代码: 52 | 53 | ```cpp 54 | int dfs_ID(bool mark[][10], char* num_can_put, int depth) { 55 | if (depth> 90) { 56 | return 0; //迭代加深,退出递归,目前深度待定 57 | } 58 | struct IDDFS_change* queue[81]; //记录填入唯一解数字时造成的改变 59 | int change_num = 0; //对应上面的 queue,记录有多少唯一解 60 | int min = 127, min_index, flag = 0; 61 | do { 62 | min = 127; flag = 0; 63 | for (int i = 0; i < 81; i++) { 64 | if (num_can_put[i] == 0) { //无解,回溯后退出 65 | solve_res = 1; 66 | for (int i = change_num - 1; i>= 0; i--) { 67 | dfs_ID_recall(mark, num_can_put, queue[i]); 68 | } 69 | return 0; 70 | } 71 | else if (num_can_put[i] == 1) { //唯一解,填入并记录 72 | flag = 1; 73 | queue[change_num] = new IDDFS_change; change_num++; 74 | for (int j = 1; j <= 9; j++) { 75 | if (!mark[i][j]) { 76 | dfs_ID_fill(mark, num_can_put, i, j, queue[change_num - 1]); 77 | break; 78 | } 79 | } 80 | continue; //填入唯一解后,再次进入循环 81 | } 82 | else if (num_can_put[i] < min) { //记录最小值 83 | min = num_can_put[i]; 84 | min_index = i; 85 | } 86 | } 87 | } while (flag == 1); //填入了唯一解,需要再搜 88 | if (min == 127) { //最小值是 127,意味着每个格子都是唯一解的,也就是找到了数独的解 89 | solve_res = 2; 90 | return 1; 91 | } 92 | //对 min_index 进入下一层 93 | int cell = min_index; 94 | for (int j = 1; j <= 9; j++) { 95 | if (!mark[cell][j]) { 96 | struct IDDFS_change* nextlay = new struct IDDFS_change; //记录改变 97 | dfs_ID_fill(mark, num_can_put, cell, j, nextlay); 98 | if (dfs_ID(mark, num_can_put, depth + 1)) { //填入 99 | return 1; 100 | } 101 | dfs_ID_recall(mark, num_can_put, nextlay); //无解,回溯 102 | } 103 | } 104 | for (int i = change_num - 1; i>= 0; i--) { //回溯填入的唯一解 105 | dfs_ID_recall(mark, num_can_put, queue[i]); 106 | } 107 | return 0; 108 | } 109 | ``` 110 | 111 | 这里用 `flag` 记录是否有只有唯一解的格子。 112 | 113 | 填入和回溯函数借助了一个结构体:`struct IDDFS_change`。 114 | 115 | ```cpp 116 | struct IDDFS_change { 117 | int cell_fill,num_fill; 118 | int cell_num_can_put; 119 | int queue[30], change_num; 120 | }; 121 | ``` 122 | 123 | 这里仅仅是用于储存需要回溯的一些数据,所以不需要用类,结构体足矣。 124 | 125 | `dfs_ID_fill` 用于填入数字,并记录信息到 p 指针,便于回溯。 126 | 127 | ```cpp 128 | void dfs_ID_fill(bool mark[][10], char* num_can_put, int cell, int num, struct IDDFS_change* p) { 129 | p->cell_fill = cell; p->num_fill = num; 130 | p->cell_num_can_put = num_can_put[cell]; p->change_num = 0; //记录回溯需要信息 131 | int x = cell / 9, y = cell % 9; 132 | sudoko_solve[x][y] = num; //数字填入该格子 133 | num_can_put[cell] = 127; 134 | for (int k = 0; k < 9; k++) { 135 | if (!mark[9 * x + k][num] && !sudoko_solve[x][k]) { 136 | p->queue[p->change_num] = 9 * x + k; p->change_num++; //记录改变 137 | mark[9 * x + k][num] = true; 138 | num_can_put[9 * x + k]--; 139 | } 140 | } 141 | for (int k = 0; k < 9; k++) { 142 | if (!mark[9 * k + y][num] && !sudoko_solve[k][y]) { 143 | p->queue[p->change_num] = 9 * k + y; p->change_num++; 144 | mark[9 * k + y][num] = true; 145 | num_can_put[9 * k + y]--; 146 | } 147 | } 148 | int i_start = x / 3 * 3, j_start = y / 3 * 3; 149 | for (int dx = 0; dx < 3; dx++) { 150 | for (int dy = 0; dy < 3; dy++) { 151 | if (!mark[(i_start + dx) * 9 + (j_start + dy)][num] && 152 | !sudoko_solve[(i_start + dx)][(j_start + dy)]) { 153 | p->queue[p->change_num] = (i_start + dx) * 9 + (j_start + dy); 154 | p->change_num++; 155 | mark[(i_start + dx) * 9 + (j_start + dy)][num] = true; 156 | num_can_put[(i_start + dx) * 9 + (j_start + dy)]--; 157 | } 158 | } 159 | } 160 | } 161 | ``` 162 | 163 | `dfs_ID_recall` 用于回溯。 164 | 165 | ```cpp 166 | void dfs_ID_recall(bool mark[][10], char* num_can_put, struct IDDFS_change* p) { 167 | int x = p->cell_fill / 9, y = p->cell_fill % 9; 168 | num_can_put[9 * x + y] = p->cell_num_can_put; 169 | sudoko_solve[x][y] = 0; 170 | for (int i = 0; i < p->change_num; i++) { 171 | num_can_put[p->queue[i]]++; 172 | mark[p->queue[i]][p->num_fill] = false; 173 | } 174 | delete p; 175 | } 176 | ``` 177 | 178 | ## 算法的迭代加深改进 179 | 180 | 这里可以考虑随机生成数独并利用该算法求解,记录:出现解时最低到达深度。 181 | 182 | 随机生成数独是指在 $9*9$ 的格子上随机放入指定数量的数字(确保不矛盾,但未必有解) 183 | 184 | 因此,这里需要更改放入数字数量,反复实验,实验结果如下所示: 185 | 186 | - 初始 17 数字: 187 | 188 | 21,20,20,21,24,20,21,27,21,27,21,24,24,21,19,21,20,23,28,24,25,23。 189 | 190 | - 初始 25 数字: 191 | 192 | 12,10,16,9,12,9,13,6,13,7,12,11,10,8,12,10,5,3,17,7。 193 | 194 | - 初始 30 数字: 195 | 196 | 3,3,5,4,5,3,3,3,3,2,8,11,6,2,10,5,4,4,2,3。 197 | 198 | 可以看出,大部分情况,设置搜索深度为 24 已经能够找到目标,这样的搜索深度并不大。如果没找到目标,全部遍历即可。 199 | 200 | 本算法在复杂度上和朴素 dfs 都是 $O(c^n)$ 级别,$n=81$,但是相比朴素 dfs 大幅降低了 c,所以即便穷举所耗时间也比较优秀。 201 | 202 | 故可以这样将算法改进为迭代加深版本: 203 | 204 | ```cpp 205 | int Sudoko::dfs_ID(bool mark[][10], char* num_can_put, int depth, bool full_search) { 206 | if (depth> 24 && !full_search) { 207 | solve_res = 1; 208 | return 0; //迭代加深,退出递归。 209 | } 210 | ... 211 | } 212 | ``` 213 | 214 | 增加了一个参数 `bool full_search` 用于表示当前是否需要全部搜索。至此,就完成了本次实验题关于实现迭代加深 DFS 算法的要求。 215 | -------------------------------------------------------------------------------- /posts/lifemanual.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 写于 2021 高考前 3 | category: 4 | - 日志 5 | date: 2021-05-21 20:16:47 6 | mathjax: true 7 | tags: [杂谈] 8 | tocbot: true 9 | --- 10 | 11 | ## 前言 12 | 13 | 本文写于 2021 高考临近前,旨在给即将进入大学的各位提供一些微小的帮助。 14 | 15 | 考虑到本人能力,视野有限,故选校 / 学习部分更多是站在一个 985 理工科的视角上,若不符合个人情况,仅供参考。 16 | 17 | 18 | 19 | --- 20 | 21 | ## 选校篇 22 | 23 | 基本原则: 24 | 25 | 1. “兴趣”优于“热门”(前提真的是兴趣 26 | 27 | 2. “择专”优于“择校” 28 | 29 | ### 如何得知专业信息 30 | 31 | --- 32 | 33 | 其实高考后留给各位选专业的时间并不是很多,因此如何在短时间内搜集自己想选择的专业的内容是很重要的。下面提供几个了解专业信息时可以侧重考虑的方面: 34 | 35 | #### 学什么 36 | 37 | “顾名思义”并不总能奏效,比如 USTC 有信息学院和计算机学院两个平行的院系,对于大部分没有搜集过相关信息的同学,如果我问他们这两个学院之间有什么区别,相信大多数人都是答不上来的。 38 | 39 | 此外,就算是学院的名字你很熟悉,比如物理学院,但是大学阶段的物理真的和诸位在中学阶段的物理学到的是一回事吗?这些问题都是很值得思考的。当然,高考后到填报志愿之间的时间并不长,因此到也不必真的需要对每个专业如数家珍,但是对自己感兴趣的专业应该多少做到心里有数。 40 | 41 | 这里提供一些了解专业学习内容的比较好的办法: 42 | 43 | > 在网络上搜索本专业的一些通用介绍,可以参考百度百科、学院官网的介绍(包括师资、培养计划、专业排名等)、官方的各种就业深造统计数据、知乎等问答性网站,但这些内容往往缺少针对性,而且不是可交互的,无法及时针对你的疑问做出相应回答。 44 | 45 | 本部分引用自上海交通大学生存手册,文末可获得详细链接。补充一点,百度搜索“你考虑的学校 + 教务系统”,一般排在前列会出现该学校的教务系统或者教务处界面,在这里通常可以公开查询到不同专业的培养方案(也就是必修什么课)。 46 | 47 | #### 未来发展 48 | 49 | 就业 or 科研?就业前景如何,科研收入如何,辛不辛苦?科研可能并不像大家想象的那么美好,从硕士到博士到博士能顺利毕业到评奖,评教职,以至于更上,每一步都会淘汰不计其数的人,如果没有对科研的真心热爱,这会是一条很辛苦的路。 50 | 51 | ### 新兴专业,交叉学科与强基计划 52 | 53 | --- 54 | 55 | 这一条要单独列出来讲,是因为这些名词看上去很高端很唬人,但大部分这些所谓的名词并不是真的就这么美好:新兴专业,很多时候,连同其他一些热门专业一样,属于是顺应了时代的需求或者被其他因素推动捧成了热门专业(这里点名“21 世纪是生物的世纪”),并不是说这些热门专业就不好,而是说没必要冲着它们的名字就作出选择,还是要想清楚自己喜欢的是什么。 56 | 57 | 此外,对于强基计划,还是要慎重考虑,一方面这些强基计划覆盖的专业多为基础专业,且理论上不允许转强基计划之外的专业;另一方面,对于强基计划对应的特殊培养方案以及可能宣传的教授指导:特殊培养方案未必对你是好事,永远不要太高估自己的抗压能力;而教授辅导这件事情,事实上只要你对该学科有兴趣,哪怕你甚至不是这个专业,在大学你往往也可以通过邮件联系导师进行本科科研等,强基计划只能是提供一个平台,具体个人的发展还是得靠自己的奋斗((( 58 | 59 | 既然说到了这些比较现实的因素,那大家可以看看这份 [清华大学 2021 转专业结果](https://www.zhihu.com/question/455564234),以供参考。 60 | 61 | ### 其他影响择校因素 62 | 63 | --- 64 | 65 | 列举一些以供参考: 66 | 67 | 1. 转专业方便程度 68 | 69 | 2. 宿舍条件:24 小时热水,上床下桌,卫生间条件,晚上是否断网断电,空调是否自行决定开关 70 | 71 | 3. 地理位置:是否方便,离家远近等 72 | 73 | 4. ~~男女比例~~ 74 | 75 | 5. 规章制度是否严格,是否自由(比如点不点名) 76 | 77 | 以上信息有条件的可以找认识的问问,没条件的话可以多看看想报的学校的贴吧以及知乎评价。 78 | 79 | --- 80 | 81 | ## 开学过渡篇 82 | 83 | 本篇内容将简要带来大学的生活概要,以便各位提前准备,适应。 84 | 85 | ### 暑假 86 | 87 | --- 88 | 89 | 暑假主要活动当然就是玩,不过如果你**非要**想学习的话,这里推荐几个可以考虑的: 90 | 91 | 1. 数学:数列极限的 $\varepsilon-N$ 语言,函数极限的 $\varepsilon-\delta$ 语言,如果你开学后第一学期要学线性代数,可以提前看一下 3blue1brown 做的 [线性代数的本质](https://www.bilibili.com/video/BV1ys411472E),或者 MIT 的 [线代课程](https://www.bilibili.com/video/BV1zx411g7gq?p=3),国内的线性代数教材普遍忽视了一些几何的东西,可能理解起来有点难受。 92 | 93 | 2. 程序设计:不同学校通修的语言可能不太一样,不过也没必要提前学太多,大概看看 B 站网课有个基本的理解就行了。如果你想对计算机科学,而不仅是编程,有更深的理解,这里推荐一下哈佛的 [CS50](https://www.bilibili.com/video/BV1Rb411378V) 课程,是一门类似于讲座性质的公开课,这视频有点老了,不过还是挺经典。 94 | 95 | ### 军训 96 | 97 | --- 98 | 99 | 这应该是大部分同学与大学的第一次接触了,一般时间也就两周左右,基本内容从晒太阳到练习军体拳到聊天到展示才艺不等,几乎等价于一场大型体育课,以下几点注意一下: 100 | 101 | 1. 军训可能采用的是那种平底鞋,站起军姿属实又麻又累,强烈建议提前准备一次性鞋垫,而且一般一次可以多垫几张,如果没有准备的话,~~反正我校每年都是有男生军训几天受不了去超市买卫生巾的~~ 102 | 103 | 2. 防晒:站在大太阳底下两周对皮肤造成的~~荼毒~~和平时完全是不能比的,因此强烈建议提前准备防晒霜之类的,~~男孩子也要爱护好自己的皮肤~~,本人当时头铁,结果袖子内外的皮肤晒出了明显的分界线,过了好几个月才逐渐模糊 104 | 105 | 3. 降温:可选,不是很必要,因为假如训练量大了,教官一般还是比较好心的,会有休息时间的,而且别人都在训练,你一个人贴清凉贴吹电风扇似乎也有点奇怪((( 106 | 107 | 以上主要是提醒一下对自己好一点,相信到时候你们妈妈应该也会跟你们说这些的,这里是稍微再强调一下。 108 | 109 | ### 寝室 110 | 111 | --- 112 | 113 | 大概会成为各位在大学待最久的地方?(不过如果你天天去图书馆卷或者在实验室搞科研那确实例外)宿舍条件这玩意选完学校就已经定下来了,是否有 24h 热水,是否上床下桌,独立卫浴... 本篇主要安利一些(可能有用)小东西。 114 | 115 | 1. 小风扇:推荐选那种可以手持又可以放桌上的那种,风量基本够用,反正可以往近了放。 116 | 117 | 2. 插线板:提醒一下买大一点的,以免不够用。 118 | 119 | 3. 理线器:那种可以粘在桌子后面桌子底下的,建议桌上各种数据线鼠标线电线如果过长了都可以绕到桌子后面走一下线,让桌子整洁一点。 120 | 121 | 4. 键盘鼠标:如果没买的话,建议买无线的,绝对比有线的方便了不少,然后如果不打游戏,对键盘鼠标手感没太高追求的话可以买那种宣传静音的,去图书馆学习时不会打扰别人。 122 | 123 | --- 124 | 125 | ## 开学学习篇 126 | 127 | ### 电子产品选购与使用 128 | 129 | --- 130 | 131 | 这里介绍学习时可能会需要的一些电子产品。由于苹果的产品比较特殊,推荐: 132 | 133 | 1. 要么买全套(iPhone,iPad,MacBook)中的至少两件(但考虑大家有打游戏等需求,可能笔记本更适合买 Windows 系统的),以感受苹果生态带来的联动 134 | 135 | 2. 要么就分开选购:手机平板选择安卓,电脑选择 Windows,或者如果电脑没有打游戏的需求,专业也不需要特别强的电脑配置的话,可以考虑买带有触屏的轻薄本,同时作为电脑和平板。(当然,对于学习而言,平板电脑不是必须的) 136 | 137 | #### 笔记本电脑 138 | 139 | --- 140 | 141 | 由于近来挖矿逐渐盛行的原因,电脑显卡价格暴增,这对笔记本市场也产生了一定的影响。 142 | 143 | 这里建议如果家里有电脑的话就先用着吧,现在买笔记本大部分溢价严重,如果是买轻薄本可以考虑,买一些游戏性能比较好的独显笔记本实在不太值得。 144 | 145 | 考虑到大家选购笔记本的时间可能并不统一,为了保证本文时效性,建议有购买需求的同学可以去 b 站关注 up 主 [笔吧评测室](https://space.bilibili.com/367877) 146 | 147 | #### 平板电脑 148 | 149 | --- 150 | 151 | 平板电脑对于学习不是必选项,如果要买的话建议首先考虑自己手机品牌,苹果就买苹果,华为就买华为,别的随意((( 152 | 153 | 平板电脑如果用来学习,最主要的应用场景大概就是手写记笔记了:iPad 上会有很多好用的笔记软件,goodnotes,notability 等,华为在这方面的软件生态会稍微差一点。 154 | 155 | 其次就是看很多电子书以及论文(pdf),有个平板会方便很多。如果觉得自己是那种学一门科目就一定会去找参考书看的,那买个平板应该很能方便你:对于大部分主课,助教及老师往往都会给出推荐书目,这些书目大部分都是可以在学校图书馆网站或者别的地方找到电子版下载的,或者助教会直接提供电子版。 156 | 157 | 最后再提醒一下,平板电脑对于学习不是必须的,买之前建议考虑清楚你买的平板会不会变成“买前生产力,买后爱奇艺”((( 158 | 159 | ### 学习规划 160 | 161 | --- 162 | 163 | #### 名词介绍 164 | 165 | --- 166 | 167 | **绩点**: 168 | 169 | 你所修读的每一门课程都会按照期末总评(一般参考考试成绩和平时分)给出一个对应的成绩档位,比如采用 95-100 分对应 4.3, 90-94 对应 4.0, 85-89 对应 3.7...... 170 | 171 | 那么对于所有你所修读的要计算成绩的课程,按照学分对每门课所取得的成绩档位进行加权平均,即得到你的绩点(GPA)。计算公式: 172 | $$\frac{\sum score_i * credit_i}{\sum credit_i}$$ 173 | 对于校内评比,往往 GPA 是很重要的一个因素。或者有的学校会单纯将分数按照学分加权平均(略去转换每门课绩点的过程),以总加权平均分作为校内评比依据。 174 | 175 | **通修课,专业课,选修课**: 176 | 177 | 基本上你要学习的课程可以分为两类,一类是培养计划内的,统称为必修课,另一类是培养计划要求之外的,统称为选修课。顺利毕业除了要求修完必修学分之外,往往还要求选修学分达到一定数目。 178 | 179 | 必修课中有一部分是数理基础课,体育课,英语课,思政课等通修课,还有一部分则是与你专业有关的专业课。 180 | 181 | #### 选课 182 | 183 | --- 184 | 185 | 这里建议第一学期默认置课基本没啥必要再动了,以适应为主,选课往往专业课看讲的怎么样,一些选修课看有没有趣,给分好不好。这些东西怎么判断建议询问对应学校学长学姐经验(进校再问肯定不迟,或者这些东西在学校的一些 qq 大群中询问一般都能得到回答)。 186 | 187 | #### 一些时间节点 188 | 189 | --- 190 | 191 | 一般来说规划是大一大二打好专业基础,大二大三有兴趣可以发邮件找导师做点科研,考虑工作实习的话大三也要开始准备了。 192 | 193 | 以上规划也需要具体专业具体分析,比如数学专业本科学到的内容大概是不足以接触一些前沿的东西的,所以~~主要就是学~~。 194 | 195 | 如果想出国考虑学英语的话,视自己英语水平而定,一般来说这些英语考试(托福,雅思,GRE)证书有效期有 2 年,所以往往稳妥打算,在大二的寒假及春季学期可以考虑针对性学习,考试(因为要申请国外学校的话可能大四一开始甚至更早就要着手申请)。平时的话能多积累点词汇,听听听力自然更好。 196 | 197 | ### 学习心态 198 | 199 | --- 200 | 201 | 诗云: 202 | 203 | > 晚上熬夜冲浪,早上不想起床。 204 | > 完全不敢逃课,前排上网很忙。 205 | > 知乎微博豆瓣,B 站白嫖之王。 206 | > 催利老师营业,建议爽子改行。 207 | > 中午想吃什么,西区芳华食堂。 208 | > 回寝开始游戏,作业完全不慌。 209 | > 三点开始午觉,四点上课匆忙。 210 | > 课上继续游戏,输到头昏脑涨。 211 | > 晚上大物实验,做到直接骂娘。 212 | > 回寝先买夜宵,冰粉不加红糖。 213 | > 抹嘴拿出手机,数据扔在一旁。 214 | > ddl 马上截止,自习室装模作样。 215 | > 微积分学习指导,看懂的不过几行。 216 | > 遇题不会求群友,群友个个是卷王。 217 | > 一般方法就不讲,wolfram 它不香? 218 | > 实验报告不会写,祖传张力帮大忙。 219 | > 凑完作业连诉苦,享受网抑云时光。 220 | > 生而为人很抱歉,失去梦想不应当。 221 | > 复习一天练习生,头脑空空上考场。 222 | > 考完快乐把号上,成绩下来涕泗淌。 223 | > 痛心疾首狂饮醉,愧对自己愧爹娘。 224 | > 明日立志当勤学,闻鸡起舞好儿郎。 225 | > 闹钟六点开始响,愣是九点没起床。 226 | > 和大多数人一样,三十没牵过姑娘。 227 | > 隔壁寝室的兄弟,和 npy 得郭奖。 228 | > 游戏水准直线升,英语能力指数降。 229 | > 能力不如田舍郎,幻想能登天子堂。 230 | > 问我所得有几何?肥西路上划水王。 231 | > 故表情包有云: 232 | > 读书人,混子人,看完忘一半是基本。学到一半群聊见,手机三分钟瞄一眼,装模作样读书人! 233 | > 中国科大学生星云诗社 - 刚体小咸鱼 投稿 234 | 235 | 各位大学的学习生活其实理论上来说是相当自由的:不会有班主任天天盯着你学习,甚至上不上课往往都看你个人意愿(~~本人已经好几节近现代史纲要课没去上了~~)。 236 | 237 | 不过出于种种原因,不管是想获得一个好看的成绩,还是周围同学都付出了许多努力,往往会有很多因素会推动你去“应试性的学习”。然而就算获得一个好成绩,也不代表真正掌握了一门课的内容。且如果这种学习不是出于你的本意,那想必你也不会很快乐。本部分内容主要是希望各位能够不要太应试性的学习。 238 | 239 | 出于不再重复造轮子的原则,这里仅提供一篇文章以供拓展阅读: 240 | [上海交通大学生存手册](https://survivesjtu.gitbook.io/survivesjtumanual/) 241 | -------------------------------------------------------------------------------- /posts/vue3-fastapi.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: vue3-fastapi 简易开发体验 3 | date: 2022-03-01 14:20:02 4 | tags: [vue, fastapi, 异步] 5 | category: web 6 | mathjax: true 7 | tocbot: true 8 | --- 9 | 10 | 温故而知新,本文将借助比较现代化的开发流程(vue-cli, vue3, fastapi)重构之前的一篇简易备忘系统 11 | 12 | 13 | 14 | ## 前端 15 | 16 | 前端借助 Vue CLI 搭建起 Vue 项目,并对原先内容进行迁移 17 | 18 | ```bash 19 | vue create hello-world 20 | ``` 21 | 22 | 后即可修改 Vue 项目 23 | 24 | ### Vue 25 | 26 | 在介绍 Vue3 之前,首先需要介绍一下 Vue 框架的基本思想。 27 | 28 | 原先,DOM (html 文档) 中显示的数据和 JS 中的变量并没有绑定关系,因此,每次变量改变(包括从后端获取数据)都需要重新操作 DOM, 更新数据 29 | 30 | Vue 对此进行了简化,这是怎么做到的呢? 31 | 32 | 从逻辑上来说,设 State 是当前所有应用(网页)中所有数据的集合,View 是用户看到的 ui 界面,它们之间应该具备一个单向的函数关系 $View = f(state)$ 33 | 34 | Vue 所做的工作即为自动描述了这一函数关系,使得 HTML 文档中显示的元素可以通过 Vue 提供的模板语法 `{{ }}` 与 State 中的变量进行绑定,比如如果我想在页面某处显示脚本中的值 `x`,那 HTML 对应位置直接写 `{{ x }}` 即可。加上 Vue 提供的 `v-if` 和 `v-for` 之类的模板控制流,使得用户可以专注于数据的操作,而无需担心这些数据怎样更新到页面上 35 | 36 | 具体实现上,Vue2 采用 `data、computed、methods、watch` 等组件,被称为响应式 API: 37 | 38 | - data 即为 $State$ 集合,包含了所有该页面需要用到的数据 39 | - methods 是一些方法(函数)的集合,可以用于处理页面点击事件,更新数据等 40 | - computed 为计算属性,可以理解一个语法糖(当然,具体实现上不是语法糖)。如果页面上一个元素的内容 $a$ 依赖数据 $x$, 具体关系为 $a = f(x)$, $f$ 是一个很复杂的函数,直接写模板 `{{ f(x) }}` 既麻烦又表意不明,这个时候可以设置计算属性 $y = f(x)$,即可通过 `{{ y }}` 达到自己想要的效果 41 | - watch 用于自动检测页面上元素的变化,可以在检测到用户的操作之后调用相应的 methods 中的函数 42 | 43 | ### 组合式 API 44 | 45 | Vue2 在功能上已经很完善,但是一个很大的弊端是如果不注意拆分组件(页面),一个组件文件可能会非常长,甚至上千行。试想一个界面内有很多元素,每个元素都有对应的数据和用户操作界面的方法,那么 `data、computed、methods、watch` 中会有页面里不同模块的内容混杂,一方面可读性较差,另外一方面太长的文件也不方便编辑。 46 | 47 | 对此,Vue3 相对于原先的响应式 API,引入了组合式 API,目的就是为了将操作页面中同一模块的 JS 逻辑整合到一起。 48 | 49 | `data、computed、methods、watch` 在这一改变后被统合到了一个 `setup` 组件。 50 | 51 | 先看一下重构之后的备忘系统前端逻辑: 52 | 53 | ```html 54 | 84 | ``` 85 | 86 | 再来逐个部分解析。 87 | 88 | #### 前后端通信 89 | 90 | 首先是三个前后端通信的接口,这里先忽略 `async` 只要知道它们能请求数据即可。 91 | 92 | ```html 93 | 119 | ``` 120 | 121 | #### 数据绑定 122 | 123 | 接下来讨论一下 $View = f(state)$ 如何实现。 124 | 125 | 主要实现方法是 `ref`,这是为了给指定的数据创建引用。为什么要创建引用以及引用的使用可以参见官方 [组合式 API 文档](https://v3.cn.vuejs.org/api/composition-api.html), 这里注意组合式 API 是兼容原先响应式 API 的, ` 152 | ``` 153 | 154 | #### 钩子 155 | 156 | 再来看 `onMounted(getNotes);` 这一行。Vue 提供了一些特殊的钩子,`onMounted` 代表组件加载完毕后会执行的语句。这里我们希望组件加载完成后直接加载所有笔记数据。关于钩子,是 Vue 的一个重要特性,本文不在此讨论。 157 | 158 | ```html 159 | 183 | ``` 184 | 185 | #### 页面模板 186 | 187 | 至此,前端的逻辑就基本分析完毕。下面考虑页面模板的编写: 188 | 189 | ```html 190 |
    191 |
    暂无数据
    192 |
  • 197 | {{ note.content }} 198 | 199 | {{ note.create_time }} 200 |
  • 201 | 增加备忘: 202 | 203 |
204 | ``` 205 | 206 | 可以看到 `v-if` 和 `v-for` 的方便之处。 207 | 208 | #### 补充:异步 209 | 210 | 异步名字看起来很高大上,但原理没有这么复杂。本质就是因为浏览器对某个接口的请求可能会耗费较长的时间(比如我们用 JS 下载一个文件,可能需要好几秒),这个期间我们希望 JS 能继续执行。因此,我们需要一个 **回调函数** ,在接口请求完成后继续执行这个回调函数,来完成与后端接口通信结束之后的处理。 211 | 212 | 举例来说,逻辑大致是这样: 213 | 214 | ```javascript 215 | func1 = ... 216 | func2 = ... 217 | func3 = ... 218 | func4 = ... 219 | 220 | func1() 221 | 222 | fuc2(请求的 url 和相关参数,func3) 223 | 224 | func4() 225 | ``` 226 | 227 | 实际执行中,func1 执行完后来到了一个请求后端接口的函数 func2,func2 在请求后端数据的同时,JS 的运行并不会阻塞,而是会继续从 func4 往后执行。直到请求完成,才会执行 func3(比如用于处理得到的数据) 228 | 229 | async/await 是对以上的异步过程的简化。这里我们不去阐述 JS 的 Promise 机制,而单从使用上理解:对于一个异步函数(比如调用后端接口),我们可以用 await 来 "等待" 这一函数调用完毕。await 之后的内容起到了与回调函数类似的作用,会在异步函数调用完后再执行。 230 | 231 | 比如以下代码(这并不实际生效,下面再解释) 232 | 233 | ```javascript 234 | function foo() { 235 | const resp = await axios.get(url); // GET 请求的结果会被存在 resp 中 236 | // 接下来可以处理 resp 237 | } 238 | ``` 239 | 240 | 但注意的是,`foo` 调用了一个异步函数,所以 `foo` 的执行会消耗较长时间,于是它也变成了一个异步函数。为了标识这一点,我们给 `foo` 注明 `async` 241 | 242 | ```javascript 243 | async function foo() { 244 | const resp = await axios.get(url); // GET 请求的结果会被存在 resp 中 245 | // 接下来可以处理 resp 246 | } 247 | ``` 248 | 249 | `foo` 是一个异步函数,所以也可以再写一个异步函数处理 `foo` 返回的数据: 250 | 251 | ```javascript 252 | async function bar() { 253 | const res = await foo(); 254 | // 接下来处理 res 255 | } 256 | ``` 257 | 258 | 若调用 `bar`,实际会依次执行 `axios`, `foo`, `bar` 中的逻辑,但是我们的代码中并没有出现嵌套,使得异步代码看起来与同步代码类似,很清爽 259 | 260 | ## 后端 261 | 262 | 本文采用 uvicorn + fastapi 在服务器上部署。部署具体可以参考 fastapi 文档。 263 | 264 | ```bash 265 | pip install fastapi 266 | pip install uvicorn[standard] 267 | vim main.py 268 | uvicorn main:app --host 0.0.0.0 --port 80 # for example 269 | ``` 270 | 271 | fastapi 官方文档的说明非常清楚,直接贴代码: 272 | 273 | ```python 274 | from fastapi import FastAPI 275 | from fastapi.middleware.cors import CORSMiddleware 276 | 277 | from pydantic import BaseModel 278 | 279 | 280 | class Note(BaseModel): 281 | content: str 282 | create_time: str 283 | 284 | 285 | app = FastAPI() 286 | app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], 287 | allow_headers=["*"],) 288 | 289 | notes = [] 290 | 291 | 292 | @app.get("/notes") 293 | def read_notes(): 294 | return notes 295 | 296 | 297 | @app.post("/notes") 298 | def append_note(note: Note): 299 | notes.append(note) 300 | return notes[-1] 301 | 302 | 303 | @app.delete("/notes/{id}") 304 | def delete_note(id: int): 305 | return notes.pop(id) 306 | 307 | ``` 308 | 309 | 这一行是用来设置跨域 310 | 311 | ```python 312 | app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], 313 | allow_headers=["*"],) 314 | ``` 315 | 316 | `@app.get("/notes")` 代表使用 `GET` 访问 `/notes` 接口时会调用的函数。这里介绍几个常见的访问接口的方式: 317 | 318 | - GET: 获取信息 319 | - POST: 添加信息 320 | - PUT: 添加/更新信息,需要保证调用 n 次和调用 1 次的结果相同,因此常用于更新数据 321 | - DELETE:删除数据 322 | 323 | ## 画饼 324 | 325 | 啥时候用 Vue3 + fastapi 把自己博客重构一遍() 326 | --------------------------------------------------------------------------------