2010-10-01 1 views
7

У меня есть три набора:Python задать вопрос пересечения

s0 = [set([16,9,2,10]), set([16,14,22,15]), set([14,7])] # true, 16 and 14 
s1 = [set([16,9,2,10]), set([16,14,22,15]), set([7,8])] # false 

хочу функцию, которая возвращает истину, если каждый набор в списке пересекается с, по меньшей мере, одного другого набора в списке. Есть ли встроенное средство для этого или простого понимания списка?

ответ

2

Это немного подробный, но я думаю, что это довольно эффективное решение. Он использует тот факт, что при пересечении двух множеств мы можем отметить их как связанные. Он делает это, сохраняя список флагов, пока список наборов. когда установлено i и установлено j пересекаются, он устанавливает флаг для обоих из них. Затем он перебирает список наборов и только пытается найти пересечение для множеств, которые еще не были пересечены. После прочтения комментариев, я думаю, это то, о чем говорил @Victor.

s0 = [set([16,9,2,10]), set([16,14,22,15]), set([14,7])] # true, 16 and 14 
s1 = [set([16,9,2,10]), set([16,14,22,15]), set([7,8])] # false 


def connected(sets): 
    L = len(sets) 

    if not L: return True 
    if L == 1: return False 

    passed = [False] * L 
    i = 0 
    while True: 
     while passed[i]: 
      i += 1 
      if i == L: 
       return True 

     for j, s in enumerate(sets): 
      if j == i: continue 
      if sets[i] & s: 
       passed[i] = passed[j] = True 
       break 
     else: 
      return False 


print connected(s0) 
print connected(s1) 

Я решил, что пустой список наборов подключен (Если вы производите элемент списка, я могу производить элемент, который он пересекает;). Список только с одним элементом несвязно тривиально. Это одна строка, которая изменится в любом случае, если вы не согласны.

+0

много хороших ответов, я использую 2.5.2 прямо сейчас, так что я закончил тем, что пошел с этим решением. –

13
all(any(a & b for a in s if a is not b) for b in s) 
+2

Элегантный, но я бы предпочел 2 прямые петли и счетчик, чтобы избежать сравнения двух предметов в 2 раза, это ускорит работу, по крайней мере, в два раза –

+0

@jchl: This действительно классно и добавляет к моим знаниям python – pyfunc

+0

Вызов 'bool()' избыточен. – kennytm

0

Эта стратегия, вероятно, не будет столь же эффективным, как @ предложение Виктора, но может быть более эффективным, чем jchl's answer в связи с увеличением использования множества арифметических (union).

s0 = [set([16,9,2,10]), set([16,14,22,15]), set([14,7])] 
s1 = [set([16,9,2,10]), set([16,14,22,15]), set([7,8])] 

def freeze(list_of_sets): 
    """Transform a list of sets into a frozenset of frozensets.""" 
    return frozenset(frozenset(set_) for set_ in list_of_sets) 

def all_sets_have_relatives(set_of_sets): 
    """Check if all sets have another set that they intersect with. 

    >>> all_sets_have_relatives(s0) # true, 16 and 14 
    True 
    >>> all_sets_have_relatives(s1) # false 
    False 
    """ 
    set_of_sets = freeze(set_of_sets) 
    def has_relative(set_): 
     return set_ & frozenset.union(*(set_of_sets - set((set_,)))) 
    return all(has_relative(set) for set in set_of_sets) 
+1

Почему все фризонсеты? – jchl

+0

@jchl: наборы не хешируются, поэтому они не могут быть элементами в наборе. Я не уверен, есть ли смысл сделать набор верхнего уровня замороженным, но казалось, что он может быть быстрее обычного набора, и он соответствует логике. – intuited

+0

@jchl: Ах, я понимаю, о чем вы сейчас говорите. Похоже, у меня возникла проблема психического расстройства желудка при рефакторинге, а затем публикация этого кода. «Однако учение прошло!» Во всяком случае, теперь это исправлено. – intuited

0

Это может обеспечить лучшую производительность в зависимости от распределения комплектов.

def all_intersect(s): 
    count = 0 
    for x, a in enumerate(s): 
     for y, b in enumerate(s): 
     if a & b and x!=y: 
      count += 1 
      break 
    return count == len(s) 
+2

1. Разве вы не имеете в виду 'break' вместо' continue'? 2. Вы уверены в 'count = -1' ?? 3. Потеряйте вторую строку и замените последние 3 строки на 'return count == len (s)'. 4. Не лучше ли 'x! = Y и a & b'? 5. Вы не выходите раньше, если у набора нет друзей - что является основой для «может дать лучшую производительность»? Лучше чем? –

+1

Здравствуйте, привет, ... ваш код BROKEN. 'count' становится числом непустых парных множеств пересечений, меньше 1. Это не то, что хочет OP. С помощью ACCIDENT он дает правильные ответы на два тестовых примера OP. Попробуйте это: s2 = [set ([16, 9, 2, 10]), установите ([16, 22, 14, 15]), установите ([14, 2])] ... он должен вернуть True, но ваш счет равен 6-1 == 5, и ваш код возвращает False. –

+0

@John, +1, спасибо за указание всех ошибок. Я отредактировал, чтобы внести все необходимые исправления. 4. Я предпочел 'a & b' над' x! = Y', потому что последнее более верно. Это плохо? 5. Да, это правильно. Это может дать лучшую производительность по сравнению с теми, которые выполняют проверку пересечения со всеми наборами. – dheerosaur

2

Вот более эффективным (если гораздо более сложным) решение, которое выполняет линейное число пересечений и ряд объединений порядка О (п * журнала (п)), где п длина s :

def f(s): 
    import math 
    j = int(math.log(len(s) - 1, 2)) + 1 
    unions = [set()] * (j + 1) 
    for i, a in enumerate(s): 
     unions[:j] = [set.union(set(), *s[i+2**k:i+2**(k+1)]) for k in range(j)] 
     if not (a & set.union(*unions)): 
      return False 
     j = int(math.log(i^(i + 1), 2)) 
     unions[j] = set.union(a, *unions[:j]) 
    return True 

Обратите внимание, что это решение работает только на Python> = 2.6.

+0

Это значительно медленнее, чем ваш предыдущий ответ. в то время как ваш предыдущий ответ составляет в среднем 4,7 мс, (при большом случайном вводе) это занимает 250. Союзы - это то, что убивает вас (с этой реализацией) и интуитивно. Теоретически привлекательно, но дорого. – aaronasterling

+0

Интересно. Я думаю, что это большой постоянный фактор из-за всего кода Python (по сравнению с предыдущим решением, которое можно оценить практически на C). Кроме того, мое первое решение будет хорошо работать, если вызов 'any()' может часто встречаться, тогда как это решение хорошо работает во всех случаях. Это решение работает лучше (9 против 24 секунд), чем первое на этом входе: '[set ((i,)) для i в диапазоне (N)] + [set (range (N))]' с 'N = 10000 ', и я подозреваю, что лучше справился бы с еще большим« N ». – jchl

+0

Я просто понял, что это решение гораздо сложнее, чем необходимо, и все еще не оптимально.Мое третье решение намного лучше по обеим причинам. – jchl

5

Вот очень простое решение, которое очень эффективно для больших входов:

def g(s): 
    import collections 
    count = collections.defaultdict(int) 
    for a in s: 
     for x in a: 
      count[x] += 1 
    return all(any(count[x] > 1 for x in a) for a in s) 
+0

вы можете просто использовать 'count.setdefault (x, 0) + = 1' вместо оператора' if/else'. – aaronasterling

+1

Нет, вы не можете. 'count.setdefault (x, 0)' не является значением lvalue. – jchl

+1

+1 потому что он обходит «очевидный» набор предметов пересечения и упрощает проблему. К сожалению, я не думаю, что вы получите достаточное количество очков. BTW, либо 'collections.Counter', либо' count = collections.defaultdict (int) 'и' count [x] + = 1' – tzot

1

Как обычно, я хотел бы дать неизбежное itertools решение ;-)

from itertools import combinations, groupby 
from operator import itemgetter 


def any_intersects(sets): 
    # we are doing stuff with combinations of sets 
    combined = combinations(sets,2) 
    # group these combinations by their first set 
    grouped = (g for k,g in groupby(combined, key=itemgetter(0))) 
    # are any intersections in each group 
    intersected = (any((a&b) for a,b in group) for group in grouped) 
    return all(intersected) 


s0 = [set([16,9,2,10]), set([16,14,22,15]), set([14,7])] 
s1 = [set([16,9,2,10]), set([16,14,22,15]), set([7,8])] 
print any_intersects(s0) # True 
print any_intersects(s1) # False 

Это действительно ленивы и будут выполнять только пересечения, которые требуются. Это также может быть очень запутанным и нечитаемым oneliner ;-)

+0

комбинированный? Как насчет «комбинированных»? ;-) – martineau

+0

Хех, некоторые онлайн-словари список «combinated», но «комбинированный» звучит намного лучше ;-) –

+0

Дальнейшее размышление, «комбо», вероятно, было бы более описательным - и, следовательно, даже лучше - переменным именем. – martineau

1

Чтобы ответить на ваш вопрос, нет, нет встроенного или простого понимания списка, которое делает то, что вы хотите. Вот еще одно решение на основе itertools, которое очень эффективно - на удивление примерно в два раза быстрее, чем @TAC4k's itertools, используя groupby() в тестах времени, используя ваш образец ввода. Вероятно, он может быть оптимизирован немного дальше, но очень читаем, как представлено. Подобно @AaronMcSmooth, я произвольно решил, что возвращать, когда в списке ввода нет или только один набор.

from itertools import combinations 

def all_intersect(sets): 
    N = len(sets) 
    if not N: return True 
    if N == 1: return False 

    intersected = [False] * N 
    for i,j in combinations(xrange(N), 2): 
     if not intersected[i] or not intersected[j]: 
      if sets[i] & sets[j]: 
       intersected[i] = intersected[j] = True 
    return all(intersected)