这是一个设计问题。我正在努力为我面临的问题创建一个概念模型。
我有一些对象的图表(< 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或Graph数据库这样的表达式语言评估。但我很困惑。我对编码解决方案不感兴趣。但建模这样一个问题的正确方法是什么?
答案 0 :(得分:1)
听起来你有一些想要以不同方式查询的对象模型。一种解决方案是使用Java来创建模型,然后使用脚本语言来支持以不同方式查询此模型。例如: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'属性。由于这是一个列表,因此在groovy中允许使用'[0]'语法。这将返回索引0处的对象。同样,为了支持您的查询要求,您需要填充上下文。
答案 1 :(得分:0)
这取决于您希望表演的目标。
如果你想要快速查询,并且在添加对象时不介意一些额外的时间/内存,保留指向具有特定属性的对象的数组/列表可能是一个好主意(特别是如果您在设计时知道可能的属性是什么)。然后,在添加新对象时,请说:
{name: A, attributes:{black, thin, invalid}, connections: {B,C}}
添加一个指向black
列表,thin
列表和invalid
列表的新指针。对连接的快速查询可能需要将指针列表/数组保留为object
类的成员。然后在创建对象时,为正确的对象添加指针。
如果您不介意查询速度较慢并希望在添加对象时优化性能,则链接列表可能是更好的方法。您可以遍历所有对象,检查每个对象是否满足查询条件。
在这种情况下,保留连接的成员指针仍然是一个好主意,如果(正如您的问题似乎表明)您正在寻求进行多级查询(即A.connections[0].connections[0]
。如果通过嵌套循环完成,这将导致极差的性能。)
希望这有帮助,它实际上取决于您期望最常呼叫的查询类型。
答案 2 :(得分:0)
在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()
答案 3 :(得分:0)
我认为用图表解决这个问题是有道理的。您提到使用图形数据库的可能性,我认为这将使您能够更好地专注于您的问题,而不是编码基础设施。来自TinkerGraph项目的简单内存图(如TinkerPop)将是一个很好的起点。
通过使用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堆栈有一些经验对于您遇到的其他场景通常会有所帮助