·2024· Year-End Review

People often say they want to go back to the past because they’re not happy with the present—but honestly, I’ve never really wanted to go back. But there will probably be many, many moments in the future when I’ll really, really want to go back to 2024. Winter: In Edmonton I left five days after my last exam in the first semester of senior year. To prepare for what I thought would be the coldest winter of my life (Edmonton’s low had dropped to -45 degrees Celsius before I got there), I wisely purchased: ...

December 29, 2024 · 27 min · 5633 words · Me

Microsoft Intern Summary: Merging Servers in the VSCode Gradle Extension

Project Background Communication Between Servers Architecture Before the Merge Why Merge? How to Merge Architecture After the Merge Step 1: Merging Task Server and Build Server Challenges Connection Workflow Step 2: Merging Language Server to Gradle Server Performance After Merge How Was Performance Measured? Pre-Merge Memory Monitoring Post-Merge Memory Monitoring Performance Results Small Medium Large Super Large Project Background During my internship at Microsoft, I worked on the VSCode Gradle Extension. This extension was initially divided into three components: ...

August 3, 2024 · 6 min · 1132 words · Me

How to Build a Simple AI Conversational Assistant Based on Langchain.js

本科的最后一门专业课:软件开发实践,要开发一个虚拟宠物医院学习系统,需要实现一个AI对话助手,具体的需求比较模糊。考虑到其他部分都是很低级的增删改查,因此一开始的想法就是能否将增删改查与AI对话相结合,提问的话问数据库中上传的药品/病例信息。 具体架构还是采取了RAG架构,本着一切从简的原则采取了Langchain.js纯前端实现,用OpenAI提供的api接口和Pinecone提供的远程向量数据库服务。 What is RAG(@GPT4): RAG(Retrieval-Augmented Generation)是一种结合了信息检索和生成模型的技术,旨在提高自然语言处理(NLP)任务中的生成质量和相关性。它通过利用外部的知识源(如文档数据库或信息库)来增强生成模型的能力,特别适用于问答、摘要、翻译等任务。 RAG的工作原理 RAG模型主要由两部分组成:检索器和生成器。 检索器: 检索器的任务是从一个大型的文档集合中检索出与输入查询最相关的文档或信息片段。这通常通过向量相似性搜索实现,其中查询和文档都被编码为向量,通过计算向量之间的相似度来找到最匹配的条目。 生成器: 生成器通常是一个基于 Transformer 的语言模型,如 GPT 或 BERT。它使用检索到的文档作为上下文信息来生成答案。这使得生成的内容不仅依赖于模型内部的知识(训练数据中学到的),而且还依赖于外部的、具体的、与当前查询直接相关的信息。 RAG的优势 信息量更丰富:与传统的生成模型相比,RAG通过访问外部数据库增加了处理问题时可以利用的信息量。 准确性和相关性提高:在依赖外部知识进行回答的任务中,RAG可以提供更准确、更详细的答案。 灵活性:可以根据需要访问任何形式的结构化或非结构化数据源。 应用场景 RAG模型在多种应用中都非常有效,例如: 问答系统:能够生成详细的答案,尤其是在需要引用外部信息源进行事实查证时。 内容摘要:从一组相关文档中提取关键信息,生成凝练的摘要。 文章写作:在创建文章内容时,能够参考和整合多种资料和报告,提高内容的丰富性和深度。 技术挑战 尽管RAG技术提供了许多优势,但实现它也面临一些挑战: 检索效率:高效检索相关文档对于实时应用尤为关键,需要优化索引和查询处理。 数据同步:确保所依赖的外部知识库是最新的,这对于保证生成内容的准确性和相关性非常重要。 结果的一致性:生成内容需要与检索到的文档保持一致性,避免产生误导信息或错误。 具体实现 首先在Pinecone官网注册一个账号(省略了) Pinecone中是分一个project可以有很多index,一个index下可以划分很多namespace。由于用的免费版只能有一个project,一个index,因此只能通过定义namespace的方式来区别不同的部分。 例如本项目有药品(medicine)和化验(lab)的查询,下面定义了这两个namespace。namespace是插入时的参数,不用手动设定。查询时可以在智能namespace下查找,十分方便。 对于每条record,namespace和ID不可修改,但是可以根据ID来更新其中的Values和metadata(就是下面的Key-Value键值对。 对于Values,就是执行embedding时转换后的数组。我前端用的是OpenAI的text-embedding-ada-002,转换后有1536维,因此这里的length就是1536。 查阅Pinecone的API接口,先写插入的函数,其中upsert就是首先看id是否存在,存在的话就更新,否则就插入。 export const pineconeAdd = async (id, namespace, input_text, metadata) => { const embedding = await embedText(input_text); try { const insertResponse = await index.namespace(namespace).upsert([ { id: `${namespace}${id}`, values: embedding, metadata: metadata } ]); return { success: true, message: 'Pinecone 添加成功', detail: insertResponse }; } catch (error) { return { success: false, message: 'Pinecone 插入错误', error }; } }; 前端插入部分的逻辑,这部分跟往Mysql插入同时执行。更新也是一样: ...

April 22, 2024 · 2 min · 289 words · Me

Leetcode record - July 2023

关于区间集合: 这个问题的难点在于如何有效地处理会议的冲突。具体来说,需要在每个时间点追踪哪些会议正在进行,然后决定是否需要额外的会议室。下面是一些思考的建议和线索: 排序:这是一个重要的线索。当你看到这种需要比较或者组织时间区间的问题时,排序通常是一个有用的第一步。在本问题中,按照开始时间对会议进行排序可以帮助我们逐个地处理会议,确保我们先处理开始时间早的会议。 优先队列(最小堆):在处理一系列事件,其中每个事件都有开始和结束时间,并且可能会发生冲突的情况下,优先队列(特别是最小堆)经常会被用到。在本问题中,我们需要知道当前正在进行的所有会议中最早结束的那一个,以决定是否需要一个新的会议室。最小堆正好可以提供这个功能,因为它总是把最小的元素放在前面。 贪心思想:这是一个隐藏的线索。这个问题可以通过一种贪心的方式来解决,即总是尽可能地复用会议室。具体来说,每次当一个新的会议开始时,如果有一个会议已经结束,那么我们就可以在同一个会议室举行这个新的会议,而不需要新开一个会议室。这个策略保证了我们总是使用最少数量的会议室。 重叠区间问题:类似的问题经常出现在算法问题中,所以可以作为一个模式来识别。当你看到需要处理重叠区间的问题时,可以想到使用排序和优先队列来解决。 在碰到这类问题时,练习和经验也很重要。一开始可能不容易看出解决方案,但通过解决更多的类似问题,你将能够更容易地识别出相应的模式,并找到正确的方法来解决这类问题。 253. Meeting Rooms II Given an array of meeting time intervals intervals where intervals[i] = [starti, endi], return the minimum number of conference rooms required. Example 1: Input: intervals = [[0,30],[5,10],[15,20]] Output: 2 Example 2: Input: intervals = [[7,10],[2,4]] Output: 1 Constraints: 1 <= intervals.length <= 104 0 <= starti < endi <= 106 class Solution { public int minMeetingRooms(int[][] intervals) { Arrays.sort(intervals, (a, b)->Integer.compare(a[0], b[0])); PriorityQueue<Integer> heap = new PriorityQueue<>(); heap.add(intervals[0][1]); int res = 1; for(int i = 1;i<intervals.length;i++){ if(intervals[i][0]>=heap.peek()){ heap.poll(); }else{ res++; } heap.add(intervals[i][1]); } return res; } } 这段代码的主要结构和组成: ...

July 29, 2023 · 5 min · 940 words · Me

Use Swagger to show API in Django

Use Swagger to show API in Django Document: https://drf-yasg.readthedocs.io/en/stable/readme.html#usage A good example: https://debrajbhal.hashnode.dev/adding-custom-documentation-to-apis-in-django-rest-framework-using-drf-yasg Add Post parameters: https://stackoverflow.com/questions/50929098/django-rest-framework-how-to-add-post-parameters-to-api-documentdrf-yasg Hard code response parameters: https://stackoverflow.com/questions/62914407/drf-yasg-how-to-hide-django-rest-framework-schema #import from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from rest_framework.decorators import api_view For GET method: #CBV version class TsView: @staticmethod @api_view(['GET']) #it depend. Add it when you choose to write CBV @swagger_auto_schema( operation_description="get ts info based on ts number", responses={ 200: openapi.Response("request success", schema=openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_OBJECT))), 400: "request failed", }, ) def get_ts_info(request, ts_num): """ Retrieve the detailed information of a specific ts by its number. The returned information includes ts number, title, summary, series, and series title. """ For POST method: ...

July 29, 2023 · 2 min · 245 words · Me

Leetcode record - June 2023

624. Maximum Distance in Arrays You are given m arrays, where each array is sorted in ascending order. You can pick up two integers from two different arrays (each array picks one) and calculate the distance. We define the distance between two integers a and b to be their absolute difference |a - b|. Return the maximum distance. Example 1: Input: arrays = [[1,2,3],[4,5],[1,2,3]] Output: 4 Explanation: One way to reach the maximum distance 4 is to pick 1 in the first or third array and pick 5 in the second array. Example 2: ...

June 28, 2023 · 3 min · 621 words · Me

Leetcode record - March 2023

Binary Search Find left bound/right bound public int[] searchRange(int[] nums, int target) { return new int[]{helper(nums, target, true), helper(nums, target, false)}; } public int helper(int[] nums, int target,boolean trueIfSearchLeftBound){ int l = 0; int r = nums.length-1; int res = -1; while (l<=r){ int midl = (l+r)/2; if (nums[midl]>target){ r = midl-1; }else if(nums[midl]<target){ l = midl+1; }else{ res = midl; if (trueIfSearchLeftBound){ r = midl-1; }else{ l = midl+1; } } } System.out.println(res); return res; } Find minimal value ...

March 21, 2023 · 5 min · 1022 words · Me

基于Vue-Springboot的论文管理系统

演示地址:ECNU-2022Spring-DatabaseTermProject第四组_哔哩哔哩_bilibili 关于登录信息、权限显示 这里利用sessionStorage将用户的信息跟权限(理论上应该一起作为user信息返回的,这里分开存储。后续页面左侧Aside的访问也可以根据存储的sessionStorage解决。 *sessionStorage 属性允许你访问一个 session Storage 对象。它与 localStorage 相似,不同之处在于 localStorage 里面存储的数据没有过期时间设置,而存储在 sessionStorage 里面的数据在页面会话结束时会被清除。页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。**在新标签或窗口打开一个页面时会在顶级浏览上下文中初始化一个新的会话,*这点和 session cookies 的运行方式不同。 request.post("/api/login", this.form).then(res => { console.log(res) if (res.code === 0) { console.log(3333) this.$message({ type: "success", message: "登录成功" }) sessionStorage.setItem("user",JSON.stringify(res.data)) // 缓存用户信息 request.post("/api/permission?user_id=" + res.data.user_id).then(res1 => { console.log(res1) console.log("permission Get") sessionStorage.setItem("userPermission",JSON.stringify(res1)) // 缓存用户信息 let userStrr =sessionStorage.getItem("userPermission") || "{}" this.permissionList =JSON.parse(userStrr) activeRouter() this.$router.push("/") //登录成功之后进行页面的跳转,跳转到主页 }) // 登录成功的时候更新当前路由 //activeRouter() } else { this.$message({ type: "error", message: res.msg }) } }) 关于路由配置 需要避免任何用户都能通过改url的方式访问对应页面,因此考虑如果此用户没有访问该页面的权限,便不进行路由加载。采取循环的方式进行路由注册。(addRoute) 同时采用foreach的方式进行权限控制。 非常简洁的写法 function activeRouter() { const userStr =sessionStorage.getItem("userPermission") if (userStr) { const userPermission =JSON.parse(userStr) let root = { path: '/', name: 'Layout', component:Layout, redirect: "/home", children: [] } userPermission.forEach(p => { let obj = { path: p.path, name: p.name, component: () => import("@/views/" + p.name) }; root.children.push(obj) }) if (router) { router.addRoute(root) } } } router.beforeEach((to, from, next) => { if (to.path === '/login' || to.path === '/register') { next() return } let userPermission = sessionStorage.getItem("userPermission") ? JSON.parse(sessionStorage.getItem("userPermission")) : {} if (!userPermission || !userPermission.length) { next('/login') } else if (!userPermission.find(p => p.path === to.path)) { next('/login') } else { next() } }) 关于前端分页 这里利用了elementUi,因此分页操作非常简洁,只需要修改:data取值,利用js的slice函数。(之前傻乎乎写了一堆..)。 ...

February 26, 2023 · 2 min · 381 words · Me

2022年度总结

如果要总结我的2022,那么疫情一定是跨不过的话题。我在这一年的多数成长、改变、思考、情绪、迷茫,都与它紧密相连——尽管我并不想要这样。‍‍ 从来没想到2022会以这样的方式展开。一年前的我不会想到上半年会在上海经历那样的封控,下半年润到加州获得了短暂的自由与成长。今年是这几年来心智成长和改变最多最多的一年。 一月份因为下楼遛弯时xdc冷不丁的一句 “我在考虑要不要备考托福,感觉将来多个选择”从而开始认真考虑未来的道路。 二月去三姨家拜年时听表姐狠狠的给我科普了一波留学找工,打破了我的信息壁垒,从而开始关注海外交流,准备语言成绩。 三月中赶在封城前一周在亲戚家极限出分。 然后经历了改变我人生的两个月。经历了才会懂。 六月卡bug5天石家庄隔离回北京2天居家后成自由人,成为“回京最速隔离比赛”冠军(笑)。 七月见缝插针找了个纯刷简历的实习。 八月飞加州,开启了一段新的人生体验。在美国这边的学习从一开始就还蛮适应的。 九月实现了厨艺的飞跃,顺便体验了下托福考试。 十月忙于各种midterm。 十一月看了此生难忘的s12决赛,逛了三番。 十二月顺利结束final,成绩还算满意。年底在阿拉斯加和夏威夷中纠结了1小时后还是跟舍友选择了后者,做了次说走就走的旅行。 某种意义上讲,我在这一年还是很幸运的。我没有在春季像同校许多同学二十几天无法洗澡,没有在夏季像许多人返乡那样浪费了很多金钱和时间,没有在秋季学期仍然面临封控的折磨,没有在冬季得新冠。 今年最大的收获应该是我不会再像以前那样焦虑了。那种“我必须要做到某件事”之类的想法没有那么强烈了——拜封城所赐。‍ 那段时间最大的non-political的感受是人真的很难掌控自己的命运。很多事件社会造成的巨大影响就是可以轻易的改变和扭转人的一生。 我直到目前为止的人生路径有点儿太“心想事成”了,没有真正经历过那些自己无法把握的事情。一方面可能是我对自己水准的深刻理解让我总能设定一个相对合理的目标,另一方面是确实运气比较好。这使得我有点过于看重事情的成败,过于“在乎”自己了。这让从前的我在做很多事情的时候都很害怕失败,往往过程也都很痛苦——虽然大多数的结果都是好的。 但是人一辈子一直重复这样的过程真的有意义吗?人生中真的有那么多非常重要,非做不可,必须要做的事情吗? 我很害怕一辈子都在逼着自己做“应该做”的事。比如一定要进大厂,一定要进名校,一定要爬到更高的职位,一定要保持/提升阶级…哪怕最后都能实现,到头来回首望去,好像稀里糊涂一辈子就过完了。 于是在下半年,我尝试着忘掉这些事情。忘掉那些看起来必须要做的事情,不再赋予人生宏大的意义。试着专注眼下,专注让此刻的自己开心一点。 今年还沉浸式体验了现行社会体制下的决策过程。要说对未来充满希望那属于沾点自欺欺人了。而润掉真的能解决问题变得开心吗?我也不是很确定。 以前一直以为人可以一辈子远离政治,现在发现政治永远跟每个人的命运息息相关。就算润到国外,我估计生活十几年后也可能会在见识西方社会黑暗面后再回到国内。如果毕业就回国,那也可能更深层次的见识到制度的黑暗然后再润出去,最后得出“人类还是尽早灭亡吧”的结论(笑)。 2023 best luck👋

December 31, 2022 · 1 min · 26 words · Me

[转载]关于时间复杂度与P、NP问题

文章原地址@matrix67 关于时间复杂度: “时间复杂度并不是表示一个程序解决问题需要花多少时间,而是: 当问题规模扩大后,程序需要的时间长度增长得有多快。 对于高速处理数据的计算机来说,处理某一个特定数据的效率不能衡量一个程序的好坏,而应该看当这个数据的规模变大到数百倍后,程序运行时间是否还是一样,或者也跟着慢了数百倍,或者变慢了数万倍。不管数据有多大,程序处理花的时间始终是那么多的,我们就说这个程序很好,具有O(1)的时间复杂度,也称常数级复杂度;数据规模变得有多大,花的时间也跟着变得有多长,这个程序的时间复杂度就是O(n),比如找n个数中的最大值;而像冒泡排序、插入排序等,数据扩大2倍,时间变慢4倍的,属于O(n^2)的复杂度。还有一些穷举类的算法,所需时间长度成几何阶数上涨,这就是O(a^n)的指数级复杂度,甚至O(n!)的阶乘级复杂度。不会存在O(2n^2)的复杂度,因为前面的那个“2”是系数,根本不会影响到整个程序的时间增长。同样地,O (n^3+n^2)的复杂度也就是O(n^3)的复杂度。因此,我们会说,一个O(0.01n^3)的程序的效率比O(100*n^2)的效率低,尽管在n很小的时候,前者优于后者,但后者时间随数据规模增长得慢,最终O(n^3)的复杂度将远远超过O(n^2)。我们也说,O(n^100)的复杂度小于O(1.01^n)的复杂度。 容易看出,前面的几类复杂度被分为两种级别,其中后者的复杂度无论如何都远远大于前者:一种是O(1),O(log(n)),O(n^a)等,我们把它叫做多项式级的复杂度,因为它的规模n出现在底数的位置;另一种是O(a^n)和O(n!)型复杂度,它是非多项式级的,其复杂度计算机往往不能承受。当我们在解决一个问题时,我们选择的算法通常都需要是多项式级的复杂度,非多项式级的复杂度需要的时间太多,往往会超时,除非是数据规模非常小。”

August 13, 2022 · 1 min · 7 words · Me