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('?'):
                bpos[x] = pos
        rows = list(triples(tuple(qc)))
        if bindings == None:
            # 1st pass
            bindings = [{var: row[pos] for var, pos in bpos.items()}
                        for row in rows]
            # >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]:
                            binding[var] = row[pos]
    return bindings

The function invocation looks like:


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?


1 Answer 1

  1. Your code would be helped by splitting parts of it out into functions.
  2. Your continue statement does nothing
  3. 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()
        return bindings
        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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.