Skip to content

Commit 90ca67c

Browse files
committed
fix dag to match different problem
1 parent 361c3e2 commit 90ca67c

File tree

12 files changed

+723
-162
lines changed

12 files changed

+723
-162
lines changed

app/assets/javascripts/dag.js

+84-93
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,97 @@
1-
// 根据 DAG 描述构建图
21
function buildGraph(dag, nodeValueToLabel) {
3-
const graph = {};
4-
const lines = dag.trim().split('\n');
5-
6-
for (const line of lines) {
7-
const [node, ...parents] = line.split(':').map(item => item.trim());
8-
const label = nodeValueToLabel[node];
9-
if (parents.length === 0 || parents[0] === '') {
10-
graph[label] = [];
11-
} else {
12-
const validParents = parents.map(parent => nodeValueToLabel[parent]).filter(parent => parent !== '');
13-
graph[label] = validParents;
14-
}
15-
}
16-
17-
return graph;
2+
const graph = {};
3+
const inDegree = {};
4+
5+
// initialize graph and in-degree
6+
for (const nodeLabel in nodeValueToLabel) {
7+
graph[nodeLabel] = [];
8+
inDegree[nodeLabel] = 0;
189
}
19-
20-
// 构建入度数组并初始化队列
21-
function initializeCounts(graph) {
22-
const inDegree = {};
23-
const queue = [];
24-
25-
for (const node in graph) {
26-
inDegree[node] = graph[node].length;
27-
if (inDegree[node] === 0) {
28-
queue.push(node);
10+
11+
// parse the DAG and build the graph
12+
const lines = dag.split('\n');
13+
for (const line of lines) {
14+
const parts = line.split(':').map(part => part.trim());
15+
if (parts.length === 2) {
16+
const nodeLabel = parts[0];
17+
const dependencies = parts[1].split(' ').filter(label => label !== '');
18+
for (const dependency of dependencies) {
19+
if (dependency !== '-1' && nodeValueToLabel[nodeLabel] !== undefined && nodeValueToLabel[dependency] !== undefined) {
20+
graph[nodeLabel].push(dependency); // add dependency to the graph
21+
inDegree[dependency]++; // increment in-degree of the dependency
22+
}
2923
}
3024
}
31-
32-
return { inDegree, queue };
3325
}
34-
3526

36-
function processSolution(graph, inDegree, queue, solution, nodeValueToLabel) {
37-
const visited = new Set();
38-
if (Array.isArray(solution)) {
39-
solution = solution.join('\n');
40-
} else if (typeof solution !== 'string') {
41-
throw new TypeError('The solution must be a string or an array.');
42-
}
43-
44-
const solutionNodes = solution.split('\n').map(line => line.trim());
45-
const graphNodes = Object.keys(graph).filter(node => node !== '__root__'); // 排除虚拟根节点
46-
47-
console.log("Solution nodes:", solutionNodes);
48-
console.log("Graph nodes:", graphNodes);
49-
50-
// 检查学生的解答中的项目数量是否与图中的节点数量匹配
51-
if (solutionNodes.length !== graphNodes.length) {
52-
throw new Error('Number of items in student solution does not match the number of nodes in the graph.');
27+
console.log("Graph:", graph);
28+
console.log("In-degree:", inDegree);
29+
return { graph, inDegree };
30+
}
31+
32+
33+
function processSolution(solution, graph, inDegree, nodeValueToLabel) {
34+
console.log("processSolution:", solution);
35+
console.log("processnodeValueToLabel:", nodeValueToLabel);
36+
const visited = new Set();
37+
38+
for (const nodeText of solution) {
39+
const nodeLabel = Object.keys(nodeValueToLabel).find(
40+
(label) => nodeValueToLabel[label] === nodeText
41+
);
42+
43+
if (nodeLabel === undefined) {
44+
console.log("Skipping node not found in nodeValueToLabel:", nodeText);
45+
continue; // jump to the next node
5346
}
54-
55-
for (const node of solutionNodes) { // 修改这里
56-
console.log("Current node:", node);
57-
console.log("Current queue:", queue);
58-
59-
// 查找节点对应的标签
60-
const label = node; // 修改这里
61-
if (!label) {
62-
console.log("Node label not found, returning false");
63-
return false;
64-
}
65-
66-
// 如果当前节点的标签不在队列中,返回false
67-
if (!queue.includes(label)) {
68-
console.log("Node label not in queue, returning false");
47+
48+
console.log('Current label:', nodeLabel);
49+
console.log('Current node text:', nodeText);
50+
console.log('Node value to label mapping:', nodeValueToLabel);
51+
52+
visited.add(nodeLabel);
53+
54+
// check if the node has dependencies
55+
for (const dependencyLabel of graph[nodeLabel]) {
56+
if (!visited.has(dependencyLabel)) {
57+
console.error("Dependency not satisfied:", nodeText, "depends on", nodeValueToLabel[dependencyLabel]);
6958
return false;
7059
}
71-
72-
// 将当前节点的标签从队列中移除
73-
queue.splice(queue.indexOf(label), 1);
74-
visited.add(label);
75-
76-
// 更新相邻节点的入度,并将入度变为0的节点加入队列
77-
for (const neighbor in graph) {
78-
if (graph[neighbor].includes(label)) {
79-
inDegree[neighbor]--;
80-
if (inDegree[neighbor] === 0) {
81-
queue.push(neighbor);
82-
}
83-
}
84-
}
85-
console.log("Updated in-degree:", inDegree);
86-
console.log("Updated queue:", queue);
8760
}
88-
89-
// 如果所有节点都被访问过,返回true,否则返回false
90-
const allVisited = visited.size === Object.keys(graph).length;
91-
console.log("All nodes visited:", allVisited);
92-
return allVisited;
9361
}
62+
63+
// check if all nodes were visited
64+
if (visited.size !== Object.keys(nodeValueToLabel).length) {
65+
console.error("Not all nodes in nodeValueToLabel were visited.");
66+
return false;
67+
}
68+
69+
console.log('Visited nodes:', Array.from(visited));
70+
return true;
71+
}
72+
73+
74+
9475

95-
function processDAG(dag, solution) {
96-
const nodeValueToLabel = {
97-
"one": "print('Hello')",
98-
"two": "print('Parsons')",
99-
"three": "print('Problems!')"
100-
};
101-
102-
const graph = buildGraph(dag, nodeValueToLabel);
103-
const { inDegree, queue } = initializeCounts(graph);
104-
const result = processSolution(graph, inDegree, queue, solution, nodeValueToLabel);
105-
return result;
76+
function processDAG(dag, solution, nodeValueToLabel) {
77+
console.log("DAG:", dag);
78+
console.log("Node value to label mapping:", nodeValueToLabel);
79+
const { graph, inDegree } = buildGraph(dag, nodeValueToLabel);
80+
const result = processSolution(solution, graph, inDegree, nodeValueToLabel);
81+
return result;
82+
}
83+
84+
function extractCode(solution, nodeValueToLabel) {
85+
const code = [];
86+
const newNodeValueToLabel = {};
87+
for (const nodeText of solution) {
88+
const nodeLabel = Object.keys(nodeValueToLabel).find(
89+
(key) => nodeValueToLabel[key] === nodeText
90+
);
91+
if (nodeLabel !== undefined) {
92+
code.push(nodeText);
93+
newNodeValueToLabel[nodeLabel] = nodeText;
94+
}
95+
}
96+
return { code, newNodeValueToLabel };
10697
}

app/controllers/exercises_controller.rb

+37-45
Original file line numberDiff line numberDiff line change
@@ -518,50 +518,37 @@ def upload_create
518518
text_representation = File.read(params[:form][:file].path)
519519
use_rights = 0 # Personal exercise
520520
end
521-
522-
# 检查 text_representation 中的 tags.style 字段是否包含 "parsons"
523-
if text_representation.include?("tags.style") && text_representation.include?("parsons")
524-
# 使用自定义解析器解析 text_representation
525-
parsed_data = parse_text_representation(text_representation)
526-
527-
# 使用 ParsonsPromptRepresenter 创建 ParsonsPrompt 对象
528-
parsons_prompt = ParsonsPromptRepresenter.new(ParsonsPrompt.new).from_hash(parsed_data)
529-
exercises = [parsons_prompt.to_hash]
530-
else
531-
# 使用 ExerciseRepresenter 解析 text_representation
532-
exercises = ExerciseRepresenter.for_collection.new([]).from_hash(YAML.load(text_representation))
533-
end
534-
535-
# 后续的处理逻辑保持不变
536-
exercises.each do |e|
537-
if e[:instructions].present? && e[:assets].present?
538-
# 处理 Parsons 问题
539-
parsons_prompt = {}
540-
541-
# 从 e 中获取相应字段的值,并赋值给 parsons_prompt
542-
parsons_prompt["exercise_id"] = e[:exercise_id]
543-
parsons_prompt["title"] = e[:title]
544-
parsons_prompt["author"] = e[:author]
545-
parsons_prompt["license"] = e[:license]
546-
parsons_prompt["tags"] = e[:tags]
547-
parsons_prompt["instructions"] = e[:instructions]
548-
parsons_prompt["assets"] = e[:assets]
549-
550-
# 更新 prompt
551-
e[:prompt] = [{ "parsons_prompt" => parsons_prompt }]
552-
553-
# 删除 e 中已经复制到 parsons_prompt 的字段
554-
e.delete(:exercise_id)
555-
e.delete(:title)
556-
e.delete(:author)
557-
e.delete(:license)
558-
e.delete(:tags)
559-
e.delete(:instructions)
560-
e.delete(:assets)
521+
522+
begin
523+
hash = YAML.load(text_representation)
524+
if !hash.kind_of?(Array)
525+
hash = [hash]
526+
is_parsons = false
527+
end
528+
rescue Psych::SyntaxError
529+
attributes = {}
530+
text_representation.scan(/^(\w+(?:\.\w+)*):(.*)$/) do |key, value|
531+
keys = key.split('.')
532+
target = attributes
533+
keys[0..-2].each do |k|
534+
target[k] ||= {}
535+
target = target[k]
536+
end
537+
target[keys.last] = value.strip
538+
end
539+
540+
title = attributes['title']
541+
assets_code = attributes.dig('assets', 'code', 'starter', 'files')
542+
if assets_code
543+
assets_code_content = assets_code['content']
544+
hash = assets_code_content.scan(/^tag:\s*(\w+)\s*\n^display:\s*(.+)$/m).map do |tag, display|
545+
{ 'tag' => tag, 'display' => display }
546+
end
547+
is_parsons = true
548+
else
549+
hash = []
550+
is_parsons = false
561551
end
562-
end
563-
if !hash.kind_of?(Array)
564-
hash = [hash]
565552
end
566553

567554
files = exercise_params[:files]
@@ -601,11 +588,16 @@ def upload_create
601588
@return_to = session.delete(:return_to) || exercises_path
602589

603590
# parse the text_representation
604-
exercises = ExerciseRepresenter.for_collection.new([]).from_hash(hash)
591+
if is_parsons
592+
exercises = ParsonsExerciseRepresenter.for_collection.new([]).from_hash(hash)
593+
else
594+
exercises = ExerciseRepresenter.for_collection.new([]).from_hash(hash)
595+
end
605596
success_all = true
606597
error_msgs = []
607598
success_msgs = []
608599
exercises.each do |e|
600+
e.name = title if title.present?
609601
if !e.save
610602
success_all = false
611603
# put together an error message
@@ -665,7 +657,7 @@ def upload_create
665657

666658
# Notify user of success
667659
success_msgs <<
668-
"<li>X#{e.id}: #{e.name} saved, try it #{view_context.link_to 'here', exercise_practice_path(e)}.</li>"
660+
"<li>X#{e.id}: #{e.name || 'Parsons problem'} saved, try it #{view_context.link_to 'here', exercise_practice_path(e)}.</li>"
669661
end
670662
end
671663

app/models/attempt.rb

+6-4
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
#
2121
# Indexes
2222
#
23-
# index_attempts_on_active_score_id (active_score_id)
24-
# index_attempts_on_exercise_version_id (exercise_version_id)
25-
# index_attempts_on_user_id (user_id)
26-
# index_attempts_on_workout_score_id (workout_score_id)
23+
# idx_attempts_on_user_exercise_version (user_id,exercise_version_id)
24+
# idx_attempts_on_workout_score_exercise_version (workout_score_id,exercise_version_id)
25+
# index_attempts_on_active_score_id (active_score_id)
26+
# index_attempts_on_exercise_version_id (exercise_version_id)
27+
# index_attempts_on_user_id (user_id)
28+
# index_attempts_on_workout_score_id (workout_score_id)
2729
#
2830
# Foreign Keys
2931
#

app/models/exercise_version.rb

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class ExerciseVersion < ActiveRecord::Base
5454
has_many :resource_files, through: :ownerships
5555
belongs_to :creator, class_name: 'User'
5656
belongs_to :irt_data, dependent: :destroy
57+
has_one :parsons_prompt
5758

5859

5960
#~ Hooks ....................................................................

app/models/parsons_prompt.rb

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
1+
# == Schema Information
2+
#
3+
# Table name: parsons_prompts
4+
#
5+
# id :integer not null, primary key
6+
# assets :text(65535)
7+
# instructions :text(65535)
8+
# title :text(65535)
9+
# created_at :datetime
10+
# updated_at :datetime
11+
# exercise_id :string(255)
12+
# exercise_version_id :integer not null
13+
#
14+
# Indexes
15+
#
16+
# fk_rails_40d6ef5b4f (exercise_version_id)
17+
#
18+
# Foreign Keys
19+
#
20+
# fk_rails_... (exercise_version_id => exercise_versions.id)
21+
#
22+
123
class ParsonsPrompt < ActiveRecord::Base
224
belongs_to :parsons
325
belongs_to :exercise_version
426

5-
store_accessor :assets, :code, :test
6-
end
27+
serialize :assets, JSON
28+
end

0 commit comments

Comments
 (0)