2025, Oct 18 23:33
Python पेयरिंग में भूमिका-ओवरलैप बग: सही स्लाइसिंग फिक्स
Python पेयरिंग में ऑफ-बाय-वन स्लाइस बग समान-भूमिका जोड़ों को पार करा देता है. set(person[1:]) से फिक्स कर CSV इतिहास संग यूनिक, ओवरलैप-रहित जोड़े बनाएं
लोगों को यादृच्छिक रूप से जोड़ना तुच्छ लगता है, जब तक कि एक ही बाधा परिणाम को पलट न दे। इस मामले में लक्ष्य है ऐसे जोड़े बनाना जो पहले कभी न बने हों और जिनकी भूमिकाएँ एक जैसी न हों। कार्यान्वयन लगभग सही पहुँचता है, लेकिन एक सूक्ष्म स्लाइस त्रुटि समान-भूमिका वाले मेल को अंदर आने देती है।
समस्या को दोहराना
नीचे दिया गया इम्प्लीमेंटेशन CSV से पहले देखे गए जोड़े पढ़ता है, रोस्टर को शफ़ल करता है और डुप्लिकेट, बहिष्करण और भूमिका-ओवरलैप को हटाते हुए नए जोड़े बनाता है। पेच यह है कि प्रत्येक ट्यूपल से भूमिकाएँ कैसे निकाली जा रही हैं।
import random
import pandas as p
# सभी नामों और उनकी भूमिकाओं की सूची
roster = [
    ("Samantha Reyes", "Innovation", "Product Owner"),
    ("Ethan McAllister", "Data Scientist"),
    ("Priya Deshmukh", "Data Architect", "SMT"),
    ("Marcus Liu", "Stream 3"),
    ("Elena Petrova", "SMT", "Stream 3"),
]
# पहले से बने जोड़ों को CSV फ़ाइल से लोड करें
def read_seen_duos(csv_path):
    df = p.read_csv(csv_path)
    duo_cache = set()
    for _, row in df.iterrows():
        duo = (row['name1'], row['name2'])
        duo_rev = (row['name2'], row['name1'])
        duo_cache.add(duo)
        duo_cache.add(duo_rev)
    return duo_cache
# नए जोड़ों को CSV फ़ाइल में जोड़ें
def append_duos_to_csv(csv_path, duos):
    df = p.DataFrame(duos, columns=['name1', 'name2'])
    df.to_csv(csv_path, index=False, mode='a', header=False)
# पहले से बने जोड़ों की फ़ाइल का पथ
pairs_csv_path = 'prev_pairs2.csv'
# देखे गए जोड़ों के कैश को आरंभ करें
prior_duos = read_seen_duos(pairs_csv_path)
# पेयरिंग लॉजिक के लिए बहिष्करण
banned_duos = []
def build_fresh_duos_with_trace(roster, prior_duos, banned_duos):
    banned_set = set(banned_duos) | set((a[1], a[0]) for a in banned_duos)
    max_tries = 10000
    attempts = 0
    while attempts < max_tries:
        random.shuffle(roster)
        duos = []
        engaged = set()
        leftovers = []
        for i in range(0, len(roster) - 1, 2):
            member1, member2 = roster[i], roster[i + 1]
            tags1 = set(member1[2:]) if len(member1) > 2 else set()
            tags2 = set(member2[2:]) if len(member2) > 2 else set()
            duo_key = (member1[0], member2[0])
            duo_key_rev = (member2[0], member1[0])
            if duo_key in banned_set or duo_key_rev in banned_set:
                print(f"Skipping pair due to exclusion: {member1[0]} - {member2[0]}")
                continue
            if duo_key in prior_duos or duo_key_rev in prior_duos:
                print(f"Skipping pair due to seen pair: {member1[0]} - {member2[0]}")
                continue
            if tags1 & tags2:
                print(f"Skipping pair due to role overlap: {member1[0]} ({tags1}) - {member2[0]} ({tags2})")
                continue
            if member1[0] in engaged or member2[0] in engaged:
                print(f"Skipping pair due to duplicate usage: {member1[0]} - {member2[0]}")
                continue
            duos.append((member1, member2))
            engaged.update([member1[0], member2[0]])
        leftovers = [person for person in roster if person[0] not in engaged]
        if not leftovers:
            duo_keys = set((d[0][0], d[1][0]) for d in duos)
            if not any((k in prior_duos or (k[1], k[0]) in prior_duos) for k in duo_keys):
                prior_duos.update(duo_keys)
                return duos
        attempts += 1
    print("Unable to generate unique pairs with the given restrictions.")
    print(f"Skipped individuals: {[person[0] for person in leftovers]}")
    raise ValueError("Unable to generate unique pairs with the given restrictions.")
# जोड़े बनाएं
result_duos = build_fresh_duos_with_trace(roster, prior_duos, banned_duos)
# जोड़ों को प्रिंट करें
for d in result_duos:
    print(f"{d[0][0]} - {d[1][0]}")
print("-----")
print(f"Total pairs: {len(result_duos)}")
# स्थायी रूप से सहेजें
append_duos_to_csv(pairs_csv_path, [(d[0][0], d[1][0]) for d in result_duos])
असल में गड़बड़ी कहाँ है
रॉस्टर में ट्यूपल की संरचना ऐसी है: इंडेक्स 0 पर नाम होता है, और इंडेक्स 1 से आगे भूमिकाएँ। Python में 0-आधारित इंडेक्सिंग है, इसलिए इंडेक्स 2 से स्लाइस लेने का मतलब है “तीसरे तत्व से शुरू करो।” यानी इंडेक्स 1 पर मौजूद पहली भूमिका चुपचाप छूट जाती है। जब ओवरलैप जाँच इन्हीं भूमिका-सेट्स पर निर्भर करती है, तो केवल पहली भूमिका साझा करने वाले दो लोग फ़िल्टर से निकल जाते हैं और साथ जोड़ दिए जाते हैं — बिल्कुल वही जो आपने देखा।
समाधान
भूमिकाओं का सेट इंडेक्स 1 से आगे बनाइए ताकि सभी भूमिकाएँ शामिल हों। लंबाई की जाँच की ज़रूरत नहीं, क्योंकि एक-तत्वीय ट्यूपल पर 1 से स्लाइस लेने पर खाली ट्यूपल मिलता है, जो खाली सेट बन जाता है।
def build_fresh_duos_with_trace(roster, prior_duos, banned_duos):
    banned_set = set(banned_duos) | set((a[1], a[0]) for a in banned_duos)
    max_tries = 10000
    attempts = 0
    while attempts < max_tries:
        random.shuffle(roster)
        duos = []
        engaged = set()
        leftovers = []
        for i in range(0, len(roster) - 1, 2):
            member1, member2 = roster[i], roster[i + 1]
            # सुधार: इंडेक्स 1 से शुरू होने वाली सभी भूमिकाएँ शामिल करें
            tags1 = set(member1[1:])
            tags2 = set(member2[1:])
            duo_key = (member1[0], member2[0])
            duo_key_rev = (member2[0], member1[0])
            if duo_key in banned_set or duo_key_rev in banned_set:
                print(f"Skipping pair due to exclusion: {member1[0]} - {member2[0]}")
                continue
            if duo_key in prior_duos or duo_key_rev in prior_duos:
                print(f"Skipping pair due to seen pair: {member1[0]} - {member2[0]}")
                continue
            if tags1 & tags2:
                print(f"Skipping pair due to role overlap: {member1[0]} ({tags1}) - {member2[0]} ({tags2})")
                continue
            if member1[0] in engaged or member2[0] in engaged:
                print(f"Skipping pair due to duplicate usage: {member1[0]} - {member2[0]}")
                continue
            duos.append((member1, member2))
            engaged.update([member1[0], member2[0]])
        leftovers = [person for person in roster if person[0] not in engaged]
        if not leftovers:
            duo_keys = set((d[0][0], d[1][0]) for d in duos)
            if not any((k in prior_duos or (k[1], k[0]) in prior_duos) for k in duo_keys):
                prior_duos.update(duo_keys)
                return duos
        attempts += 1
    print("Unable to generate unique pairs with the given restrictions.")
    print(f"Skipped individuals: {[person[0] for person in leftovers]}")
    raise ValueError("Unable to generate unique pairs with the given restrictions.")
यह बारीकी क्यों मायने रखती है
डेटा की संरचना से जुड़ी मान्यताएँ नियंत्रण-तर्क में जल्दी रिस जाती हैं। यहाँ विशिष्टता और ओवरलैप से जुड़े नियम ट्यूपल की संरचना को सही ढंग से समझने पर टिके हैं। ऑफ-बाय-वन स्लाइस समीक्षा में आसानी से छूट सकती है, लेकिन वही पेयरिंग के नियम को कमज़ोर कर देती है और इतिहास में गलत जोड़े जोड़ देती है। स्लाइस सुधारते ही इच्छित अर्थ वापस आ जाता है, बिना पेयरिंग रणनीति या I/O व्यवहार बदले।
मुख्य बातें
जब भूमिकाएँ दूसरे तत्व से शुरू होती हैं, तो फ़िल्टरिंग में इस्तेमाल होने वाले सभी गुणों को समेटने के लिए हमेशा इंडेक्स 1 से स्लाइस लें। इस संदर्भ में set(person[1:]) पर्याप्त है और बदलती ट्यूपल-लंबाई के प्रति भी सुदृढ़। ऐसा करते ही ओवरलैप जाँच अपेक्षित रूप से चलती है और जेनरेटर केवल नए, अलग-भूमिका वाले जोड़े बनाता है।