Acordei no final da tarde e decidi - está na hora de criar um novo recurso na minha biblioteca . E por um lado e verifique o gráfico quanto a ciclos para corrigir e acelerar. No almoço da manhã, criei um novo recurso, aprimorei o código, fiz a representação do gráfico de forma conveniente e cheguei ao problema de encontrar todos os ciclos simples no gráfico. Bebendo um copo de água fria, abri o Google, digitei "pesquisar todos os ciclos simples em um gráfico" e vi ...
Não vi muito ... embora fossem apenas quatro horas da manhã. Alguns links para os algoritmos link1 , link2 e muitas dicas queé hora de dormirPode haver muitos ciclos no gráfico e, no caso geral, o problema não é solucionável. E outra pergunta sobre Habré sobre esse assunto, cuja resposta também não me salvou - eu já sabia sobre a pesquisa em profundidade.
Mas, uma vez resolvido, mesmo o NP resolverá um problema difícil em P , mais eu bebo água, não café - e não pode terminar abruptamente. E eu sabia que, dentro da estrutura da minha tarefa, deveria ser possível encontrar todos os ciclos em um curto espaço de tempo, sem supercomputadores - não a ordem de grandeza para mim.
Vamos desviar um pouco do detetive e entender por que eu preciso.
O que é essa biblioteca?
A biblioteca chamada DITranquility é escrita em Swift e sua tarefa é injetar dependências . A biblioteca lida com a tarefa de injeção de dependência com um estrondo, possui recursos que outras bibliotecas Swift não conseguem e faz isso com boa velocidade.
Mas por que eu deveria adquirir o hábito de procurar loops no gráfico de dependência?
Por uma questão de killerfichi - se a biblioteca executa a funcionalidade principal com um estrondo, você está procurando maneiras de melhorá-la e melhorá-la. Um killefic está verificando a correção do gráfico de dependência - é um conjunto de verificações diferentes que permitem evitar problemas durante a execução, o que economiza tempo de desenvolvimento. E a verificação de ciclos se destaca separadamente entre todas as verificações, pois essa operação leva muito mais tempo. E até recentemente, não civilizava mais tempo.
, , , . , "Graph API" . "Graph API" — , :
- - . , , , .
- , - — graphvis.
- .
— , ...
, :
- MacBook pro 2019, 2,6 GHz 6-Core i7, 32 Gb, Xcode 11.4, Swift 5.2
- Swift 300+ ( )
- 900
- 2000
- 40
- 7000
debug , release, debug.
, 95 .
3 , .
1.
. , .
, . Graph API , , . . , , , .
, , — . , , , :
, , , . , — , . — ?
, - :
Graph:
vertices: [Vertex]
adjacencyList: [[Edge]]
Vertex:
more information about vertex
Edge:
toIndices: [Int]
information about edge
Vertex , Edge , , .
, . , , , , .
2.
func findAllCycles() -> [Cycle] {
result: [Cycle] = []
for index in vertices {
result += findCycles(from: index)
}
return result
}
func findCycles(from index: Int) -> [Cycle] {
result: [Cycle] = []
dfs(startIndex: index, currentIndex: index, visitedIndices: [], result: &result)
return result
}
func dfs(startIndex: Int,
currentIndex: Int,
// visitedIndices
visitedIndices: Set<Int>,
// result -
result: ref [Cycle]) {
if currentIndex == startIndex && !visitedIndices.isEmpty {
result.append(cycle)
return
}
if visitedIndices.contains(currentIndex) {
return
}
visitedIndices += [currentIndex]
for toIndex in adjacencyList[currentIndex] {
dfs(startIndex: startIndex, currentIndex: toIndex, visitedIndices: visitedIndices, result: &result)
}
}
, 10 … , , — - ...
, — ? , ? , dfs , 30 . 900 * 1000000 = 900.000.000 dfs…
3.
, , , , , - … , , , !
, , , , . — , , . , , , - :
func findAllCycles() -> [Cycle] {
globalVisitedIndices: Set<Int> = []
result: [Cycle] = []
for index in vertices {
if globalVisitedIndices.containts(index) {
continue
}
result += findCycles(from: index, globalVisitedIndices: &globalVisitedIndices)
}
return result
}
func findCycles(from index: Int, globalVisitedIndices: ref Set<Int>) -> [Cycle] {
result: [Cycle] = []
dfs(currentIndex: index, visitedIndices: [], globalVisitedIndices, &globalVisitedIndices, result: &result)
return result
}
func dfs(currentIndex: Int,
// visitedIndices
visitedIndices: Set<Int>,
// globalVisitedIndices -
globalVisitedIndices: ref Set<Int>,
// result -
result: ref [Cycle]) {
if visitedIndices.contains(currentIndex) {
// visitedIndices , ,
result.append(cycle)
return
}
visitedIndices += [currentIndex]
for toIndex in adjacencyList[currentIndex] {
dfs(currentIndex: toIndex, visitedIndices: visitedIndices, globalVisitedIndices: &globalVisitedIndices, result: &result)
}
}
" , " … . 10 , , , , :
- , , , .
- "" — , .
, . , , .
4.
, . , , , , , , . , ? . , , run… , — , … , … … , . :
if visitedIndices.contains(currentIndex) {
, , . :
B->E->C , if . , :
A->B->E->C->B!.. C, . A->B->D->A.
A->C->B->D->A , C .
, dfs , .
5.
, . , , , dfs 30 , 1-2 . :
"Big" - , A.
! Big C, , A B, , C , , A.
? , , , . . O(N^2) , .
, :
func findAllCycles() -> [Cycle] {
reachableIndices: [Set<Int>] = findAllReachableIndices()
result: [Cycle] = []
for index in vertices {
result += findCycles(from: index, reachableIndices: &reachableIndices)
}
return result
}
func findAllReachableIndices() -> [Set<Int>] {
reachableIndices: [Set<Int>] = []
for index in vertices {
reachableIndices[index] = findAllReachableIndices(for: index)
}
return reachableIndices
}
func findAllReachableIndices(for startIndex: Int) -> Set<Int> {
visited: Set<Int> = []
stack: [Int] = [startIndex]
while fromIndex = stack.popFirst() {
visited.insert(fromIndex)
for toIndex in adjacencyList[fromIndex] {
if !visited.contains(toIndex) {
stack.append(toIndex)
}
}
}
return visited
}
func findCycles(from index: Int, reachableIndices: ref [Set<Int>]) -> [Cycle] {
result: [Cycle] = []
dfs(startIndex: index, currentIndex: index, visitedIndices: [], reachableIndices: &reachableIndices, result: &result)
return result
}
func dfs(startIndex: Int,
currentIndex: Int,
visitedIndices: Set<Int>,
reachableIndices: ref [Set<Int>],
result: ref [Cycle]) {
if currentIndex == startIndex && !visitedIndices.isEmpty {
result.append(cycle)
return
}
if visitedIndices.contains(currentIndex) {
return
}
if !reachableIndices[currentIndex].contains(startIndex) {
return
}
visitedIndices += [currentIndex]
for toIndex in adjacencyList[currentIndex] {
dfs(startIndex: startIndex, currentIndex: toIndex, visitedIndices: visitedIndices, result: &result)
}
}
, , , 5 — . — 15 , 6-7 . , , , — .
6. ?
, , — - . , , - .
, , , .
— " , , , ?". . 5 .
, 250, , , :
func findAllCycles() -> [Cycle] {
reachableIndices: [Set<Int>] = findAllReachableIndices()
result: [Cycle] = []
for index in vertices {
result += findCycles(from: index, reachableIndices: &reachableIndices)
}
return result
}
func findAllReachableIndices() -> [Set<Int>] {
reachableIndices: [Set<Int>] = []
for index in vertices {
reachableIndices[index] = findAllReachableIndices(for: index)
}
return reachableIndices
}
func findAllReachableIndices(for startIndex: Int) -> Set<Int> {
visited: Set<Int> = []
stack: [Int] = [startIndex]
while fromIndex = stack.popFirst() {
visited.insert(fromIndex)
for toIndex in adjacencyList[fromIndex] {
if !visited.contains(toIndex) {
stack.append(toIndex)
}
}
}
return visited
}
func findCycles(from index: Int, reachableIndices: ref [Set<Int>]) -> [Cycle] {
result: [Cycle] = []
dfs(startIndex: index, currentIndex: index, visitedIndices: [], reachableIndices: &reachableIndices, result: &result)
return result
}
func dfs(startIndex: Int,
currentIndex: Int,
visitedIndices: Set<Int>,
reachableIndices: ref [Set<Int>],
result: ref [Cycle]) {
if currentIndex == startIndex && !visitedIndices.isEmpty {
result.append(cycle)
return
}
if visitedIndices.contains(currentIndex) {
return
}
if currentIndex < startIndex || !reachableIndices[currentIndex].contains(startIndex) {
return
}
visitedIndices += [currentIndex]
for toIndex in adjacencyList[currentIndex] {
dfs(startIndex: startIndex, currentIndex: toIndex, visitedIndices: visitedIndices, result: &result)
}
}
: if currentIndex < startIndex
.
, run, , — … 6 ? , … .
, , , . — , A, , A . A, .
, , /.
— , . 5-10% .
6.
5-6 , , ! . , Swift , .
, , 6 … , . — " ? ...". — . — , .
3 , , . , Apple , (, , ). Swift popLast()
, . .
var visited: Set<Int> = []
var stack: [Int] = [startVertexIndex]
while let fromIndex = stack.first {
stack.removeFirst()
visited.insert(fromIndex)
for toIndex in graph.adjacencyList[fromIndex].flatMap({ $0.toIndices }) {
if !visited.contains(toIndex) {
stack.append(toIndex)
}
}
}
return visited
var visited: Set<Int> = []
var stack: [Int] = [startVertexIndex]
while let fromIndex = stack.popLast() {
visited.insert(fromIndex)
for toIndex in graph.adjacencyList[fromIndex].flatMap({ $0.toIndices }) {
if !visited.contains(toIndex) {
stack.insert(toIndex, at: 0)
}
}
}
return visited
, , — , Swift 5-10%.
? — 95 , 2.5-3 , .
, , — .
, , 15 , .
— , , , .
, "" -. , - :
- graphvis — .
- — , , , .
- , .
P.S. 5 , . , 1.5 — 4.5 . .
P.P.S. , . , , //.
UPD: banshchikov Donald B. Johnson. swift.
, .
:
_ | ||
---|---|---|
(debug) | 2.4-2.5 | 36.2-44.5 |
() | 0.25-0.33 | 32.1-36.4 |
6920* | 5736 | |
* | 5736 | 5736 |
O tempo foi medido apenas para a pesquisa de ciclos. Uma biblioteca de terceiros converte os dados de entrada em uma conveniente, mas essa conversão não deve levar mais que 0,1 segundos. E como você pode ver, o tempo difere por uma ordem de magnitude (e no release por duas). E uma diferença tão grande não pode ser atribuída a uma implementação não ideal.
- Como escrevi, na minha biblioteca, as arestas não são apenas uma transição de um vértice para outro. Essas informações não podem ser passadas para uma implementação de terceiros, portanto, o número de loops encontrados não corresponde. Por esse motivo, os resultados são pesquisados em todos os ciclos exclusivos ao longo dos vértices, excluindo as arestas, para garantir que o resultado corresponda.