import random
import numpy as np
from pprint import pprint
from collections import defaultdict

# Генератор вопросов
def generate_questions(num_questions):
    questions = []
    for q_id in range(num_questions):
        q_type = random.choice(['single', 'multi'])
        
        # Для вариантов ответов
        if q_type in ['single', 'multi']:
            num_options = random.randint(3, 5)
            options = [f"Option_{chr(65+i)}" for i in range(num_options)]
            if q_type == 'single':
                correct = [random.choice(options)]
            else:
                correct = random.sample(options, k=random.randint(1, 2))
        else:
            options = None
            correct = round(random.uniform(1, 100), 1)  # Число с одним decimal
            
        questions.append({
            'id': q_id,
            'type': q_type,
            'text': f"Question {q_id+1}",
            'options': options,
            'correct': correct
        })
    return questions

# Генератор ответов пользователей
def generate_answers(num_users, questions, base_prob=0.3):
    user_answers = []
    
    for u_id in range(num_users):
        answers = []
        total_score = 0
        
        for q in questions:
            # Вероятность правильного ответа с нормальным распределением
            user_prob = np.clip(np.random.normal(base_prob, 0.1), 0.1, 0.9)
            
            if q['type'] == 'single':
                if random.random() < user_prob:
                    ans = q['correct'][0]
                else:
                    ans = random.choice([o for o in q['options'] if o not in q['correct']])
                score = 1 if ans in q['correct'] else 0
                
            elif q['type'] == 'multi':
                correct = set(q['correct'])
                ans = set()
                for o in q['options']:
                    if o in correct:
                        if random.random() < user_prob:
                            ans.add(o)
                    else:
                        if random.random() < 0.3:  # Вероятность выбрать неверный
                            ans.add(o)
                score = len(ans & correct) / len(correct)
                
            else:  # numeric
                if random.random() < user_prob:
                    ans = round(q['correct'] + random.uniform(-2, 2), 1)
                else:
                    ans = round(random.uniform(q['correct']-10, q['correct']+10), 1)
                score = 1 - min(abs(ans - q['correct'])/10, 1)
                
            answers.append({'q_id': q['id'], 'answer': ans})
            total_score += score
        
        user_answers.append({
            'user_id': u_id,
            'answers': answers,
            'score': round(total_score, 2)
        })
    
    return user_answers



# Алгоритм определения правильных ответов
def detect_correct_answers(questions, user_answers):
    stats = defaultdict(lambda: defaultdict(lambda: {'total_score': 0, 'count': 0}))
    
    # Сбор статистики
    for user in user_answers:
        score = user['score']
        for ans in user['answers']:
            q = questions[ans['q_id']]
            answer = ans['answer']
            
            if q['type'] == 'single':
                key = answer
            elif q['type'] == 'multi':
                for o in answer:
                    stats[q['id']][o]['total_score'] += score
                    stats[q['id']][o]['count'] += 1
                continue
            else:  # numeric - группируем с округлением
                key = round(answer, 0)
                
            stats[q['id']][key]['total_score'] += score
            stats[q['id']][key]['count'] += 1
    
    # Определение правильных ответов
    predicted = {}
    for q in questions:
        q_stats = stats[q['id']]
        
        if not q_stats:
            predicted[q['id']] = None
            continue
            
        if q['type'] == 'single':
            max_avg = -1
            best_option = None
            for opt, data in q_stats.items():
                if data['count'] < 3: continue  # порог минимальных ответов
                avg = data['total_score'] / data['count']
                if avg > max_avg:
                    max_avg = avg
                    best_option = opt
            predicted[q['id']] = [best_option] if best_option else None
            
        elif q['type'] == 'multi':
            avg_scores = {}
            for opt, data in q_stats.items():
                if data['count'] < 3: continue
                avg_scores[opt] = data['total_score'] / data['count']
            
            if not avg_scores:
                predicted[q['id']] = None
                continue
                
            threshold = np.mean(list(avg_scores.values()))
            predicted[q['id']] = [k for k, v in avg_scores.items() if v > threshold]
            
        else:  # numeric
            best_val = None
            max_avg = -1
            for val, data in q_stats.items():
                if data['count'] < 3: continue
                avg = data['total_score'] / data['count']
                if avg > max_avg:
                    max_avg = avg
                    best_val = val
            predicted[q['id']] = best_val
    
    return predicted

from itertools import product
from collections import defaultdict

def brute_force_analysis(questions, attempts):
    """
    Анализ методом перебора всех возможных комбинаций ответов
    """
    # Генерируем все возможные комбинации правильных ответов
    possible_answers = []
    for q in questions:
        possible_answers.append(q['options'])
    
    all_combinations = list(product(*possible_answers))
    
    valid_combinations = []
    
    # Проверяем каждую комбинацию
    for combination in all_combinations:
        valid = True
        
        for attempt in attempts:
            expected_score = 0
            
            # Считаем ожидаемый балл для студента
            for q_idx, answer in enumerate(attempt['answers']):
                if answer == combination[q_idx]:
                    expected_score += 1
            
            # Проверяем соответствие реальному баллу
            if abs(expected_score - attempt['score']) > 1e-9:
                valid = False
                break
                
        if valid:
            valid_combinations.append(combination)
    
    # Считаем статистику
    result = []
    total = len(valid_combinations)
    counter = defaultdict(int)
    
    for combo in valid_combinations:
        counter[combo] += 1
    
    for combo, count in counter.items():
        probability = count / total * 100 if total > 0 else 0
        result.append({
            'combination': combo,
            'probability': round(probability, 1)
        })
    
    return sorted(result, key=lambda x: -x['probability'])

# Тестирование
for i in range(20, 21, 10):
    # Генерация данных
    questions = generate_questions(10)
    user_answers = generate_answers(i, questions)
    
    # Запуск алгоритма
    # predicted = detect_correct_answers(questions, user_answers)
    predicted = brute_force_analysis(questions, user_answers)

    # pprint(questions)
    # pprint(user_answers[:10])
    # pprint(predicted)

    # Оценка точности
    correct_count = 0
    for q in questions:
        true = q['correct']
        pred = predicted[q['id']]
        
        if q['type'] == 'multi':
            match = set(pred) == set(true) if pred else False
        else:
            match = (pred == true) if pred else False
            
        if match:
            correct_count += 1
            
    print(f"{i} => {correct_count/len(questions)*100:.1f}%")

Embed on website

To embed this project on your website, copy the following code and paste it into your website's HTML: