s0rg/decompose

View on GitHub
internal/cluster/layers.go

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
package cluster

import (
    "fmt"
    "io"
    "strconv"
    "strings"

    "github.com/s0rg/set"
    "github.com/s0rg/trie"

    "github.com/s0rg/decompose/internal/graph"
    "github.com/s0rg/decompose/internal/node"
)

type Layers struct {
    b           graph.NamedBuilderWriter
    edges       map[string]map[string]*node.Ports
    nodes       map[string]*node.Node
    remotes     set.Unordered[string]
    g           connGraph
    defaultName string
    similarity  float64
}

func NewLayers(
    b graph.NamedBuilderWriter,
    s float64,
    d string,
) *Layers {
    return &Layers{
        b:           NewRules(b, nil),
        g:           make(connGraph),
        edges:       make(map[string]map[string]*node.Ports),
        nodes:       make(map[string]*node.Node),
        remotes:     make(set.Unordered[string]),
        similarity:  s,
        defaultName: d,
    }
}

func (l *Layers) Name() string {
    return l.b.Name() + " auto:" +
        strconv.FormatFloat(l.similarity, 'f', 3, 64)
}

func (l *Layers) AddNode(n *node.Node) error {
    l.nodes[n.ID] = n

    if n.IsExternal() {
        l.remotes.Add(n.ID)

        return nil
    }

    l.g.AddNode(n)

    return nil
}

func (l *Layers) AddEdge(e *node.Edge) {
    l.upsertEdge(e.SrcID, e.DstID, e.Port)

    if l.remotes.Has(e.SrcID) || l.remotes.Has(e.DstID) {
        return
    }

    l.g.AddEdge(e.SrcID, e.DstID)
}

func (l *Layers) upsertEdge(src, dst string, p *node.Port) {
    dest, ok := l.edges[src]
    if !ok {
        dest = make(map[string]*node.Ports)
    }

    var ports *node.Ports

    if ports, ok = dest[dst]; !ok {
        ports = &node.Ports{}
        dest[dst] = ports
    }

    ports.Add(clusterPorts, p)

    l.edges[src] = dest
}

func (l *Layers) names(ids []string) (rv []string) {
    rv = make([]string, 0, len(ids))

    for _, id := range ids {
        n := l.nodes[id]

        rv = append(rv, n.Name)
    }

    return rv
}

func (l *Layers) Write(w io.Writer) error {
    var (
        seen  = make(set.Unordered[string])
        layer []string
    )

    const maxLabelParts = 3

    for i := 0; ; i++ {
        layer = l.g.NextLayer(layer, seen)
        if len(layer) == 0 {
            break
        }

        grp := NewGrouper(l.similarity)

        for _, id := range layer {
            grp.Add(id, l.g[id])
        }

        grp.IterGroups(func(id int, membersID []string) {
            label := CreateLabel(l.names(membersID), maxLabelParts)

            for _, mid := range membersID {
                n := l.nodes[mid]
                n.Cluster = fmt.Sprintf("l%02d-%02d-%s", i, id, label)

                _ = l.b.AddNode(n)

                delete(l.nodes, mid)
            }
        })
    }

    // store remains
    for _, n := range l.nodes {
        n.Cluster = l.defaultName
        _ = l.b.AddNode(n)
    }

    for src, dmap := range l.edges {
        for dst, ports := range dmap {
            ports.Iter(func(_ string, plist []*node.Port) {
                for _, p := range plist {
                    l.b.AddEdge(&node.Edge{
                        SrcID:   src,
                        DstID:   dst,
                        SrcName: clusterPorts,
                        DstName: clusterPorts,
                        Port:    p,
                    })
                }
            })
        }
    }

    if err := l.b.Write(w); err != nil {
        return fmt.Errorf("auto: %w", err)
    }

    return nil
}

func CreateLabel(names []string, nmax int) (rv string) {
    t := trie.New[struct{}]()
    v := struct{}{}

    for _, n := range names {
        t.Add(n, v)
    }

    const (
        root    = ""
        minus   = "-"
        cutset  = "1234567890" + minus
        maxdiff = 3
    )

    comm := t.Common(root, maxdiff)
    if len(comm) > nmax {
        comm = comm[:nmax]
    }

    for i := 0; i < len(comm); i++ {
        comm[i] = strings.Trim(comm[i], cutset)

        if k := strings.Index(comm[i], minus); k > 0 {
            comm[i] = comm[i][:k]
        }
    }

    return strings.Join(comm, minus)
}