English 中文(简体)
Python nested lists and recursion problem
原标题:
  • 时间:2009-11-24 03:24:00
  •  标签:
  • python
  • logic

I m trying to process a first order logic formula represented as nested lists and strings in python so that that its in disjunctive normal form,

i.e [ & , [ | , a , b ], [ | , c , d ]]

turns into

[ | [ & , [ & , a , c ], [ & , b , c ]], [ & , [ & , a , d ], [ & , b , d ]]]

where | is or and & is and .

currently im using a recursive implementation which does several passes over a formula until it can t find any nested or symbols inside a list argument for ands .

This is my implementation, performDNF(form) is the entry point. Right now it performs a single pass over the formula but then the while loop checking function finds no | s inside of & s and terminates, help anyone this is driving me mad.

def dnfDistributivity(self, form):
    if isinstance(form, type([])):
        if len(form) == 3:
            if form[0] ==  & :
                if form[1][0] ==  | :
                    form = [ | , [ & , form[2], form[1][1]], [ & , form[2], form[1][2]]]
                elif form[2][0] ==  | :
                    form = [ | , [ & , form[1], form[2][1]], [ & , form[1], form[2][2]]]
            form[1] = self.dnfDistributivity(form[1])
            form[2] = self.dnfDistributivity(form[2])
        elif len(form) == 2:
            form[1] = self.dnfDistributivity(form[1])
    return form

def checkDistributivity(self, form, result = 0):
    if isinstance(form, type([])):
        if len(form) == 3:
            if form[0] ==  & :
                print "found &"
                if isinstance(form[1], type([])):
                    if form[1][0] ==  | :
                        return 1
                elif isinstance(form[2], type([])):
                    if form[2][0] ==  | :
                        return 1
                else:
                    result = self.checkDistributivity(form[1], result)
                    print result
                    if result != 1:
                        result = self.checkDistributivity(form[2], result)
                        print result
        elif len(form) == 2:
            result = self.checkDistributivity(form[1], result)
            print result
    return result

def performDNF(self, form):
    while self.checkDistributivity(form):
        form = self.dnfDistributivity(self.dnfDistributivity(form))
    return form
问题回答

Okay, here is an actual solution that seems to work.

I do not understand your code, and I hadn t heard of DNF, so I started out by studying the problem some more.

The Wikipedia page on DNF was very helpful. It included a grammar that describes DNF.

Based on that, I wrote a simple set of recursive functions that I believe correctly recognize DNF in the format you require. My code includes some simple test cases.

Then I realized that the binary tree nature of your data representation makes it relatively simple to apply DeMorgan s laws to simplify the not case, by writing a function called negate() that recursively negates, and the rest fell into place.

I have included test cases. It seems to work.

I have no further plans to work on this. If someone finds a bug, please provide a test case and I ll look at it.

This code should run on any Python version 2.4 or newer. You could even port it to older Python versions by replacing the frozenset with a simple list of characters. I tested with Python 3.x, and found the exception syntax has changed, so you will need to change the raise lines if you want to run it under Python 3; the important parts all work.

In the question, you did not mention what character you use for not; given your use of & for and and | for or, I assumed you likely use ! for not and wrote the code accordingly. This is one of the things that puzzles me about your code: don t you ever expect to find not in your input?

I had some fun working on this. It s not as pointless as a sudoku puzzle.

import sys


ch_and =  & 
ch_not =  ! 
ch_or =  | 

def echo(*args):
    # like print() in Python 3 but works in 2.x or in 3
    sys.stdout.write(" ".join(str(x) for x in args) + "
")

try:
    symbols = frozenset([ch_and, ch_not, ch_or])
except NameError:
    raise Exception, "sorry, your Python is too old for this code"

try:
    __str_type = basestring
except NameError:
    __str_type = str


def is_symbol(x):
    if not isinstance(x, __str_type) or len(x) == 0:
        return False
    return x[0] in symbols

def is_and(x):
    if not isinstance(x, __str_type) or len(x) == 0:
        return False
    return x[0] == ch_and

def is_or(x):
    if not isinstance(x, __str_type) or len(x) == 0:
        return False
    return x[0] == ch_or

def is_not(x):
    if not isinstance(x, __str_type) or len(x) == 0:
        return False
    return x[0] == ch_not

def is_literal_char(x):
    if not isinstance(x, __str_type) or len(x) == 0:
        return False
    return x[0] not in symbols

def is_list(x, n):
    return isinstance(x, list) and len(x) == n


def is_literal(x):
    """
True if x is a literal char, or a  not  followed by a literal char."""
    if is_literal_char(x):
        return True
    return is_list(x, 2) and is_not(x[0]) and is_literal_char(x[1])


def is_conjunct(x):
    """
True if x is a literal, or  and  followed by two conjuctions."""
    if is_literal(x):
        return True
    return (is_list(x, 3) and
            is_and(x[0]) and is_conjunct(x[1]) and is_conjunct(x[2]))


def is_disjunct(x):
    """
True if x is a conjunction, or  or  followed by two disjuctions."""
    if is_conjunct(x):
        return True
    return (is_list(x, 3) and
            is_or(x[0]) and is_disjunct(x[1]) and is_disjunct(x[2]))



def is_dnf(x):
    return is_disjunct(x)

def is_wf(x):
    """returns True if x is a well-formed list"""
    if is_literal(x):
        return True
    elif not isinstance(x, list):
        raise TypeError, "only lists allowed"
    elif len(x) == 2 and is_not(x[0]) and is_wf(x[1]):
        return True
    else:
        return (is_list(x, 3) and (is_and(x[0]) or is_or(x[0])) and
                is_wf(x[1]) and is_wf(x[2]))

def negate(x):
    # trivial: negate a returns !a
    if is_literal_char(x):
        return [ch_not, x]

    # trivial: negate !a returns a
    if is_list(x, 2) and is_not(x[0]):
        return x[1]

    # DeMorgan s law:  negate (a && b) returns (!a || !b)
    if is_list(x, 3) and is_and(x[0]):
        return [ch_or, negate(x[1]), negate(x[2])]

    # DeMorgan s law:  negate (a || b) returns (!a && !b)
    if is_list(x, 3) and is_or(x[0]):
        return [ch_and, negate(x[1]), negate(x[2])]

    raise ValueError, "negate() only works on well-formed values."

def __rewrite(x):
    # handle all dnf, which includes simple literals.
    if is_dnf(x):
        # basis case. no work to do, return unchanged.
        return x

    if len(x) == 2 and is_not(x[0]):
        x1 = x[1]
        if is_list(x1, 2) and is_not(x1[0]):
            # double negative!  throw away the  not   not  and keep rewriting.
            return __rewrite(x1[1])
        assert is_list(x1, 3)
        # handle non-inner  not 
        return __rewrite(negate(x1))

    # handle  and  with  or  inside it
    assert is_list(x, 3) and is_and(x[0]) or is_or(x[0])
    if len(x) == 3 and is_and(x[0]):
        x1, x2 = x[1], x[2]
        if ((is_list(x1, 3) and is_or(x1[0])) and
                (is_list(x2, 3) and is_or(x2[0]))):
# (a || b) && (c || d) -- (a && c) || (b && c) || (a && d) || (b && d)
            lst_ac = [ch_and, x1[1], x2[1]]
            lst_bc = [ch_and, x1[2], x2[1]]
            lst_ad = [ch_and, x1[1], x2[2]]
            lst_bd = [ch_and, x1[2], x2[2]]
            new_x = [ch_or, [ch_or, lst_ac, lst_bc], [ch_or, lst_ad, lst_bd]]
            return __rewrite(new_x)

        if (is_list(x2, 3) and is_or(x2[0])):
# a && (b || c)  -- (a && b) || (a && c)
            lst_ab = [ch_and, x1, x2[1]]
            lst_ac = [ch_and, x1, x2[2]]
            new_x = [ch_or, lst_ab, lst_ac]
            return __rewrite(new_x)

        if (is_list(x1, 3) and is_or(x1[0])):
# (a || b) && c  -- (a && c) || (b && c)
            lst_ac = [ch_and, x1[1], x2]
            lst_bc = [ch_and, x1[2], x2]
            new_x = [ch_or, lst_ac, lst_bc]
            return __rewrite(new_x)

    return [x[0], __rewrite(x[1]), __rewrite(x[2])]
    #return x

def rewrite(x):
    if not is_wf(x):
        raise ValueError, "can only rewrite well-formed lists"
    while not is_dnf(x):
        x = __rewrite(x)
    return x


#### self-test code ####

__failed = False
__verbose = True
def test_not_wf(x):
    global __failed
    if is_wf(x):
        echo("is_wf() returned True for:", x)
        __failed = True

def test_dnf(x):
    global __failed
    if not is_wf(x):
        echo("is_wf() returned False for:", x)
        __failed = True
    elif not is_dnf(x):
        echo("is_dnf() returned False for:", x)
        __failed = True

def test_not_dnf(x):
    global __failed
    if not is_wf(x):
        echo("is_wf() returned False for:", x)
        __failed = True
    elif is_dnf(x):
        echo("is_dnf() returned True for:", x)
        __failed = True
    else:
        xr = rewrite(x)
        if not is_wf(xr):
            echo("rewrite produced non-well-formed for:", x)
            echo("result was:", xr)
            __failed = True
        elif not is_dnf(xr):
            echo("rewrite failed for:", x)
            echo("result was:", xr)
            __failed = True
        else:
            if __verbose:
                echo("original:", x)
                echo("rewritten:", xr)
                echo()

def self_test():
    a, b, c, d =  a ,  b ,  c ,  d 
    test_dnf(a)
    test_dnf(b)
    test_dnf(c)
    test_dnf(d)

    lstna = [ch_not, a]
    test_dnf(lstna)

    lstnb = [ch_not, b]
    test_dnf(lstnb)

    lsta = [ch_and, a, b]
    test_dnf(lsta)

    lsto = [ch_or, a, b]
    test_dnf(lsto)

    test_dnf([ch_and, lsta, lsta])

    test_dnf([ch_or, lsta, lsta])

    lstnn = [ch_not, [ch_not, a]]
    test_not_dnf(lstnn)

    test_not_dnf([ch_and, lstnn, lstnn])

    # test  and / or  inside  not 
    test_not_dnf([ch_not, lsta])
    test_not_dnf([ch_not, lsto])

    # test  or  inside of  and 
    # a&(b|c) --> (a&b)|(b&c)
    test_not_dnf([ch_and, a, [ch_or, b, c]])
    # (a|b)&c --> (a&c)|(b&c)
    test_not_dnf([ch_and, [ch_or, a, b], c])
    # (a|b)&(c|d) --> ((a&c)|(b&c))|((a&d)|(b&d))
    test_not_dnf([ch_and, [ch_or, a, b], [ch_or, c, d]])

    # a&a&a&(b|c) --> a&a&(a&b|b&c) --> a&(a&a&b|a&b&c) --> (a&a&a&b|a&a&b&c)
    test_not_dnf([ch_and, a, [ch_and, a, [ch_and, a, [ch_or, b, c]]]])

    if __failed:
        echo("one or more tests failed")

self_test()

Now, I m sorry to say this, but the more I think about it, the more I think you probably just got me to do your homework for you. So, I just wrote an improved version of this code, but I m not planning to share it here; I ll leave it as an exercise for you. You should be able to do it easily, after I describe it.

It s a horrible hack that I have a while loop repeatedly calling __rewrite(). The rewrite() function should be able to rewrite the tree structure with a single call to __rewrite(). With just a few simple changes, you can get rid of the while loop; I did it, and tested it, and it works. You want __rewrite() to walk the tree down and then rewrite stuff on the way back up, and it will work in one pass. You could also modify __rewrite() to return an error if the list isn t well-formed, and get rid of the call to is_wf(); that s also easy.

I suspect your teacher would dock you points for the while loop, so you should be motivated to try this. I hope you have fun doing it, and I hope you learned something useful from my code.

I didn t really try to understand your solution, sorry. I think it is way too difficult to read and probably the approach is too cumbersome.

If you have a DNF, all you need to do is find all combinations of atoms, taking one out of each sublist. This pretty much boils down to this problem ... from each OR-subclause you need to take one atom, and combine all those with AND.

The OR-combination of all those possible AND-clauses yields your desired result. Right?





相关问题
Can Django models use MySQL functions?

Is there a way to force Django models to pass a field to a MySQL function every time the model data is read or loaded? To clarify what I mean in SQL, I want the Django model to produce something like ...

An enterprise scheduler for python (like quartz)

I am looking for an enterprise tasks scheduler for python, like quartz is for Java. Requirements: Persistent: if the process restarts or the machine restarts, then all the jobs must stay there and ...

How to remove unique, then duplicate dictionaries in a list?

Given the following list that contains some duplicate and some unique dictionaries, what is the best method to remove unique dictionaries first, then reduce the duplicate dictionaries to single ...

What is suggested seed value to use with random.seed()?

Simple enough question: I m using python random module to generate random integers. I want to know what is the suggested value to use with the random.seed() function? Currently I am letting this ...

How can I make the PyDev editor selectively ignore errors?

I m using PyDev under Eclipse to write some Jython code. I ve got numerous instances where I need to do something like this: import com.work.project.component.client.Interface.ISubInterface as ...

How do I profile `paster serve` s startup time?

Python s paster serve app.ini is taking longer than I would like to be ready for the first request. I know how to profile requests with middleware, but how do I profile the initialization time? I ...

Pragmatically adding give-aways/freebies to an online store

Our business currently has an online store and recently we ve been offering free specials to our customers. Right now, we simply display the special and give the buyer a notice stating we will add the ...

Converting Dictionary to List? [duplicate]

I m trying to convert a Python dictionary into a Python list, in order to perform some calculations. #My dictionary dict = {} dict[ Capital ]="London" dict[ Food ]="Fish&Chips" dict[ 2012 ]="...

热门标签