Skip to content

Commit

Permalink
fix dag to match different problem
Browse files Browse the repository at this point in the history
  • Loading branch information
keweizhan committed Apr 17, 2024
1 parent 361c3e2 commit 90ca67c
Show file tree
Hide file tree
Showing 12 changed files with 723 additions and 162 deletions.
177 changes: 84 additions & 93 deletions app/assets/javascripts/dag.js
Original file line number Diff line number Diff line change
@@ -1,106 +1,97 @@
// 根据 DAG 描述构建图
function buildGraph(dag, nodeValueToLabel) {
const graph = {};
const lines = dag.trim().split('\n');

for (const line of lines) {
const [node, ...parents] = line.split(':').map(item => item.trim());
const label = nodeValueToLabel[node];
if (parents.length === 0 || parents[0] === '') {
graph[label] = [];
} else {
const validParents = parents.map(parent => nodeValueToLabel[parent]).filter(parent => parent !== '');
graph[label] = validParents;
}
}

return graph;
const graph = {};
const inDegree = {};

// initialize graph and in-degree
for (const nodeLabel in nodeValueToLabel) {
graph[nodeLabel] = [];
inDegree[nodeLabel] = 0;
}

// 构建入度数组并初始化队列
function initializeCounts(graph) {
const inDegree = {};
const queue = [];

for (const node in graph) {
inDegree[node] = graph[node].length;
if (inDegree[node] === 0) {
queue.push(node);

// parse the DAG and build the graph
const lines = dag.split('\n');
for (const line of lines) {
const parts = line.split(':').map(part => part.trim());
if (parts.length === 2) {
const nodeLabel = parts[0];
const dependencies = parts[1].split(' ').filter(label => label !== '');
for (const dependency of dependencies) {
if (dependency !== '-1' && nodeValueToLabel[nodeLabel] !== undefined && nodeValueToLabel[dependency] !== undefined) {
graph[nodeLabel].push(dependency); // add dependency to the graph
inDegree[dependency]++; // increment in-degree of the dependency
}
}
}

return { inDegree, queue };
}


function processSolution(graph, inDegree, queue, solution, nodeValueToLabel) {
const visited = new Set();
if (Array.isArray(solution)) {
solution = solution.join('\n');
} else if (typeof solution !== 'string') {
throw new TypeError('The solution must be a string or an array.');
}

const solutionNodes = solution.split('\n').map(line => line.trim());
const graphNodes = Object.keys(graph).filter(node => node !== '__root__'); // 排除虚拟根节点

console.log("Solution nodes:", solutionNodes);
console.log("Graph nodes:", graphNodes);

// 检查学生的解答中的项目数量是否与图中的节点数量匹配
if (solutionNodes.length !== graphNodes.length) {
throw new Error('Number of items in student solution does not match the number of nodes in the graph.');
console.log("Graph:", graph);
console.log("In-degree:", inDegree);
return { graph, inDegree };
}


function processSolution(solution, graph, inDegree, nodeValueToLabel) {
console.log("processSolution:", solution);
console.log("processnodeValueToLabel:", nodeValueToLabel);
const visited = new Set();

for (const nodeText of solution) {
const nodeLabel = Object.keys(nodeValueToLabel).find(
(label) => nodeValueToLabel[label] === nodeText
);

if (nodeLabel === undefined) {
console.log("Skipping node not found in nodeValueToLabel:", nodeText);
continue; // jump to the next node
}

for (const node of solutionNodes) { // 修改这里
console.log("Current node:", node);
console.log("Current queue:", queue);

// 查找节点对应的标签
const label = node; // 修改这里
if (!label) {
console.log("Node label not found, returning false");
return false;
}

// 如果当前节点的标签不在队列中,返回false
if (!queue.includes(label)) {
console.log("Node label not in queue, returning false");

console.log('Current label:', nodeLabel);
console.log('Current node text:', nodeText);
console.log('Node value to label mapping:', nodeValueToLabel);

visited.add(nodeLabel);

// check if the node has dependencies
for (const dependencyLabel of graph[nodeLabel]) {
if (!visited.has(dependencyLabel)) {
console.error("Dependency not satisfied:", nodeText, "depends on", nodeValueToLabel[dependencyLabel]);
return false;
}

// 将当前节点的标签从队列中移除
queue.splice(queue.indexOf(label), 1);
visited.add(label);

// 更新相邻节点的入度,并将入度变为0的节点加入队列
for (const neighbor in graph) {
if (graph[neighbor].includes(label)) {
inDegree[neighbor]--;
if (inDegree[neighbor] === 0) {
queue.push(neighbor);
}
}
}
console.log("Updated in-degree:", inDegree);
console.log("Updated queue:", queue);
}

// 如果所有节点都被访问过,返回true,否则返回false
const allVisited = visited.size === Object.keys(graph).length;
console.log("All nodes visited:", allVisited);
return allVisited;
}

// check if all nodes were visited
if (visited.size !== Object.keys(nodeValueToLabel).length) {
console.error("Not all nodes in nodeValueToLabel were visited.");
return false;
}

console.log('Visited nodes:', Array.from(visited));
return true;
}




function processDAG(dag, solution) {
const nodeValueToLabel = {
"one": "print('Hello')",
"two": "print('Parsons')",
"three": "print('Problems!')"
};

const graph = buildGraph(dag, nodeValueToLabel);
const { inDegree, queue } = initializeCounts(graph);
const result = processSolution(graph, inDegree, queue, solution, nodeValueToLabel);
return result;
function processDAG(dag, solution, nodeValueToLabel) {
console.log("DAG:", dag);
console.log("Node value to label mapping:", nodeValueToLabel);
const { graph, inDegree } = buildGraph(dag, nodeValueToLabel);
const result = processSolution(solution, graph, inDegree, nodeValueToLabel);
return result;
}

function extractCode(solution, nodeValueToLabel) {
const code = [];
const newNodeValueToLabel = {};
for (const nodeText of solution) {
const nodeLabel = Object.keys(nodeValueToLabel).find(
(key) => nodeValueToLabel[key] === nodeText
);
if (nodeLabel !== undefined) {
code.push(nodeText);
newNodeValueToLabel[nodeLabel] = nodeText;
}
}
return { code, newNodeValueToLabel };
}
82 changes: 37 additions & 45 deletions app/controllers/exercises_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -518,50 +518,37 @@ def upload_create
text_representation = File.read(params[:form][:file].path)
use_rights = 0 # Personal exercise
end

# 检查 text_representation 中的 tags.style 字段是否包含 "parsons"
if text_representation.include?("tags.style") && text_representation.include?("parsons")
# 使用自定义解析器解析 text_representation
parsed_data = parse_text_representation(text_representation)

# 使用 ParsonsPromptRepresenter 创建 ParsonsPrompt 对象
parsons_prompt = ParsonsPromptRepresenter.new(ParsonsPrompt.new).from_hash(parsed_data)
exercises = [parsons_prompt.to_hash]
else
# 使用 ExerciseRepresenter 解析 text_representation
exercises = ExerciseRepresenter.for_collection.new([]).from_hash(YAML.load(text_representation))
end

# 后续的处理逻辑保持不变
exercises.each do |e|
if e[:instructions].present? && e[:assets].present?
# 处理 Parsons 问题
parsons_prompt = {}

# 从 e 中获取相应字段的值,并赋值给 parsons_prompt
parsons_prompt["exercise_id"] = e[:exercise_id]
parsons_prompt["title"] = e[:title]
parsons_prompt["author"] = e[:author]
parsons_prompt["license"] = e[:license]
parsons_prompt["tags"] = e[:tags]
parsons_prompt["instructions"] = e[:instructions]
parsons_prompt["assets"] = e[:assets]

# 更新 prompt
e[:prompt] = [{ "parsons_prompt" => parsons_prompt }]

# 删除 e 中已经复制到 parsons_prompt 的字段
e.delete(:exercise_id)
e.delete(:title)
e.delete(:author)
e.delete(:license)
e.delete(:tags)
e.delete(:instructions)
e.delete(:assets)

begin
hash = YAML.load(text_representation)
if !hash.kind_of?(Array)
hash = [hash]
is_parsons = false
end
rescue Psych::SyntaxError
attributes = {}
text_representation.scan(/^(\w+(?:\.\w+)*):(.*)$/) do |key, value|
keys = key.split('.')
target = attributes
keys[0..-2].each do |k|
target[k] ||= {}
target = target[k]
end
target[keys.last] = value.strip
end

title = attributes['title']
assets_code = attributes.dig('assets', 'code', 'starter', 'files')
if assets_code
assets_code_content = assets_code['content']
hash = assets_code_content.scan(/^tag:\s*(\w+)\s*\n^display:\s*(.+)$/m).map do |tag, display|
{ 'tag' => tag, 'display' => display }
end
is_parsons = true
else
hash = []
is_parsons = false
end
end
if !hash.kind_of?(Array)
hash = [hash]
end

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

# parse the text_representation
exercises = ExerciseRepresenter.for_collection.new([]).from_hash(hash)
if is_parsons
exercises = ParsonsExerciseRepresenter.for_collection.new([]).from_hash(hash)
else
exercises = ExerciseRepresenter.for_collection.new([]).from_hash(hash)
end
success_all = true
error_msgs = []
success_msgs = []
exercises.each do |e|
e.name = title if title.present?
if !e.save
success_all = false
# put together an error message
Expand Down Expand Up @@ -665,7 +657,7 @@ def upload_create

# Notify user of success
success_msgs <<
"<li>X#{e.id}: #{e.name} saved, try it #{view_context.link_to 'here', exercise_practice_path(e)}.</li>"
"<li>X#{e.id}: #{e.name || 'Parsons problem'} saved, try it #{view_context.link_to 'here', exercise_practice_path(e)}.</li>"
end
end

Expand Down
10 changes: 6 additions & 4 deletions app/models/attempt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
#
# Indexes
#
# index_attempts_on_active_score_id (active_score_id)
# index_attempts_on_exercise_version_id (exercise_version_id)
# index_attempts_on_user_id (user_id)
# index_attempts_on_workout_score_id (workout_score_id)
# idx_attempts_on_user_exercise_version (user_id,exercise_version_id)
# idx_attempts_on_workout_score_exercise_version (workout_score_id,exercise_version_id)
# index_attempts_on_active_score_id (active_score_id)
# index_attempts_on_exercise_version_id (exercise_version_id)
# index_attempts_on_user_id (user_id)
# index_attempts_on_workout_score_id (workout_score_id)
#
# Foreign Keys
#
Expand Down
1 change: 1 addition & 0 deletions app/models/exercise_version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class ExerciseVersion < ActiveRecord::Base
has_many :resource_files, through: :ownerships
belongs_to :creator, class_name: 'User'
belongs_to :irt_data, dependent: :destroy
has_one :parsons_prompt


#~ Hooks ....................................................................
Expand Down
26 changes: 24 additions & 2 deletions app/models/parsons_prompt.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,28 @@
# == Schema Information
#
# Table name: parsons_prompts
#
# id :integer not null, primary key
# assets :text(65535)
# instructions :text(65535)
# title :text(65535)
# created_at :datetime
# updated_at :datetime
# exercise_id :string(255)
# exercise_version_id :integer not null
#
# Indexes
#
# fk_rails_40d6ef5b4f (exercise_version_id)
#
# Foreign Keys
#
# fk_rails_... (exercise_version_id => exercise_versions.id)
#

class ParsonsPrompt < ActiveRecord::Base
belongs_to :parsons
belongs_to :exercise_version

store_accessor :assets, :code, :test
end
serialize :assets, JSON
end
Loading

0 comments on commit 90ca67c

Please sign in to comment.