use queue to allow send vscode_ws when it is none

This commit is contained in:
Cai
2025-11-03 13:51:15 +08:00
parent 6c38e9067d
commit ce88ae645e
8 changed files with 220 additions and 413 deletions

View File

@@ -14,6 +14,8 @@ from dataclasses import dataclass, field
from typing import Dict, Optional
import subprocess
import threading
from collections import deque
from eventlet.semaphore import Semaphore
from apps.utils import *
EMPTY_CHAPTER_CHAIN = [ Chapter(1, CHAPTER_FOCUS, "本章是未打开某个具体章节时的默认章节。", "处于本章节时不会有任何章节跳转。请作为一名经验丰富的算法教师,回答用户的问题。") ]
@@ -47,7 +49,9 @@ class ChatManager:
self.function_manager = FunctionManager()
self.bb = None
self.chat_historys = None
pass
self.vscode_ws = None
self._vscode_queue = deque()
self._lock = Semaphore(1)
def init(self,user_uuid,ase_client,raw_markdown,raw_markdown_prompts,raw_score_prompts,material_id,chapter_name, lesson_name, max_iter = 5, app=None, socketio=None):
@@ -65,8 +69,41 @@ class ChatManager:
self.bb = None
self.chat_historys = []
def send_to_vscode(self, event: str, data: dict, room: str = None, namespace: str = '/vscode'):
"""统一发送入口:未就绪则入队,就绪则直接 emit。"""
if room is None:
room = self.user_uuid
with self._lock:
if self.vscode_ws is not None:
# 已就绪,直接发
self.vscode_ws.emit(event, data, room=room, namespace=namespace)
else:
# 未就绪,入队缓存
self._vscode_queue.append((event, data, room, namespace))
def set_vscode_ws(self, ws):
"""在 on_login 时调用,注册 websocket 并立刻 flush 队列。"""
with self._lock:
self.vscode_ws = ws
self._flush_vscode_queue_locked()
def _flush_vscode_queue_locked(self):
"""发送缓存的消息(需在持锁状态下调用)。"""
while self._vscode_queue:
event, data, room, namespace = self._vscode_queue.popleft()
# 这里假设 ws 仍然有效;如果发送失败可以考虑 try/except 并回滚入队
self.vscode_ws.emit(event, data, room=room, namespace=namespace)
def mark_vscode_disconnected(self):
"""可在 on_disconnect 里调用,标记断开(之后会继续缓存)。"""
with self._lock:
self.vscode_ws = None
self._vscode_queue.clear()
def disconnect(self, uuid):
try:
self.mark_vscode_disconnected()
self.stop(uuid)
self.ase_client.disconnect()
except Exception as e:
@@ -77,7 +114,7 @@ class ChatManager:
nowchapter_content = self.chapter_chain[self.chapter_chain_now].chapter
code_texts = extract_code_blocks(nowchapter_content)
for code_text in code_texts:
self.vscode_ws.emit('code-file', {'type':'create', 'data':code_text, 'chapter_title': self.chapter_chain[self.chapter_chain_now].title}, room=self.user_uuid ,namespace='/vscode')
self.send_to_vscode('code-file', {'type':'create', 'data':code_text, 'chapter_title': self.chapter_chain[self.chapter_chain_now].title}, room=self.user_uuid ,namespace='/vscode')
def next_chapter(self):

View File

@@ -14,6 +14,9 @@ from ..services.user_service import get_or_load_current_user
class VSCodeNamespace(Namespace):
def on_login(self,data):
ex = current_app.extensions
print("VSCode client connected")
@@ -25,8 +28,7 @@ class VSCodeNamespace(Namespace):
print(f"User {user_uuid} connected with path: {path}")
join_room(user_uuid, namespace='/vscode')
chatmanager = ex['user_uuid2chatmanager'][user_uuid]
chatmanager.vscode_ws=self
chatmanager.set_vscode_ws(self)
def on_load_chapter(self, data):

View File

@@ -0,0 +1,172 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 你的其他样式 -->
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script>
MathJax.config = {
tex: {
inlineMath: [['$', '$'], ['\(', '\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
</head>
<body>
<h1>分治策略进阶与主方法</h1>
<blockquote>
<p>Auto-generated at 2025-09-22 15:44</p>
</blockquote>
<h2>分治策略进阶与主方法</h2>
<h3>最大子数组问题</h3>
<h4>任务:编码与分析</h4>
<h5>场景介绍</h5>
<p>我们的任务是分析每日交通流量的<strong>变化数组</strong>(正数代表流量增加,负数代表减少),并找到哪一个<strong>连续时段</strong>的流量总增量最大。这在算法上被称为“最大子数组问题” 。</p>
<p>例如,对于流量变化数组<code>[13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7]</code>总增量最大的连续时段是从第8天到第11天总增量为 <code>18 + 20 - 7 + 12 = 43</code>
虽然这个问题可以通过O(n2) 的暴力法求解,但我们将使用更高效的分治策略。</p>
<h5>题目:最大交通流量增量</h5>
<p>你需要实现一个分治算法来解决最大子数组问题。
同时,为了对比,你也会实现一个暴力求解算法。</p>
<h5>代码框架</h5>
<p>在下方代码编辑区,完成 <code>find_maximum_subarray</code>(分治法)和 <code>find_max_crossing_subarray</code> 以及 <code>find_maximum_subarray_brute</code>(暴力法)三个函数。</p>
<pre><code class="language-python">import time
import random
def find_maximum_subarray_brute(arr):
&quot;&quot;&quot;
使用暴力法求解最大子数组问题
返回: (最大和, 开始索引, 结束索引)
&quot;&quot;&quot;
#--- 本周任务:请在下方实现分治算法 ---
def find_max_crossing_subarray(arr, low, mid, high):
&quot;&quot;&quot;
找到跨越中点的最大子数组
返回: (最大和, 开始索引, 结束索引)
&quot;&quot;&quot;
# TODO: 实现寻找跨越中点的最大子数组的逻辑
# 提示: 从mid向左和向右分别扫描找到各自的最大和
def find_maximum_subarray(arr, low, high):
&quot;&quot;&quot;
使用分治法求解最大子数组问题
返回: (最大和, 开始索引, 结束索引)
&quot;&quot;&quot;
# TODO: 实现分治递归逻辑
# 提示: 递归基是当数组只有一个元素时
#--- 测试与对比部分 --- 以下内容无需修改
traffic_changes = [13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7]
print(f&quot;交通流量变化数据: {traffic_changes}&quot;)
#使用分治法
max_sum_dc, start_dc, end_dc = find_maximum_subarray(traffic_changes, 0, len(traffic_changes) - 1)
print(f&quot;分治法结果: 最大增量 = {max_sum_dc}, 时段 = Day {start_dc} to Day {end_dc}&quot;)
#使用暴力法验证
max_sum_brute, start_brute, end_brute = find_maximum_subarray_brute(traffic_changes)
print(f&quot;暴力法结果: 最大增量 = {max_sum_brute}, 时段 = Day {start_brute} to Day {end_brute}&quot;)
#性能测试
large_data = [random.randint(-50, 50) for _ in range(2000)]
start_time = time.perf_counter()
find_maximum_subarray(large_data, 0, len(large_data) - 1)
dc_time = (time.perf_counter() - start_time) * 1000
print(f&quot;\n在 n=2000 的数据集上,分治法耗时: {dc_time:.2f} ms&quot;)
start_time = time.perf_counter()
find_maximum_subarray_brute(large_data)
brute_time = (time.perf_counter() - start_time) * 1000
print(f&quot;在 n=2000 的数据集上,暴力法耗时: {brute_time:.2f} ms&quot;)
</code></pre>
<h3>分治经典算法:快速排序</h3>
<h4>快速排序原理</h4>
<p>快速排序Quick Sort是分治法的经典应用之一。与归并排序不同的是快速排序通过原地划分数组来完成排序无需额外的同规模辅助存储空间。它在平均情况下表现极为高效是实际应用中常用的排序算法之一。</p>
<p>快速排序的分治核心思路非常好理解对于一个未排序的数组希望分解为左区间和一个“枢轴pivot”元素和右区间其中左区间的数都小于等于枢轴右区间的都大于等于枢轴。
我们这里用一种固定枢轴法来将序列变成上述的区间情况:
”选取数组第一个元素作为枢轴,用一个移动变量指向数组末尾元素并不断向前移动,直到找到第一个小于枢轴的元素,将该元素与枢轴位置交换,然后从枢轴原位置的下一位个元素开始往后找第一大于的交替,重复上述过程,最终实现以枢轴为界划分出左小右大的区间,再对左右区间递归执行此操作完成排序“</p>
<h4>任务:编码与实验</h4>
<p>现在你需要亲手实现快速排序算法,并通过实验验证其在不同输入情况下的性能差异。</p>
<p>实现 quick_sort(arr) 函数,使用分治策略对传入的数组进行排序。为简单起见,可选择每次固定使用数组的第一个元素作为枢轴,将比枢轴小的元素放在左侧,比枢轴大的放在右侧,然后递归地排序左右子数组。完成实现后,我们将利用性能测试函数对比有序输入(近似最坏情况)和随机输入(平均情况)下算法的运行时间。
代码框架
请在下方代码编辑区完成 quick_sort(arr) 的实现。</p>
<pre><code>import random
import time
def quick_sort(arr):
&quot;&quot;&quot;
实现快速排序算法(固定枢轴策略)
参数arr: 待排序的数组
返回: 排序后的数组
&quot;&quot;&quot;
# TODO: 实现快速排序的分治逻辑
# 提示:选取第一个元素为枢轴,递归排序左右子数组
def measure_performance(sort_func, data):
start_time = time.perf_counter_ns()
sort_func(data.copy()) # 对数据副本排序,避免影响原数据
end_time = time.perf_counter_ns()
return (end_time - start_time) / 10**6 # 转换为毫秒
#性能测试:对比有序输入和随机输入
sizes = [1000, 5000, 10000]
print(&quot;快速排序性能测试(固定枢轴):&quot;)
for n in sizes:
sorted_data = list(range(n))
random_data = [random.randint(0, 10**6) for _ in range(n)]
time_sorted = measure_performance(quick_sort, sorted_data)
time_random = measure_performance(quick_sort, random_data)
print(f&quot;数据规模 n={n}: 有序输入 {time_sorted:.2f} ms, 随机输入 {time_random:.2f} ms&quot;)
</code></pre>
<p>完成编码并运行测试,报告时间差异。</p>
<h3>快速排序的复杂度分析</h3>
<h4>时间复杂度</h4>
<p>基于 “划分 - 递归” 核心逻辑,时间复杂度由<strong>划分效率(枢轴选择)<strong></strong>递归深度</strong>共同决定
三种典型场景:最坏情况、平均情况、最佳情况</p>
<p>最坏情况:每次划分得规模 n-1 和 0 的子数组
平均情况:输入随机 / 等概率选枢轴
最佳情况:每次划分平分数组</p>
<h4>空间复杂度</h4>
<p>快速排序比归并排序更常用的一点在于,快排是“原地的”。</p>
<h3>注入随机进行优化:随机快排与期望复杂度</h3>
<h4>分析</h4>
<p>为了避免固定枢轴选择导致的最坏情况,我们可以引入随机化策略优化快速排序。
随机快排在每次划分时随机选择枢轴,从概率上保证划分均衡,从而将最坏情况表现转化为极低概率事件。
理论上可以证明,随机快排对任何输入的期望时间复杂度为<eq>O(n \log n)</eq>,且大幅降低了出现<eq>O(n^2)</eq>耗时的概率。</p>
<h4>实践</h4>
<pre><code>import random
import time
def random_quick_sort(arr):
&quot;&quot;&quot;
实现随机快速排序算法(随机选择枢轴)
参数arr: 待排序的数组
返回: 排序后的数组
&quot;&quot;&quot;
def quick_sort(arr):
&quot;&quot;&quot;
你在上一章实现的快排内容
&quot;&quot;&quot;
#验证随机快排在极端有序输入下的性能
n = 10000
sorted_data = list(range(n))
time_fixed = measure_performance(quick_sort, sorted_data)
time_random = measure_performance(random_quick_sort, sorted_data)
print(f&quot;数据规模 n={n}: 固定枢轴快排 {time_fixed:.2f} ms, 随机枢轴快排 {time_random:.2f} ms&quot;)
</code></pre>
</body>
</html>

View File

@@ -11,8 +11,8 @@
{% if role == 'teacher' %}
<!-- 如果是教师角色 -->
<div class="navbar-links">
<a href="/teacherindex">首页</a>
<a href="/teacherdashboard">课程</a>
<a href="/">首页</a>
<a href="/teacherboard">课程</a>
<a href="/teachergrades">成绩</a>
</div>
{% else %}