Как поддерживать неизменяемый список, когда вы воздействуете на объект, связанный друг с другом в этом списке
Я пытаюсь запрограммировать быстрый алгоритм без допорядной сортировки (NDS) Deb, используемый в NSGA2, неизменным способом, используя Scala.
![enter image description here]()
Но проблема кажется более сложной, чем я думаю, поэтому я упрощаю здесь проблему создания MWE.
Представьте себе совокупность Seq[A]
, и каждый элемент A
будет decoratedA
со списком, который содержит указатели на другие элементы популяции Seq[A]
.
Функция evalA(a:decoratedA)
принимает список linkedA
, который содержит и уменьшает значение каждого из них.
Далее я возьму список подмножеств decoratedAPopulation
популяции A и вызовет evalA
для каждого. У меня проблема, потому что между каждой итерацией по элементу в этом списке подмножеств decoratedAPopulation
мне нужно обновить мою популяцию A
новым decoratedA
и обновленным linkedA
содержать...
Более проблематично, каждый элемент популяции нуждается в обновлении "linkedA", чтобы заменить связанный элемент, если он изменится...
![enter image description here]()
Hum, как вы можете видеть, кажется сложным поддерживать синхронизированный синхронизируемый список таким образом. Я предлагаю другое решение внизу, которое, вероятно, нуждается в рекурсии, чтобы вернуть после каждого evalA
новую группу с замененным элементом.
![enter image description here]()
Как я могу сделать это правильно неизменным способом?
Легко закодировать в изменчивом виде, но я не могу найти хороший способ сделать это непреложным способом, у вас есть путь или идея сделать это?
object test extends App{
case class A(value:Int) {def decrement()= new A(value - 1)}
case class decoratedA(oneAdecorated:A, listOfLinkedA:Seq[A])
// We start algorithm loop with A element with value = 0
val population = Seq(new A(0), new A(0), new A(8), new A(1))
val decoratedApopulation = Seq(new decoratedA(population(1),Seq(population(2),population(3))),
new decoratedA(population(2),Seq(population(1),population(3))))
def evalA(a:decoratedA) = {
val newListOfLinked = a.listOfLinkedA.map{e => e.decrement()
new decoratedA(a.oneAdecorated,newListOfLinked)}
}
def run()= {
//decoratedApopulation.map{
// ?
//}
}
}
Обновление 1:
О вводе/выводе исходного алгоритма.
Первая часть алгоритма Deb (Шаг 1 до Шаг 3) анализирует список Individual и вычисляет для каждого A: (a) кол-во подсчетов, число of A, которые доминируют во мне (атрибут value
A) (b) список A я доминирует (listOfLinkedA
).
Таким образом, он возвращает полностью заполненную совокупность decoratedA
, а для входа в Шаг 4 (моя проблема) я беру первый не доминируемый фронт, ср. подмножество элементов decoratedA
с A
значением = 0.
Моя проблема начинается здесь, со списком decoratedA
с A
значением = 0; и я просматриваю следующий фронт в этом списке, вычисляя каждый listOfLinkedA
каждого из этих A
На каждой итерации между шагами с 4 по 6 нужно вычислить новый B
список подмножеств decoratedA
с A
значением = 0. Для каждого, я уменьшаю сначала атрибут count доминирования каждого элемента в listOfLinkedA
, затем я фильтрую, чтобы получить элемент равным 0. A конец шага 6, B сохраняется в списке List[Seq[DecoratedA]]
, затем я перезапускаю до шага 4 с B и вычисляю новый C
и т.д..
Что-то вроде этого в моем коде, я вызываю explore()
для каждого элемента B
, а Q
равен концу нового подмножества decoratedA
с value
(пригодность здесь) = 0:
case class PopulationElement(popElement:Seq[Double]){
implicit def poptodouble():Seq[Double] = {
popElement
}
}
class SolutionElement(values: PopulationElement, fitness:Double, dominates: Seq[SolutionElement]) {
def decrement()= if (fitness == 0) this else new SolutionElement(values,fitness - 1, dominates)
def explore(Q:Seq[SolutionElement]):(SolutionElement, Seq[SolutionElement])={
// return all dominates elements with fitness - 1
val newSolutionSet = dominates.map{_.decrement()}
val filteredSolution:Seq[SolutionElement] = newSolutionSet.filter{s => s.fitness == 0.0}.diff{Q}
filteredSolution
}
}
A конец алгоритма, у меня есть окончательный список seq decoratedA
List[Seq[DecoratedA]]
, который содержит все мои фронты, вычисленные.
Обновление 2
Образец значения, извлеченный из этого примера.
Я беру только фронт парето (красный) и следующий {f, h, l} следующий фронт с преобладанием count = 1.
![enter image description here]()
case class p(x: Int, y: Int)
val a = A(p(3.5, 1.0),0)
val b = A(p(3.0, 1.5),0)
val c = A(p(2.0, 2.0),0)
val d = A(p(1.0, 3.0),0)
val e = A(p(0.5, 4.0),0)
val f = A(p(0.5, 4.5),1)
val h = A(p(1.5, 3.5),1)
val l = A(p(4.5, 1.0),1)
case class A(XY:p, value:Int) {def decrement()= new A(XY, value - 1)}
case class ARoot(node:A, children:Seq[A])
val population = Seq(
ARoot(a,Seq(f,h,l),
ARoot(b,Seq(f,h,l)),
ARoot(c,Seq(f,h,l)),
ARoot(d,Seq(f,h,l)),
ARoot(e,Seq(f,h,l)),
ARoot(f,Nil),
ARoot(h,Nil),
ARoot(l,Nil))
Возврат алгоритма List(List(a,b,c,d,e), List(f,h,l))
Обновление 3
Через 2 часа и некоторые проблемы с сопоставлением шаблонов (Ahum...) я возвращаюсь с полным примером, который автоматически вычисляет счетчик с преобладанием и дети каждого ARoot.
Но у меня такая же проблема, вычисление списка моих детей не совсем правильно, потому что каждый элемент A, возможно, является общим членом другого дочернего списка ARoot, поэтому мне нужно подумать о вашем ответе, чтобы изменить его:/В это время я только вычисляет список детей Seq[p]
, и мне нужен список Seq[A]
case class p(x: Double, y: Double){
def toSeq():Seq[Double] = Seq(x,y)
}
case class A(XY:p, dominatedCounter:Int) {def decrement()= new A(XY, dominatedCounter - 1)}
case class ARoot(node:A, children:Seq[A])
case class ARootRaw(node:A, children:Seq[p])
object test_stackoverflow extends App {
val a = new p(3.5, 1.0)
val b = new p(3.0, 1.5)
val c = new p(2.0, 2.0)
val d = new p(1.0, 3.0)
val e = new p(0.5, 4.0)
val f = new p(0.5, 4.5)
val g = new p(1.5, 4.5)
val h = new p(1.5, 3.5)
val i = new p(2.0, 3.5)
val j = new p(2.5, 3.0)
val k = new p(3.5, 2.0)
val l = new p(4.5, 1.0)
val m = new p(4.5, 2.5)
val n = new p(4.0, 4.0)
val o = new p(3.0, 4.0)
val p = new p(5.0, 4.5)
def isStriclyDominated(p1: p, p2: p): Boolean = {
(p1.toSeq zip p2.toSeq).exists { case (g1, g2) => g1 < g2 }
}
def sortedByRank(population: Seq[p]) = {
def paretoRanking(values: Set[p]) = {
//comment from @dk14: I suppose order of values isn't matter here, otherwise use SortedSet
values.map { v1 =>
val t = (values - v1).filter(isStriclyDominated(v1, _)).toSeq
val a = new A(v1, values.size - t.size - 1)
val root = new ARootRaw(a, t)
println("Root value ", root)
root
}
}
val listOfARootRaw = paretoRanking(population.toSet)
//From @dk14: Here is convertion from Seq[p] to Seq[A]
val dominations: Map[p, Int] = listOfARootRaw.map(a => a.node.XY -> a.node.dominatedCounter) //From @dk14: It a map with dominatedCounter for each point
val listOfARoot = listOfARootRaw.map(raw => ARoot(raw.node, raw.children.map(p => A(p, dominations.getOrElse(p, 0)))))
listOfARoot.groupBy(_.node.dominatedCounter)
}
//Get the first front, a subset of ARoot, and start the step 4
println(sortedByRank(Seq(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)).head)
}
Ответы
Ответ 1
Говоря о вашей проблеме с отличительными фронтами (после обновления 2):
val (left,right) = population.partition(_.node.value == 0)
List(left, right.map(_.copy(node = node.copy(value = node.value - 1))))
Не нужно ничего мутировать здесь. copy
скопирует все, кроме полей, которые вы указали с новыми значениями. Говоря о коде, новая копия будет связана с одним и тем же списком детей, но с новым value = value - 1
.
P.S. У меня такое чувство, что вы действительно можете сделать что-то вроде этого:
case class A(id: String, level: Int)
val a = A("a", 1)
val b = A("b", 2)
val c = A("c", 2)
val d = A("d", 3)
clusterize(List(a,b,c,d)) === List(List(a), List(b,c), List(d))
Это просто реализовать:
def clusterize(list: List[A]) =
list.groupBy(_.level).toList.sortBy(_._1).map(_._2)
Тест:
scala> clusterize(List(A("a", 1), A("b", 2), A("c", 2), A("d", 3)))
res2: List[List[A]] = List(List(A(a,1)), List(A(b,2), A(c,2)), List(A(d,3)))
P.S.2. Пожалуйста, рассмотрите лучшие соглашения об именах, например здесь.
Говоря о "мутирующих" элементах в некоторой сложной структуре:
Идея "непреложного мутирования" некоторой общей (между частями структуры) значения заключается в том, чтобы отделить вашу "мутацию" от структуры. Или просто говоря, разделите и убедите:
- заранее рассчитать изменения
- применить их
Код:
case class A(v: Int)
case class AA(a: A, seq: Seq[A]) //decoratedA
def update(input: Seq[AA]) = {
//shows how to decrement each value wherever it is:
val stats = input.map(_.a).groupBy(identity).mapValues(_.size) //domination count for each A
def upd(a: A) = A(a.v - stats.getOrElse(a, 0)) //apply decrement
input.map(aa => aa.copy(aa = aa.seq.map(upd))) //traverse and "update" original structure
}
Итак, я ввел новую структуру Map[A, Int]
, которая показывает, как изменить исходную. Этот подход основан на очень упрощенной версии Концепция Applicative Functor. В общем случае он должен быть Map[A, A => A]
или даже Map[K, A => B]
или даже Map[K, Zipper[A] => B]
как прикладной функтор (input <*> map
). * Застежка-молния (см. 1, 2) на самом деле может дать вам информацию о текущем элементе контекст.
Примечания:
-
Я предположил, что A
с одинаковым значением одинаковы; это поведение по умолчанию для classessess, иначе вам нужно предоставить дополнительные id
(или переопределить hashCode/equals
).
-
Если вам нужно больше уровней - например, AA(AA(AA(...))))
- просто сделайте stats
и upd
рекурсивным, если вес diecrement зависит от уровня вложенности - просто добавьте уровень вложенности в качестве параметра в свою рекурсивную функцию.
-
Если декремент зависит от родительского node (например, только декремент A(3)
', который принадлежит A(3)
) - добавьте родительский элемент node (s) как часть ключа stats
и проанализируйте его во время upd
.
-
Если существует некоторая зависимость между вычислением статистики (насколько уменьшится), скажем input(1)
из input(0)
- вы должны использовать foldLeft
с частичной статистикой в качестве аккумулятора: val stats = input.foldLeft(Map[A, Int]())((partialStats, elem) => partialStats ++ analize(partialStats, elem))
Btw, здесь требуется O(N)
(линейная память и использование процессора)
Пример:
scala> val population = Seq(A(3), A(6), A(8), A(3))
population: Seq[A] = List(A(3), A(6), A(8), A(3))
scala> val input = Seq(AA(population(1),Seq(population(2),population(3))), AA(population(2),Seq(population(1),population(3))))
input: Seq[AA] = List(AA(A(6),List(A(8), A(3))), AA(A(8),List(A(6), A(3))))
scala> update(input)
res34: Seq[AA] = List(AA(A(5),List(A(7), A(3))), AA(A(7),List(A(5), A(3))))