有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java实现可导航图的优雅方式?

这是一个设计问题。我正在努力为我面临的问题创建一个概念模型

我有许多对象的图形(<;1000)。这些物体以多种方式连接在一起。每个对象都有一些属性

我需要能够通过它们的连接和属性访问这些对象

例如,让我们假设以下对象-

{name: A, attributes:{black, thin, invalid}, connections: {B,C}} 
{name: B, attributes:{white, thin, valid}, connections: {A}} 
{name: C, attributes:{black, thick, invalid}, connections: {A,B}}

现在,我应该能够通过以下方式查询此图- 使用属性-

black - yields [A,C]
black.thick - yields C

使用连接-

A.connections[0].connections[0] - yields A

使用其组合—

black[0].connections[0] - yields B

我的主要语言是Java。但我不认为Java有能力对付这些野兽。因此,我试图用动态语言(如Python)实现这一点。 我还考虑过使用表达式语言评估,如OGNL或图形数据库。但我很困惑。我对编码解决方案不感兴趣。但是,建立这样一个问题模型的正确方法是什么


共 (4) 个答案

  1. # 1 楼答案

    听起来好像您有一些对象模型,您希望以不同的方式查询它们。一种解决方案是使用Java创建模型,然后使用脚本语言以不同的方式支持对该模型的查询。e、 g:我推荐Java+Groovy

    您可以为模型使用以下Java类

    public class Node {
    
        private String name;
        private final Set<String> attributes = new HashSet<String>();
        private final List<Node> connections = new ArrayList<Node>();
    
        // getter / setter for all
    }
    

    然后,应使用正确填充的“connections”属性填充此类对象的列表

    要支持不同类型的脚本,您需要做的是为脚本创建一个上下文,然后填充该上下文。上下文基本上是一张地图。映射的键成为脚本可用的变量。诀窍是填充此上下文以支持查询需求

    例如,在groovy中,绑定是上下文(参见http://groovy.codehaus.org/Embedding+Groovy)。因此,如果您以以下方式填充它,您的查询需求将得到满足

    上下文/绑定映射

    1. <Node name(String), Node object instance(Node)>
    2. <Attribute name(String), list of nodes having this attribute(List<Node>)>
    

    当您计算一个名为“a.connections[0]”的脚本时,将在绑定中查找针对键“a”存储的对象。然后将访问返回的对象“connections”属性。由于这是一个列表,“[0]”语法在groovy中是允许的。这将返回索引为0的对象。同样,为了支持查询需求,您需要填充上下文

  2. # 2 楼答案

    这取决于你希望你的表现在哪里

    如果您想要快速查询,并且不介意在添加对象时增加一点额外的时间/内存,那么保留一个指向具有特定属性的对象的指针数组/列表可能是一个好主意(特别是如果您在设计时知道可能的属性是什么)。然后,在添加新对象时,说:

    {name: A, attributes:{black, thin, invalid}, connections: {B,C}} 
    

    添加指向black列表thin列表和invalid列表的新指针。快速查询连接可能需要保留一个指针列表/数组作为object类的成员。然后在创建对象时,为正确的对象添加指针

    如果您不介意查询速度较慢,并且希望在添加对象时优化性能,那么链表可能是一种更好的方法。您可以循环遍历所有对象,检查每个对象是否满足查询条件

    在这种情况下,如果(如您的问题所示)您希望执行多级查询(即A.connections[0].connections[0],那么保留连接的成员指针仍然是一个好主意。如果通过嵌套循环执行,这将导致极低的性能。)

    希望这会有所帮助,这实际上取决于您希望最频繁地调用哪种查询

  3. # 3 楼答案

    我认为用图表解决这个问题是有意义的。您提到了使用图形数据库的可能性,我认为这将使您能够更好地关注您的问题,而不是编码基础设施。来自TinkerPop项目的TinkerGraph这样的简单内存图是一个很好的起点

    通过使用TinkerGraph,您可以访问名为Gremlin(另请参见GremlinDocs)的查询语言,该语言可以帮助回答您在文章中提出的问题。这里是REPL中的一个Gremlin会话,它展示了如何构造您所展示的图,以及一些示例图遍历,这些遍历可以生成您想要的答案。。。第一部分简单构造了示例中的图形:

    gremlin> g = new TinkerGraph()
    ==>tinkergraph[vertices:0 edges:0]
    gremlin> a = g.addVertex("A",['color':'black','width':'thin','status':'invalid']) 
    ==>v[A]
    gremlin> b = g.addVertex("B",['color':'white','width':'thin','status':'valid'])  
    ==>v[B]
    gremlin> c = g.addVertex("C",['color':'black','width':'thick','status':'invalid'])
    ==>v[C]
    gremlin> a.addEdge('connection',b)                                                
    ==>e[0][A-connection->B]
    gremlin> a.addEdge('connection',c)                                                
    ==>e[1][A-connection->C]
    gremlin> b.addEdge('connection',a)                                                
    ==>e[2][B-connection->A]
    gremlin> c.addEdge('connection',a)                                                
    ==>e[3][C-connection->A]
    gremlin> c.addEdge('connection',b)                                                
    ==>e[4][C-connection->B]
    

    现在查询:

    // black - yields [A,C]
    gremlin> g.V.has('color','black')
    ==>v[A]
    ==>v[C]
    
    // black.thick - yields C
    gremlin> g.V.has('width','thick')
    ==>v[C]
    
    // A.connections[0].connections[0] - yields A
    gremlin> a.out.out[0]
    ==>v[A]
    
    // black[0].connections[0] - yields B
    gremlin> g.V.has('color','black')[0].out[0]
    ==>v[B]
    

    如果您不熟悉堆栈,这种方法确实会引入一些学习曲线,但我认为您会发现,图形适合作为许多问题的解决方案,并且对TinkerPop堆栈有一些经验通常会对您遇到的其他场景有所帮助

  4. # 4 楼答案

    用Java表达这一点没有问题。只需定义表示节点集的节点的类。假设有一组固定的属性,它可能看起来像:

    enum Attribute {
        BLACK, WHITE, THIN, VALID /* etc. */ ;
    }
    class Node {
        public final String name;
        public final EnumSet<Attribute> attrs
            = EnumSet.noneOf(Attribute.class);
        public final NodeSet connections
            = new NodeSet();
    
        public Node(String name)
        {
            this.name = name;
        }
    
        // ... methods for adding attributes and connections
    }
    

    然后是一个表示一组节点的类:

    class NodeSet extends LinkedHashSet<Node> {
        /**
         * Filters out nodes with at least one of the attributes.
         */
        public NodeSet with(Attribute... as) {
            NodeSet out = new NodeSet();
            for(Node n : this) {
                for(a : as)
                    if (n.attrs.contains(a)) {
                        out.add(n);
                        break;
                    }
            }
            return out;
        }
    
        /**
         * Returns all nodes connected to this set.
         */
        public NodeSet connections() {
            NodeSet out = new NodeSet();
            for(Node n : this)
                out.addAll(n.connections);
            return out;
        }
    
        /**
         * Returns the first node in the set.
         */
        public Node first() {
            return iterator().next();
        }
    }
    

    (我没有检查代码是否编译,它只是一个草图。)然后,假设您拥有所有节点的NodeSet all,您可以执行以下操作

    all.with(BLACK).first().connections()