问题
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.ArchitecturalElementrequired: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