Ruby(在rails上)检查树是否不是圆形的

时间:2014-11-10 10:57:46

标签: ruby-on-rails ruby

我有一份问题清单。每个问题都有4个答案,每个答案都链接到下一个问题,其中有4个答案链接到下一个问题,直到" Endquesion"没有答案。

这是一个类似于树的构造,我想确定它保持这种方式 - 没有答案链接到已经被问过的问题。

我认为唯一的方法是使用递归函数。

我在想这样的事情:

mq = [question.id] q = question.id

def not_circular(q, mq)
  mother_questions = mq
  sister_questions = []
  question = Question.find(q)
  question.answers.each do |a|
    if mother_questions.include?(a.next_question)
      return a.content
    else
      if !a.endlevel
        sister_questions << a.next_question
      end          
    end  
  end
  mother_questions = mother_questions + sister_questions

    question.answers.each do |a|
      if !a.endlevel
      return not_circular(a.next_question, mother_questions)
      end 
    end  
    return false
  end

但是我看到了一些问题 - 我正在考虑制作一组#34;父母问题&#34;并检查&#34; next_question&#34;在这个数组中(它将停止该函数并返回&#34;循环&#34; next_question&#34;)。但是在我的示例代码中,对于&#34;姐妹问题&#34;将会出现误报。 (当同一个问题的答案指向同一个下一个问题时)可以而且应该是相同的。

有人能指出我正确的方向吗?

编辑:

问题有很多答案。答案属于问题。 答案有一个:next_question变量,指向下一个问题。

编辑2: 我得到了正确测试树的至少一个分支的功能(参见上面的新代码)。现在我只想弄清楚如何测试所有分支。

4 个答案:

答案 0 :(得分:1)

我最喜欢在树中处理这个问题的方法,甚至是有向的非循环图,都是在连接表上进行验证。我不确定我是否完全理解您正在使用的结构,因此我会解决彼此先决条件的课程的常见问题。

class Course < ActiveRecord::Base
  has_many :course_relationships, dependent: :destroy
  has_many :prereqs, through: :course_relationships

  has_many :inverse_course_relationships, class_name: 'CourseRelationship', foreign_key: 'prereq_id', dependent: :destroy
  has_many :inverse_prereqs, through: :inverse_course_relationships, source: :course
end

然后我在连接表中进行验证:

class CourseRelationship < ActiveRecord::Base
  belongs_to :course
  belongs_to :prereq, class_name: 'Course'

  validate :is_acyclic

  def is_acyclic
    if course == prereq
      errors.add(:base, "A course can't be a prerequisite to itself")
      return false
    end

    check_for_course = Proc.new do |current_course|
      if course == current_course
        errors.add(:base, "Catch 22 detected. \"#{course.title}\" is already required before \"#{prereq.title}\".")
        return false
      end

      current_course.prereqs.each do |course_to_check|
        check_for_course.call course_to_check
      end
    end

    check_for_course.call prereq
    return true
  end
end

这确保了每次创建新关系时,课程永远不会成为自身的先决条件(即使是间接的)。

答案 1 :(得分:0)

我认为这个测试的责任(即有效性)应该是答案,而不是问题:如果有人将问题的答案与已经被问到的问题联系起来,那么答案是错误的,即无效,而不是选择的问题。

所以,我会将这个测试移到答案课上:为了完成这项工作,你需要在问题中找到一个新的关联,这个关联指向那些将这个问题作为下一个问题的答案。我没有测试过这个,但我认为它应该可行。在尝试使用方法之前测试previous_answers关联是否有效。

#in Question
has_many :answers
has_many :previous_answers, :class_name => "Answer", :source => :next_question

#in Answer
belongs_to :question
belongs_to :next_question

validate :does_not_link_to_previously_asked_question

def does_not_link_to_previously_asked_question
  if self.previous_questions.include?(self.next_question)
    self.errors.add(:next_question_id, "This question has already been asked")
  end
end

def previous_questions
  current = [self.question]
  questions = []
  while current.size > 0
    current = current.collect(&:previous_answers).flatten.collect(&:question).reject(&:blank?)
    questions += current
  end
  questions.uniq
end

答案 2 :(得分:0)

这里有一些理论我没有完全考虑过,但如果你想要一个简单的保证,不存在循环路径,最简单的方法是确保没有答案可以链接回一个问题从比自身更高的层次。因此,问题知道它们在树中的位置,并且答案无法链接到问题的级别之上。答案仍然可以链接到他们自己级别以下的任何问题,只是不在上面。

否则我认为你需要将这个计算看作是一个独立的Ruby,而不是试图将它与你的模型联系得太远 - 你可以创建一个相当简单的问题和答案树,其中只包含Id和父/用于建模数据的子关系,而不必在层之间进行太多对话。如果它在它自己的模块中,也将使它更容易测试和管理。

确保您无法添加可能导致当前答案的问题的方法是确保答案后面的问题在该答案的任何路径上都不存在。如果在你的树模型中,每个节点都知道它的父节点以及它的子节点,那么从当前的答案(通过它的parent数组的每个成员)中走出树是一个相当简单的问题。并确定该路径上的每个问题。那些问题就构成了一个“排除”的列表,不能合法地添加到当前的答案中。

答案 3 :(得分:0)

谢谢大家的建议!

我实际上设法以我的方式解决它(或者至少我的测试使它看起来像我成功地解决了它)。

这是我的代码:

mq = [Question.id]
q = Question.id

def not_circular(q, mq)
  if mq.empty?
   mother_questions = [q]
  else
   mother_questions = mq + [q]
  end  
  question = Question.find(q)
  question.answers.each do |a|
    if mother_questions.include?(a.next_question)
      return a.content
    else
      if a.next_question != 0 && !a.last_question
        if not_circular(a.next_question, mother_questions)
         return not_circular(a.next_question, mother_questions)
        end
      end          
    end  
  end      
  return false
end

现在,链接到上一个问题(因此创建一个圆圈)的答案将返回给管理员,他可以更改它。