Scala: Recursive search among elements of a list of elements

给你一囗甜甜゛ 提交于 2019-12-11 02:58:08

问题


If I have a class like this:

class Person (var name:String, var surname:String, var sons: Set[Person])

I want to have a control that a Person cannot contain herself between his sons and the sons of his sons. How can I do this?

I had thought a recursive search. But I have to be careful not to create cycles. I can use a Boolean as a guard and just you find an item that contains itself stops searching.

How can I implement this? Do you have ideas? Thank you very much.

UPDATE Thank you very much for your help. Good answers, but above all you had great ideas. Now I just need a little last help to test this check on my small project.

My real situation is as follows:

trait ArchitecturalElement extends PropertyHolderElement with TypedElement{}

abstract class Component extends ConnectableElement with ArchitecturalElement {
  var subElements : Set[ArchitecturalElement]
  var interactionPoints : Set[InteractionPoint]
  //Here I put the control
  //A component cannot contain himself in his subElements and in subElements of it subelement
  def notHerOwnDescendant = {
  def notDescendant(ancestor: Component, current: Component, checked: Set[ArchitecturalElement]): Boolean =
  !current.subElements.contains(ancestor) && current.subElements.forall(
        p => checked.contains(p) || notDescendant(ancestor, p, checked + p))

  notDescendant(this, this, Set())
  }

 }//Component

abstract class InteractionPoint extends ConnectableElement{}

class SAInterface(  var name : String,
        var description : String = "empty",
        var direction : SAInterfaceDirection = null
        )extends InteractionPoint with ArchitecturalElement

class SAComponent ( var name :String,
        var description : String = "empty",
        var subElements : Set[ArchitecturalElement] = Set(),
        var interactionPoints : Set[InteractionPoint] = Set()
        ) extends Component

But I have an incompatible types:

type mismatch; found : a0Dominio.ArchitecturalElement required: a0Dominio.SAComponent

p => checked.contains(p) || notDescendant(ancestor, p, checked + p)
                                                //  ^  here

From a Set [ArchitecturalElement], I derive a Set [Component]? Whereas Component inherits from ArchitecturalElement.


回答1:


The following method produces all descendants of the current person, while it doesn't get lost in cycles:

def descendants(stop: Set[Person] = Set()): Set[Person] = 
  if(stop contains this) Set() 
  else                   this.sons.flatMap(descendants(_,stop + this)) + this

You can use it to check for your condition. In order to detect arbitrary cycles, you have a positive result once you end up in the first branch of the if.

An alternative is to enforce the graph to be acyclic in the constructor/setters. This allows you to use a search without the stop set, since all existing instances are guaranteed to be cycle free and the condition can be tested with a slightly more simpler and faster approach.




回答2:


If I understand you correctly, you want something like this:

class Person(...) {
  ...

  def notHerOwnDescendant = 
    !sons.contains(this) && sons.forall(!_.sons.contains(this))

  ...
}

Or else, if you want to go all the way down:

class Person(...) {
  ...

  def notDescendant(person: Person) = 
    !sons.contains(person) && sons.forall(_.notDescendant(person))

  def notHerOwnDescendant = 
    notDescendant(this)

  ...
}

Disclaimer: I couldn't test this code in an IDE so no guarantee that it compiles and is correct. Nevertheless I hope it helps as food for thoughts at least :-)

Update

Here is an updated and tested version which handles cycles using the "graph coloring" solution mentioned by @gilad:

class Person (val name:String, val surname:String, var sons: Set[Person]) {
  def notHerOwnDescendant = {
    def notDescendant(ancestor: Person, current: Person, checked: Set[Person]): Boolean =
      !current.sons.contains(ancestor)
          && current.sons.forall(
            p => checked.contains(p) || notDescendant(ancestor, p, checked + p))

    notDescendant(this, this, Set())
  }
}

Some test code:

val abel = new Person("", "Abel", Set())
val cain = new Person("", "Cain", Set())
val adam = new Person("", "Adam", Set(abel, cain))

println("Adam is not his own descendant: " + adam.notHerOwnDescendant)
cain.sons += cain
println("Added Cain to his sons' set")
println("Adam is not his own descendant: " + adam.notHerOwnDescendant)
abel.sons += adam
println("Added Adam to his grandsons' set")
println("Adam is not his own descendant: " + adam.notHerOwnDescendant)

And the output:

Adam is not his own descendant: true
Added Cain to his sons' set
Adam is not his own descendant: true
Added Adam to his grandsons' set
Adam is not his own descendant: false

A simpler solution?

As a side note, I believe you could prevent a person from becoming his own descendant simply by sticking with val properties instead of vars. If the set of sons is immutable, you can't create a cycle in the graph since you need to have sons ready before creating the parent, and you can't add the parent to any existing descendant's sons' set. Note that I had to keep sons as a var to make the above testing code compile.

At least this is how I see it - however there may be some tricks to defeat this, using e.g. lazy evaluation which I am not very experienced with yet. So somebody please correct me if I am wrong.

Update 2

type mismatch; found : a0Dominio.ArchitecturalElement required: a0Dominio.SAComponent

I think there may be a typo here: based on the declaration of your method, SAComponent should be Component shouldn't it?

At any rate, the problem is that you declare subElements as a Set[ArchitecturalElement], so its elements are obviously of type ArchitecturalElement, which is not a Component (the inheritance relationship is the other way around). So either change subElements into a Set[Component], or declare the function parameters ancestor and current as ArchitecturalElement.

Update 3

A new member method of Person to find all persons in the descendant graph which are part of cycles:

def descendantsWithCycle = {
  def findCycle(current: Person, checked: Set[Person]): Set[Person] =
    if (checked contains current) Set(current)
    else {
      val newChecked = checked + current
      current.sons.flatMap(p => findCycle(p, newChecked))
    }

  findCycle(this, Set())
}


来源:https://stackoverflow.com/questions/13561556/scala-recursive-search-among-elements-of-a-list-of-elements

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!