HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Querying a data structure that contains various triples

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
triplesqueryingthatstructurecontainsdatavarious

Problem

I wrote code that queries a data structure that contains various triples - 3-item tuples in the form subject-predicate-object. I wrote this code based on an exercise from Programming the Semantic Web textbook.

def query(clauses):
    bindings = None
    for clause in clauses:
        bpos = {}
        qc = []
        for pos, x in enumerate(clause):
            if x.startswith('?'):
                qc.append(None)
                bpos[x] = pos
            else:
                qc.append(x)
        rows = list(triples(tuple(qc)))
        if bindings == None:
            # 1st pass
            bindings = [{var: row[pos] for var, pos in bpos.items()}
                        for row in rows]
        else:
            # >2 pass, eliminate wrong bindings
            for binding in bindings:
                for row in rows:
                    for var, pos in bpos.items():
                        if var in binding:
                            if binding[var] != row[pos]:
                                bindings.remove(binding)
                                continue
                        else:
                            binding[var] = row[pos]
    return bindings


The function invocation looks like:

bg.query([('?person','lives','Chiapas'),
          ('?person','advocates','Zapatism')])


The function triples inside it accepts 3-tuples and returns list of 3-tuples. It can be found here.

The function query loops over each clause, tracks variables (strings that start with '?'), replaces them with None, invokes triples, receives rows, tries to fit values to existing bindings.

How can I simplify this code? How can I make it more functional-style, without nested for-loops, continue keyword and so on?

Solution


  • Your code would be helped by splitting parts of it out into functions.



  • Your continue statement does nothing



  • Rather then having a special case for the first time through, initialize bindings to [{}] for the same effect.



My version:

def clause_to_query(clause):
    return tuple(None if term.startswith('?') else term for term in clause)

def compatible_bindings(clause, triples):
     for triple in triples:
        yield { clause_term : fact_term
            for clause_term, fact_term in zip(clause, row) if clause_term.startswith('?')
        }

def merge_bindings(left, right):
    shared_keys = set(left.keys()).intersection(right.keys)

    if all(left[key] == right[key] for key in shared_keys):
        bindings = left.copy()
        bindings.update(right)
        return bindings
    else:
        return None # indicates that a binding cannot be merged

def merge_binding_lists(bindings_left, bindings_right):
    new_bindings = []
    for left, right in itertools.product(bindings_left, bindings_right):
        merged_binding = merge_bindings(left, right)
        if merged_binding is not None:
            new_bindings.append( merged_binding )

    return new_bindings

def query(clauses):
    bindings = [{}]
    for clause in clauses:
        query = clause_to_query(clause)
        new_bindings = compatible_bindings(clause, triples(query))
        bindings = merge_binding_lists(bindings, new_bindings)

    return bindings

Code Snippets

def clause_to_query(clause):
    return tuple(None if term.startswith('?') else term for term in clause)

def compatible_bindings(clause, triples):
     for triple in triples:
        yield { clause_term : fact_term
            for clause_term, fact_term in zip(clause, row) if clause_term.startswith('?')
        }

def merge_bindings(left, right):
    shared_keys = set(left.keys()).intersection(right.keys)

    if all(left[key] == right[key] for key in shared_keys):
        bindings = left.copy()
        bindings.update(right)
        return bindings
    else:
        return None # indicates that a binding cannot be merged


def merge_binding_lists(bindings_left, bindings_right):
    new_bindings = []
    for left, right in itertools.product(bindings_left, bindings_right):
        merged_binding = merge_bindings(left, right)
        if merged_binding is not None:
            new_bindings.append( merged_binding )

    return new_bindings


def query(clauses):
    bindings = [{}]
    for clause in clauses:
        query = clause_to_query(clause)
        new_bindings = compatible_bindings(clause, triples(query))
        bindings = merge_binding_lists(bindings, new_bindings)

    return bindings

Context

StackExchange Code Review Q#20278, answer score: 4

Revisions (0)

No revisions yet.