import lzma,glob,os,random,time,collections,sys

def parse(fn):
    clauses=[]; n=0
    for line in lzma.open(fn,'rt'):
        if line.startswith('p'): n=int(line.split()[2])
        elif line[:1] not in ('c','p') and line.strip():
            lits=[int(x) for x in line.split() if x!='0']
            if lits: clauses.append(lits)
    triples=[tuple(c) for c in clauses if len(c)==3 and all(x>0 for x in c)]
    prim=sorted(set(x for t in triples for x in t))
    idx={v:i for i,v in enumerate(prim)}
    trs=[tuple(idx[x] for x in t) for t in triples]
    return n,prim,trs,clauses

def infer_k(prim,clauses,n):
    # Horn satisfiability with first t false, rest true; find min t satisfiable
    auxvars=[i for i in range(1,n+1) if i not in set(prim)]
    # brute all t
    def sat(t):
        val=[None]*(n+1)
        for i,v in enumerate(prim): val[v]=(i>=t) # True means CNF true, selected false for first t
        changed=True
        # Horn least model: initialize positive units true, then implications
        for c in clauses:
            if len(c)==1 and c[0]>0: val[c[0]]=True
        while changed:
            changed=False
            for c in clauses:
                # clause satisfied? if any lit true
                ok=False; un=[]
                for lit in c:
                    vv=val[abs(lit)]
                    if vv is None: un.append(lit)
                    elif (lit>0)==vv: ok=True; break
                if ok: continue
                # if no un, conflict
                if not un: return False
                # unit propagate if one unassigned
                if len(un)==1:
                    lit=un[0]; need=lit>0; v=abs(lit)
                    if val[v] is None:
                        val[v]=need; changed=True
                    elif val[v]!=need: return False
        # set remaining false and check
        for i in range(1,n+1):
            if val[i] is None: val[i]=False
        return all(any(((lit>0)==val[abs(lit)]) for lit in c) for c in clauses)
    lo=None
    for t in range(len(prim)+1):
        if sat(t): return t
    return None

def greedy(v,trs,seed=1):
    rng=random.Random(seed)
    order=list(range(v)); rng.shuffle(order)
    selected=[]; sel=[False]*v; pair=set()
    inc=[[] for _ in range(v)]
    # map pairs to third via triples
    bad=[[False]*v for _ in range(0)]
    pair_third={}
    for a,b,c in trs:
        pair_third[tuple(sorted((a,b)))] = c
        pair_third[tuple(sorted((a,c)))] = b
        pair_third[tuple(sorted((b,c)))] = a
    for x in order:
        ok=True
        for y in selected:
            z=pair_third.get(tuple(sorted((x,y))))
            if z is not None and sel[z]: ok=False; break
        if ok:
            sel[x]=True; selected.append(x)
    return selected

def greedy_dynamic(v,trs,seed=1):
    rng=random.Random(seed)
    pair_third={}
    for a,b,c in trs:
        pair_third[tuple(sorted((a,b)))] = c
        pair_third[tuple(sorted((a,c)))] = b
        pair_third[tuple(sorted((b,c)))] = a
    sel=[False]*v; selected=[]; cand=set(range(v))
    while cand:
        # sample/min conflicts with selected? all candidates are ok
        x=rng.choice(tuple(cand)) if len(cand)<1000 else rng.choice(list(cand))
        sel[x]=True; selected.append(x); cand.remove(x)
        # remove z that completes pair x,y for each selected y
        for y in selected[:-1]:
            z=pair_third.get(tuple(sorted((x,y))))
            if z in cand: cand.remove(z)
    return selected

for fn in sorted(glob.glob('/data/train/*.cnf.xz')):
    n,prim,trs,clauses=parse(fn); v=len(prim); k=infer_k(prim,clauses,n)
    best=[]; t0=time.time()
    for seed in range(200):
        s=greedy(v,trs,seed)
        if len(s)>len(best): best=s
    print(os.path.basename(fn), 'v',v,'triples',len(trs),'k',k,'best',len(best),'time',round(time.time()-t0,2))
