use queue to allow send vscode_ws when it is none
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
172
Html/apps/static/68bacdfadf5aeae0912f7f18-第三周-分治法实践与优化.html
Normal file
172
Html/apps/static/68bacdfadf5aeae0912f7f18-第三周-分治法实践与优化.html
Normal 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):
|
||||
"""
|
||||
使用暴力法求解最大子数组问题
|
||||
返回: (最大和, 开始索引, 结束索引)
|
||||
"""
|
||||
|
||||
#--- 本周任务:请在下方实现分治算法 ---
|
||||
|
||||
def find_max_crossing_subarray(arr, low, mid, high):
|
||||
"""
|
||||
找到跨越中点的最大子数组
|
||||
返回: (最大和, 开始索引, 结束索引)
|
||||
"""
|
||||
# TODO: 实现寻找跨越中点的最大子数组的逻辑
|
||||
# 提示: 从mid向左和向右分别扫描,找到各自的最大和
|
||||
|
||||
def find_maximum_subarray(arr, low, high):
|
||||
"""
|
||||
使用分治法求解最大子数组问题
|
||||
返回: (最大和, 开始索引, 结束索引)
|
||||
"""
|
||||
# TODO: 实现分治递归逻辑
|
||||
# 提示: 递归基是当数组只有一个元素时
|
||||
|
||||
#--- 测试与对比部分 --- 以下内容无需修改
|
||||
traffic_changes = [13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7]
|
||||
print(f"交通流量变化数据: {traffic_changes}")
|
||||
|
||||
#使用分治法
|
||||
max_sum_dc, start_dc, end_dc = find_maximum_subarray(traffic_changes, 0, len(traffic_changes) - 1)
|
||||
print(f"分治法结果: 最大增量 = {max_sum_dc}, 时段 = Day {start_dc} to Day {end_dc}")
|
||||
|
||||
#使用暴力法验证
|
||||
max_sum_brute, start_brute, end_brute = find_maximum_subarray_brute(traffic_changes)
|
||||
print(f"暴力法结果: 最大增量 = {max_sum_brute}, 时段 = Day {start_brute} to Day {end_brute}")
|
||||
|
||||
#性能测试
|
||||
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"\n在 n=2000 的数据集上,分治法耗时: {dc_time:.2f} ms")
|
||||
|
||||
start_time = time.perf_counter()
|
||||
find_maximum_subarray_brute(large_data)
|
||||
brute_time = (time.perf_counter() - start_time) * 1000
|
||||
print(f"在 n=2000 的数据集上,暴力法耗时: {brute_time:.2f} ms")
|
||||
</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):
|
||||
"""
|
||||
实现快速排序算法(固定枢轴策略)
|
||||
参数arr: 待排序的数组
|
||||
返回: 排序后的数组
|
||||
"""
|
||||
# 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("快速排序性能测试(固定枢轴):")
|
||||
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"数据规模 n={n}: 有序输入 {time_sorted:.2f} ms, 随机输入 {time_random:.2f} ms")
|
||||
</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):
|
||||
"""
|
||||
实现随机快速排序算法(随机选择枢轴)
|
||||
参数arr: 待排序的数组
|
||||
返回: 排序后的数组
|
||||
"""
|
||||
|
||||
|
||||
def quick_sort(arr):
|
||||
"""
|
||||
你在上一章实现的快排内容
|
||||
"""
|
||||
|
||||
#验证随机快排在极端有序输入下的性能
|
||||
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"数据规模 n={n}: 固定枢轴快排 {time_fixed:.2f} ms, 随机枢轴快排 {time_random:.2f} ms")
|
||||
|
||||
</code></pre>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user