```
from collections import defaultdict
import heapq
import itertools

# Define a structure for counts
class CountItemset:
    def __init__(self, itemset, count):
        self.itemset = tuple(sorted(itemset))
        self.count = count

    def __lt__(self, other):
        return self.count < other.count

# Hybrid Storage for Tid-Sets
class HybridTidSet:
    def __init__(self, ni):
        self.ni = ni
        self.tid_list = None
        self.dif_list = None
        self.bit_vector = None
        self.mode = None  # 'tid_list', 'dif_list', 'bit_vector'

    def set_tid_set(self, tids):
        # Decide storage mode
        sz_tidlist = 4 * len(tids)
        sz_diflist = 4 * (self.ni - len(tids))
        sz_bv = (self.ni + 7) // 8  # Number of bytes

        min_size = min(sz_tidlist, sz_diflist, sz_bv)
        if min_size == sz_bv:
            self.mode = 'bit_vector'
            self.bit_vector = bytearray(sz_bv)
            for tid in tids:
                byte_index = tid // 8
                bit_index = tid % 8
                self.bit_vector[byte_index] |= 1 << bit_index
        elif min_size == sz_tidlist:
            self.mode = 'tid_list'
            self.tid_list = sorted(tids)
        else:
            self.mode = 'dif_list'
            all_tids = set(range(self.ni))
            self.dif_list = sorted(all_tids - set(tids))

    def intersect(self, other):
        # Intersection based on storage modes
        if self.mode == 'tid_list' and other.mode == 'tid_list':
            return sorted(set(self.tid_list) & set(other.tid_list))
        elif self.mode == 'dif_list' and other.mode == 'dif_list':
            return sorted(set(self.dif_list) | set(other.dif_list))
        elif self.mode == 'bit_vector' and other.mode == 'bit_vector':
            return [i for i in range(self.ni) if (self.bit_vector[i//8] & (1 << (i % 8))) and (other.bit_vector[i//8] & (1 << (i % 8)))]
        elif (self.mode == 'tid_list' and other.mode == 'dif_list') or (self.mode == 'dif_list' and other.mode == 'tid_list'):
            if self.mode == 'dif_list':
                return [tid for tid in other.tid_list if tid not in self.dif_list]
            else:
                return [tid for tid in self.tid_list if tid not in other.dif_list]
        else:
            # Convert to set for simplicity (not optimized)
            s1 = set()
            if self.mode == 'tid_list':
                s1 = set(self.tid_list)
            elif self.mode == 'dif_list':
                s1 = set(range(self.ni)) - set(self.dif_list)
            elif self.mode == 'bit_vector':
                s1 = {i for i in range(self.ni) if self.bit_vector[i//8] & (1 << (i %8))}
            
            s2 = set()
            if other.mode == 'tid_list':
                s2 = set(other.tid_list)
            elif other.mode == 'dif_list':
                s2 = set(range(other.ni)) - set(other.dif_list)
            elif other.mode == 'bit_vector':
                s2 = {i for i in range(other.ni) if other.bit_vector[i//8] & (1 << (i %8))}
            
            return sorted(s1 & s2)

    def count_ones(self):
        if self.mode == 'tid_list':
            return len(self.tid_list)
        elif self.mode == 'dif_list':
            return self.ni - len(self.dif_list)
        elif self.mode == 'bit_vector':
            count = 0
            for byte in self.bit_vector:
                count += bin(byte).count('1')
            return count
        

class PTF:
    def __init__(self, transactions, k):
        self.transactions = transactions  # List of sets
        self.k = k
        self.item_order = self.order_items()
        self.partitions = self.prefix_partition()
        self.coN = self.compute_coN()
        self.min_heap = []
        self.rmsup = 0

    def order_items(self):
        # Total order: descending order of frequency
        freq = defaultdict(int)
        for transaction in self.transactions:
            for item in transaction:
                freq[item] += 1
        ordered = sorted(freq.keys(), key=lambda x: (-freq[x], x))
        return ordered

    def prefix_partition(self):
        # Create prefix-based partitions
        partitions = defaultdict(list)
        for tid, transaction in enumerate(self.transactions):
            sorted_items = sorted(transaction, key=lambda x: self.item_order.index(x))
            if sorted_items:
                prefix = sorted_items[0]
                suffix = sorted_items[1:]
                partitions[prefix].append((tid, set(suffix)))
        return partitions

    def compute_coN(self):
        # Compute co-occurrence numbers
        coN = defaultdict(lambda: defaultdict(int))
        for prefix, trans in self.partitions.items():
            for tid, suffix in trans:
                coN[prefix][prefix] +=1
                for item in suffix:
                    coN[prefix][item] +=1
        # Merge coN into a single structure sorted by count
        merged_coN = {}
        for prefix, items in coN.items():
            for item, count in items.items():
                if len(merged_coN) < self.k:
                    heapq.heappush(self.min_heap, CountItemset([item] if item == prefix else [prefix, item], count))
                    if len(self.min_heap) > self.k:
                        heapq.heappop(self.min_heap)
                else:
                    if count > self.min_heap[0].count:
                        heapq.heappop(self.min_heap)
                        heapq.heappush(self.min_heap, CountItemset([item] if item == prefix else [prefix, item], count))
        # Set initial rmsup
        if len(self.min_heap) >= self.k:
            self.rmsup = self.min_heap[0].count
        else:
            self.rmsup = 0
        return merged_coN

    def mine(self):
        # Process each partition
        for prefix in self.item_order:
            if prefix not in self.partitions:
                continue
            # Check if partition can be skipped
            if self.coN[prefix][prefix] <= self.rmsup:
                continue
            # Get promising items (support > rmsup)
            promising = [item for item, count in self.coN[prefix].items() if count > self.rmsup]
            if len(promising) <=2:
                continue
            # Vertical representation
            ni = len(self.partitions[prefix])
            vertical = {}
            for item in promising:
                if item == prefix:
                    continue
                tids = [tid for tid, suffix in self.partitions[prefix] if item in suffix]
                ht = HybridTidSet(ni)
                ht.set_tid_set(tids)
                vertical[item] = ht
            # Generate candidates
            for pair in itertools.combinations(promising, 2):
                if pair[1] not in vertical:
                    continue
                intersect_tids = vertical[pair[0]].intersect(vertical[pair[1]])
                count = len(intersect_tids)
                if count > self.rmsup:
                    heapq.heappush(self.min_heap, CountItemset(pair, count))
                    if len(self.min_heap) > self.k:
                        heapq.heappop(self.min_heap)
                        self.rmsup = self.min_heap[0].count
            # Note: Further extensions and pruning steps can be implemented similarly

        # Extract top-k
        top_k = sorted(self.min_heap, key=lambda x: (-x.count, x.itemset))
        return [(x.itemset, x.count) for x in top_k]
    
# Sample Transactions based on the running example
transactions = [
    {'x1', 'x2', 'x9', 'x10'},                # T1
    {'x1', 'x4', 'x6'},                       # T2
    {'x1', 'x2', 'x9', 'x10', 'x3'},          # T3
    {'x2', 'x4', 'x7', 'x10'},                # T4
    {'x2', 'x9', 'x10', 'x5'},                # T5
    {'x1', 'x2', 'x9', 'x10', 'x4', 'x6'},    # T6
    {'x2', 'x4', 'x9', 'x10'},                # T7
    {'x1', 'x2', 'x9', 'x10'},                # T8
    {'x2', 'x9', 'x10', 'x6'},                # T9
    {'x1', 'x2', 'x4', 'x9', 'x10'},          # T10
    {'x2', 'x4', 'x9', 'x10', 'x5'},          # T11
    {'x2', 'x9', 'x10'},                       # T12
]

k = 8  # Top-k frequent itemsets to find

ptf = PTF(transactions, k)
top_k = ptf.mine()

print("Top-k Frequent Itemsets:")
for itemset, count in top_k:
    print(f"{set(itemset)}: {count}")

```
AttributeError: 'PTF' object has no attribute 'min_heap'