Спаривание строк файла почти случайным образом

У меня есть файл, скажем, file1.rule, который имеет четное количество строк, последний столбец этого файла представляет fitness, а второй последний столбец представляет class. Я хочу соединить строки по классам (сначала выбирается строка с наибольшей пригодностью, а затем случайная из оставшихся) только с одним условием, что никакие две идентичные строки не могут образовывать пару. В моем файле никакие две абсолютно идентичные строки для класса не могут встречаться более n/2 раз, где n — количество строк для этого конкретного класса.

Ниже мой файл:

*,*,*,1,0,1.0
*,*,1,*,0,0.22
*,*,2,2,1,0.71
*,*,2,2,1,0.71
*,2,2,*,1,0.64
*,2,2,*,1,0.64
1,*,*,3,2,0.95
*,*,3,2,2,0.66
*,*,3,4,2,0.67
3,*,*,*,2,0.33
3,*,*,*,2,0.33
3,*,*,*,2,0.33

И код для этого:

rule_file_name = "file1.rule"
from collections import defaultdict
list1 = []

with open(rule_file_name) as rule_fp:
    for line in rule_fp.readlines():
        list1.append(line.replace("\n","").split(","))

assert len(list1) & 1 == 0
classes = defaultdict(list)
for _list in list1:
    classes[_list[4]].append(_list)

    
from random import sample, seed
seed(1)
for key, _list in classes.items():
    assert len(_list) & 1 == 0
    _list.sort(key=lambda x: x[5])
    pairs = []
    #while(len(_list)>2):
    while _list:
        #print(len(_list))
        first = _list[-1]
        candidate = sample(_list, 1)[0]
        if first != candidate:
            #print(f'first{first}, candidate{candidate}')
            print(f'{first},{candidate}')
            pairs.append((first, candidate))
            _list.remove(first)
            _list.remove(candidate)
    classes[key] = pairs

Приведенный выше код отлично работает для классов 0 и 1, и сопряжение выполняется, но для класса 2 первые 2 случайно выбранные пары:

['1', '*', '*', '3', '2', '0.95'],['*', '*', '3', '2', '2', '0.66']
['*', '*', '3', '4', '2', '0.67'],['3', '*', '*', '*', '2', '0.33']

Теперь после них оставшиеся 2 строки класса 2: 3,*,*,*,2,0.33 and 3,*,*,*,2,0.33 идентичны, поэтому они не могут образовать пару, и, следовательно, цикл while выполняется бесконечное количество раз.

По моему наблюдению, это условие наступит только тогда, когда для любого класса останутся только последние 2 строки, в этом случае я просто хочу отбросить эти 2 строки. Поэтому я попытался заменить запись условия while: while(len(_list)>2): , но в этом случае последние 2 всегда будут игнорироваться, даже если они полностью отличаются друг от друга. Что делать?

Могу ли я использовать любой таймер внутри цикла while, как показано ниже?

if some_condition or time.time() > timeout:
        break

Я также пытался изменить код следующим образом:

while _list:
    first = _list[-1]
    _list.remove(first)
    candidate = sample(_list, 1)[0]
    if (len(_list)<=2) and first == candidate:
        break
    elif first != candidate:
        #print(f'first{first}, candidate{candidate}')
        print(f'{first},{candidate}')
        pairs.append((first, candidate))
        #_list.remove(first)
        _list.remove(candidate)
classes[key] = pairs

Но в этом, когда мой файл выглядит следующим образом:

*,2,2,*,1,0.64
*,*,2,2,1,0.71
*,*,2,2,1,0.71
*,2,2,*,1,0.64

Он выбирает ['*', '*', '2', '2', '1', '0.71'],['*', '2', '2', '*', '1', '0.64'], затем я получаю сообщение об ошибке candidate = sample(_list, 1)[0] в этой строке: ValueError: Sample larger than population or is negative. Пожалуйста, помогите мне.

«побитовое и» с 1 возвращает 0 для четных значений. Это верно для вашей логики?

Eugene 10.04.2022 05:04

@Eugene Да, это правильно, потому что у меня всегда будет четное количество строк, и каждый класс также будет иметь четное количество строк.

Dev 10.04.2022 05:12

Отвечает ли это на ваш вопрос? Быстрая случайно-парная перестановка

Sneftel 10.04.2022 05:12

Ошибка для второй версии, потому что вы хотите получить значение из пустого списка. В прошлой итерации в вашем списке был один элемент, но при проверке длины списка оставался образец письма

Eugene 10.04.2022 05:13

@Sneftel Нет, на самом деле мой вопрос отличается от этого.

Dev 10.04.2022 05:15

@Eugene, почему пустой список? Можете ли вы немного уточнить, пожалуйста?

Dev 10.04.2022 05:16

Ваш код удаляет последний элемент в списке в _list.remove(first) и вызывает образец для пустого списка. Потому что в списке нечетные значения и на каждой итерации удаляйте пару элементов.

Eugene 10.04.2022 05:17

Не могли бы вы дать мне одно решение? Я новичок в программировании и Python, поэтому сталкиваюсь с трудностями.

Dev 10.04.2022 05:19

Проверьте условие с длиной перед удалением элемента из списка.

Eugene 10.04.2022 05:20
if (len(_list)<=2) and first == candidate: break _list.remove(first) elif first != candidate: Вы говорите что-то подобное? Но я думаю, что это вызовет ошибку.
Dev 10.04.2022 05:26
Анализ настроения постов в Twitter с помощью Python, Tweepy и Flair
Анализ настроения постов в Twitter с помощью Python, Tweepy и Flair
Анализ настроения текстовых сообщений может быть настолько сложным или простым, насколько вы его сделаете. Как и в любом ML-проекте, вы можете выбрать...
7 лайфхаков для начинающих Python-программистов
7 лайфхаков для начинающих Python-программистов
В этой статье мы расскажем о хитростях и советах по Python, которые должны быть известны разработчику Python.
Установка Apache Cassandra на Mac OS
Установка Apache Cassandra на Mac OS
Это краткое руководство по установке Apache Cassandra.
Сертификатная программа "Кванты Python": Бэктестер ансамблевых методов на основе ООП
Сертификатная программа "Кванты Python": Бэктестер ансамблевых методов на основе ООП
В одном из недавних постов я рассказал о том, как я использую навыки количественных исследований, которые я совершенствую в рамках программы TPQ...
Создание персонального файлового хранилища
Создание персонального файлового хранилища
Вы когда-нибудь хотели поделиться с кем-то файлом, но он содержал конфиденциальную информацию? Многие думают, что электронная почта безопасна, но это...
Создание приборной панели для анализа данных на GCP - часть I
Создание приборной панели для анализа данных на GCP - часть I
Недавно я столкнулся с интересной бизнес-задачей - визуализацией сбоев в цепочке поставок лекарств, которую могут просматривать врачи и...
0
10
34
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это может быть одно из направлений:

# ...
# construct the defaultdict classes as before.

from collections import Counter
from random import seed, choice


def attempt_partition_into_pairs(rows):
    rows.sort(key=lambda x: x[5])
    # convert to tuples so that the rows are hashable.
    rows = [tuple(row) for row in rows]
    # convert to a Counter so that we organize duplicates
    counter = Counter(rows)

    pairs = []

    # ensure there are always at least two distinct rows
    while len(counter) >= 2:
        # choose and remove first
        first = rows.pop()
        counter[first] -= 1
        if counter[first] == 0:
            del counter[first]

        # choose candidate
        while True:
            candidate = choice(rows)
            if candidate != first:
                break
        counter[candidate] -= 1
        if counter[candidate] == 0:
            del counter[candidate]
        rows.remove(candidate)

        # output
        assert candidate != first
        print(f'{first},{candidate}')
        pairs.append((first, candidate))

    return pairs


seed(1)
for key, _list in classes.items():
    assert len(_list) & 1 == 0
    classes[key] = attempt_partition_into_pairs(_list)

Это печатает:

('*', '*', '*', '1', '0', '1.0'),('*', '*', '1', '*', '0', '0.22')
('*', '*', '2', '2', '1', '0.71'),('*', '2', '2', '*', '1', '0.64')
('*', '*', '2', '2', '1', '0.71'),('*', '2', '2', '*', '1', '0.64')
('1', '*', '*', '3', '2', '0.95'),('3', '*', '*', '*', '2', '0.33')
('*', '*', '3', '4', '2', '0.67'),('3', '*', '*', '*', '2', '0.33')
('*', '*', '3', '2', '2', '0.66'),('3', '*', '*', '*', '2', '0.33')

Я взял на себя смелость исключить эту функцию. Использование collections.Counter помогает отслеживать дубликаты. Также вы можете random.choice(), а не random.sample(..., 1).

Технически это сохраняет некоторое поведение O(n^2) при вызове rows.remove(candidate), поэтому может быть немного медленным, если ваш входной файл огромен, но, конечно, не медленнее, чем ваша первоначальная попытка. Вы, вероятно, можете исправить это, если потребуется больше ума.

Это выглядит хорошо и получило концепцию. Спасибо.

Dev 10.04.2022 11:41

Другие вопросы по теме