Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find in a dynamic pythonic way the minimum elements in a partially ordered set

Let Os be a partially ordered set, and given any two objects O1 and O2 in Os, F(O1,O2) will return 1 if O1 is bigger than O2, -1 if O1 is smaller than O2, 2 if they are incomparable, and 0 if O1 is equal to O2.

I need to find the subset Mn of elements is Os that are the minimum. That is for each A in Mn, and for each B in Os, F(A,B) is never equal to 1.

It is not hard to do, but I am convinced it could be done in a more pythonic way.

The fast and dirty way is:

def GetMinOs(Os):
    Mn=set([])
    NotMn=set([])
    for O1 in Os:
       for O2 in Os:
           rel=f(O1,O2)
           if rel==1:       NotMn|=set([O1])
           elif rel==-1:    NotMn|=set([O2])
    Mn=Os-NotMn
    return Mn

In particular I am not happy with the fact that I am essentially going through all the elements N^2 times. I wonder if there could be a dynamic way. By "dynamic" I don't mean merely fast, but also such that once something is discovered being not a possible in the minimum, maybe it could be taken off. And doing all this in a pythonic, elegant way

like image 430
Pietro Speroni Avatar asked Nov 05 '22 06:11

Pietro Speroni


1 Answers

GetMinOs2 below, "dynamically" removes elements which are known to be non-minimal. It uses a list Ol which starts with all the elements of Os. A "pointer" index l points to the "end" of the list Ol. When a non-minimal element is found, its position is swapped with the value in Ol[l] and the pointer l is decremented so the effective length of Ol is shrunk. Doing so removes non-minimal elements, so you don't check them again.

GetMinOs2 assumes f has the normal properties of a comparison function: transitivity, commutativity, etc.

In the test code below, with a dreamt-up f, my timeit run shows about a 54x improvement in speed:

def f(O1,O2):
    if O1%4==3 or O2%4==3: return 2
    return cmp(O1,O2)

def GetMinOs(Os):
    Mn=set([])
    NotMn=set([])
    for O1 in Os:
       for O2 in Os:
           rel=f(O1,O2)
           if rel==1:       NotMn|=set([O1])
           elif rel==-1:    NotMn|=set([O2])
    Mn=Os-NotMn
    return Mn

def GetMinOs2(Os):
    Ol=list(Os)
    l=len(Ol)
    i=0
    j=1
    while i<l:
        while j<l:
            rel=f(Ol[i],Ol[j])
            if rel==1:
                l-=1
                Ol[i]=Ol[l]
                j=i+1
                break
            elif rel==-1:
                l-=1
                Ol[j]=Ol[l]
            else:
                j+=1
        else:
            i+=1
            j=i+1
    return set(Ol[:l])


Os=set(range(1000))

if __name__=='__main__':
    answer=GetMinOs(Os)
    result=GetMinOs2(Os)
    assert answer==result

The timeit results are:

% python -mtimeit -s'import test' 'test.GetMinOs2(test.Os)'
1000 loops, best of 3: 22.7 msec per loop
% python -mtimeit -s'import test' 'test.GetMinOs(test.Os)'
10 loops, best of 3: 1.23 sec per loop

PS. Please be warned: I haven't thoroughly checked the algorithm in GetMinOs2, but I think the general idea is right. I've put a little test at the end of the script which shows it works at least on the sample data set(range(1000)).

like image 75
unutbu Avatar answered Nov 09 '22 08:11

unutbu