Союз каждый набор, который имеет хотя бы одно пересечение с другими наборами из списка [duplicate]

Лучшая практика в том, что должна возвращать функция, сильно варьируется от языка к языку и даже между различными проектами Python.

Для Python в целом я согласен с предпосылкой, что возвращение None является плохим, если ваша функция обычно возвращает итерабельность, потому что повторение без тестирования становится невозможным. Просто верните пустой итерабельный в этом случае, он все равно будет проверять False, если вы используете стандартное тестирование истинности Python:

  ret_val = x (), если ret_val: do_stuff (ret_val)  

и по-прежнему разрешить вам перебирать его без тестирования:

  для дочернего элемента в x (): do_other_stuff (child)  

Для функций которые, вероятно, вернут одно значение, я думаю, что возвращение None является совершенно приемлемым, просто документируйте, что это может произойти в вашей docstring.

34
задан Developer 22 February 2012 в 07:48
поделиться

15 ответов

Моя попытка:

def merge(lsts):
  sets = [set(lst) for lst in lsts if lst]
  merged = 1
  while merged:
    merged = 0
    results = []
    while sets:
      common, rest = sets[0], sets[1:]
      sets = []
      for x in rest:
        if x.isdisjoint(common):
          sets.append(x)
        else:
          merged = 1
          common |= x
      results.append(common)
    sets = results
  return sets

lst = [[65, 17, 5, 30, 79, 56, 48, 62],
       [6, 97, 32, 93, 55, 14, 70, 32],
       [75, 37, 83, 34, 9, 19, 14, 64],
       [43, 71],
       [],
       [89, 49, 1, 30, 28, 3, 63],
       [35, 21, 68, 94, 57, 94, 9, 3],
       [16],
       [29, 9, 97, 43],
       [17, 63, 24]]
print merge(lst)

Benchmark:

import random

# adapt parameters to your own usage scenario   
class_count = 50
class_size = 1000
list_count_per_class = 100
large_list_sizes = list(range(100, 1000))
small_list_sizes = list(range(0, 100))
large_list_probability = 0.5

if False:  # change to true to generate the test data file (takes a while)
  with open("/tmp/test.txt", "w") as f:
    lists = []
    classes = [range(class_size*i, class_size*(i+1)) for i in range(class_count)]
    for c in classes:
      # distribute each class across ~300 lists
      for i in xrange(list_count_per_class):
        lst = []
        if random.random() < large_list_probability:
          size = random.choice(large_list_sizes)
        else:
          size = random.choice(small_list_sizes)
        nums = set(c)
        for j in xrange(size):
          x = random.choice(list(nums))
          lst.append(x)
          nums.remove(x)
        random.shuffle(lst)
        lists.append(lst)
    random.shuffle(lists)
    for lst in lists:
      f.write(" ".join(str(x) for x in lst) + "\n")

setup = """
# Niklas'
def merge_niklas(lsts):
  sets = [set(lst) for lst in lsts if lst]
  merged = 1
  while merged:
    merged = 0
    results = []
    while sets:
      common, rest = sets[0], sets[1:]
      sets = []
      for x in rest:
        if x.isdisjoint(common):
          sets.append(x)
        else:
          merged = 1
          common |= x
      results.append(common)
    sets = results
  return sets

# Rik's
def merge_rik(data):
  sets = (set(e) for e in data if e)
  results = [next(sets)]
  for e_set in sets:
    to_update = []
    for i,res in enumerate(results):
      if not e_set.isdisjoint(res):
        to_update.insert(0,i)

    if not to_update:
      results.append(e_set)
    else:
      last = results[to_update.pop(-1)]
      for i in to_update:
        last |= results[i]
        del results[i]
      last |= e_set
  return results

# katrielalex's
def pairs(lst):
  i = iter(lst)
  first = prev = item = i.next()
  for item in i:
    yield prev, item
    prev = item
  yield item, first

import networkx
def merge_katrielalex(lsts):
  g = networkx.Graph()
  for lst in lsts:
    for edge in pairs(lst):
      g.add_edge(*edge)
  return networkx.connected_components(g)

# agf's (optimized)
from collections import deque
def merge_agf_optimized(lists):
  sets = deque(set(lst) for lst in lists if lst)
  results = []
  disjoint = 0
  current = sets.pop()
  while True:
    merged = False
    newsets = deque()
    for _ in xrange(disjoint, len(sets)):
      this = sets.pop()
      if not current.isdisjoint(this):
        current.update(this)
        merged = True
        disjoint = 0
      else:
        newsets.append(this)
        disjoint += 1
    if sets:
      newsets.extendleft(sets)
    if not merged:
      results.append(current)
      try:
        current = newsets.pop()
      except IndexError:
        break
      disjoint = 0
    sets = newsets
  return results

# agf's (simple)
def merge_agf_simple(lists):
  newsets, sets = [set(lst) for lst in lists if lst], []
  while len(sets) != len(newsets):
    sets, newsets = newsets, []
    for aset in sets:
      for eachset in newsets:
        if not aset.isdisjoint(eachset):
          eachset.update(aset)
          break
      else:
        newsets.append(aset)
  return newsets

# alexis'
def merge_alexis(data):
  bins = range(len(data))  # Initialize each bin[n] == n
  nums = dict()

  data = [set(m) for m in data ]  # Convert to sets
  for r, row in enumerate(data):
    for num in row:
      if num not in nums:
        # New number: tag it with a pointer to this row's bin
        nums[num] = r
        continue
      else:
        dest = locatebin(bins, nums[num])
        if dest == r:
          continue # already in the same bin

        if dest > r:
          dest, r = r, dest   # always merge into the smallest bin

        data[dest].update(data[r])
        data[r] = None
        # Update our indices to reflect the move
        bins[r] = dest
        r = dest

  # Filter out the empty bins
  have = [ m for m in data if m ]
  return have


def locatebin(bins, n):
  while bins[n] != n:
    n = bins[n]
  return n

lsts = []
size = 0
num = 0
max = 0
for line in open("/tmp/test.txt", "r"):
  lst = [int(x) for x in line.split()]
  size += len(lst)
  if len(lst) > max: max = len(lst)
  num += 1
  lsts.append(lst)
"""

setup += """
print "%i lists, {class_count} equally distributed classes, average size %i, max size %i" % (num, size/num, max)
""".format(class_count=class_count)

import timeit
print "niklas"
print timeit.timeit("merge_niklas(lsts)", setup=setup, number=3)
print "rik"
print timeit.timeit("merge_rik(lsts)", setup=setup, number=3)
print "katrielalex"
print timeit.timeit("merge_katrielalex(lsts)", setup=setup, number=3)
print "agf (1)"
print timeit.timeit("merge_agf_optimized(lsts)", setup=setup, number=3)
print "agf (2)"
print timeit.timeit("merge_agf_simple(lsts)", setup=setup, number=3)
print "alexis"
print timeit.timeit("merge_alexis(lsts)", setup=setup, number=3)

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

Ниже приведены некоторые примеры выходов на моем компьютере для разных параметров. Они показывают, что все алгоритмы имеют свои сильные и слабые стороны, в зависимости от типа ввода, который они получают:

=====================
# many disjoint classes, large lists
class_count = 50
class_size = 1000
list_count_per_class = 100
large_list_sizes = list(range(100, 1000))
small_list_sizes = list(range(0, 100))
large_list_probability = 0.5
=====================

niklas
5000 lists, 50 equally distributed classes, average size 298, max size 999
4.80084705353
rik
5000 lists, 50 equally distributed classes, average size 298, max size 999
9.49251699448
katrielalex
5000 lists, 50 equally distributed classes, average size 298, max size 999
21.5317108631
agf (1)
5000 lists, 50 equally distributed classes, average size 298, max size 999
8.61671280861
agf (2)
5000 lists, 50 equally distributed classes, average size 298, max size 999
5.18117713928
=> alexis
=> 5000 lists, 50 equally distributed classes, average size 298, max size 999
=> 3.73504281044

===================
# less number of classes, large lists
class_count = 15
class_size = 1000
list_count_per_class = 300
large_list_sizes = list(range(100, 1000))
small_list_sizes = list(range(0, 100))
large_list_probability = 0.5
===================

niklas
4500 lists, 15 equally distributed classes, average size 296, max size 999
1.79993700981
rik
4500 lists, 15 equally distributed classes, average size 296, max size 999
2.58237695694
katrielalex
4500 lists, 15 equally distributed classes, average size 296, max size 999
19.5465381145
agf (1)
4500 lists, 15 equally distributed classes, average size 296, max size 999
2.75445604324
=> agf (2)
=> 4500 lists, 15 equally distributed classes, average size 296, max size 999
=> 1.77850699425
alexis
4500 lists, 15 equally distributed classes, average size 296, max size 999
3.23530197144

===================
# less number of classes, smaller lists
class_count = 15
class_size = 1000
list_count_per_class = 300
large_list_sizes = list(range(100, 1000))
small_list_sizes = list(range(0, 100))
large_list_probability = 0.1
===================

niklas
4500 lists, 15 equally distributed classes, average size 95, max size 997
0.773697137833
rik
4500 lists, 15 equally distributed classes, average size 95, max size 997
1.0523750782
katrielalex
4500 lists, 15 equally distributed classes, average size 95, max size 997
6.04466891289
agf (1)
4500 lists, 15 equally distributed classes, average size 95, max size 997
1.20285701752
=> agf (2)
=> 4500 lists, 15 equally distributed classes, average size 95, max size 997
=> 0.714507102966
alexis
4500 lists, 15 equally distributed classes, average size 95, max size 997
1.1286110878
20
ответ дан 12 revs 16 August 2018 в 00:43
поделиться
  • 1
    Вы можете использовать not x.isdisjoint(common) вместо x & common, чтобы избежать полного пересечения. – Janne Karila 2 February 2012 в 14:57
  • 2
    Janne Karila: Спасибо за информацию! Я изменил это. – Niklas B. 2 February 2012 в 14:59
  • 3
    lst = [[65, 17, 5, 30, 79, 56, 48, 62], [6, 97, 32, 93, 55, 14, 70, 32], [75, 37, 83, 34, 9, 19, 14, 64], [43, 71], [], [89, 49, 1, 30, 28, 3, 63], [35, 21, 68, 94, 57, 94, 9, 3], [16], [29, 9, 97, 43], [17, 63, 24]] неверно результат [set([1, 3, 5, **9**, 17, 21, 24, 28, 29, 30, 35, 43, 48, 49, 56, 57, 62, 63, 65, 68, 79, 89, 94, 97]), set([6, **9**, 14, 19, 32, 34, 37, 55, 64, 70, 75, 83, 93, 97]), set([43, 71]), set(), set([16])]. – Developer 2 February 2012 в 17:45
  • 4
    @Developer: Да, вы правы. Мое мышление было ошибочным, дизъюнктность - это не отношение эквивалентности! – Niklas B. 2 February 2012 в 17:59
  • 5
    Во всяком случае, это такая забавная проблема :-) Спасибо за предоставление тестовой настройки. – alexis 26 February 2012 в 16:30

Это медленнее, чем решение Niklas (я получил 3,9 с на test.txt вместо 0.5s для своего решения), но дает тот же результат и может быть проще реализовать, например. Fortran, так как он не использует множества, только сортировка общего количества элементов, а затем один проход через все из них.

Он возвращает список с идентификаторами объединенных списков, поэтому также сохраняется отслеживание пустых списков, они остаются невредимыми.

def merge(lsts):
        # this is an index list that stores the joined id for each list
        joined = range(len(lsts))
        # create an ordered list with indices
        indexed_list = sorted((el,index) for index,lst in enumerate(lsts) for el in lst)
        # loop throught the ordered list, and if two elements are the same and
        # the lists are not yet joined, alter the list with joined id
        el_0,idx_0 = None,None
        for el,idx in indexed_list:
                if el == el_0 and joined[idx] != joined[idx_0]:
                        old = joined[idx]
                        rep = joined[idx_0]
                        joined = [rep if id == old else id for id in joined]
                el_0, idx_0 = el, idx
        return joined
3
ответ дан agf 16 August 2018 в 00:43
поделиться
  • 1
    Спасибо, что поделились своей идеей. Хорошим моментом является то, что вы используете маркировку (то есть идентификатор), которая может использоваться для дальнейших разработок. – Developer 7 February 2012 в 11:07
  • 2
    еще раз спасибо за указание на мою ошибку, я думаю, что мой код в порядке. – ChessMaster 23 February 2012 в 05:42
  • 3
    Приятно видеть другой метод. Благодарю. Это элегантно. – Developer 23 February 2012 в 13:27
  • 4
    Что случилось с pop и insert? Это очевидный способ обработки списка как стека, а del просто удаляет элементы из списка. Кроме того, следующее быстрое решение, как мне кажется, принадлежит мне, поэтому вы должны потратить время на это. С вашей тестовой скоростью я получил, что моя все еще быстрее, с Niklas тест иногда ваш быстрее, иногда это наоборот, но разница, кажется, очень мало, ничего, что локальная декларация не может тень. Сэр, вам не нужно хвастаться, если у вас возникли вопросы о моем коде, вы могли бы просто спросить там, а не издеваться над этим здесь. – Rik Poggi 25 February 2012 в 20:35
  • 5
    @RikPoggi pop из конца list или deque является постоянным временем, а insert / del из другого места в списке - O (n), потому что все элементы после этой точки в списке имеют для перемещения. – agf 26 February 2012 в 02:32
  • 6
    Вы действительно посмотрели мой код? Или вы просто бросаете случайный Big-O? Я не догадался, я приурочил и профилировал свой код, и также кажется, что я проделал хорошую работу, так как по вашей собственной тестовой шахте быстрее, чем ваша. Есть один insert(0,i), который будет стоить точно так же, как append(i) (и я использую вставку, чтобы избежать изменения списка позже). del на самом деле не очень дорого, и в любом случае так работает мой код: он выполняет минимальную проверку disjoint по цене сохранения таблицы результатов. – Rik Poggi 26 February 2012 в 03:08

Вот мой ответ. Я не проверял его против сегодняшней партии ответов.

Алгоритмы на основе пересечений - это O (N ^ 2), поскольку они проверяют каждый новый набор на все существующие, поэтому я использовал подход, который индексирует каждое число и работает на близком к O (N) (если мы принимаем, что поиск по словарю - O (1)). Затем я запустил тесты и почувствовал себя полным идиотом, потому что он работал медленнее, но при ближайшем рассмотрении оказалось, что тестовые данные заканчиваются только несколькими различными наборами результатов, поэтому квадратичные алгоритмы не имеют большой работы для делать. Протестируйте его более чем на 10-15 различных ящиков, и мой алгоритм выполняется намного быстрее. Попробуйте тестовые данные с более чем 50 отдельными ящиками, и это невероятно быстрее.

(Edit: Также была проблема с тем, как выполняется тест, но я был не прав в моем диагнозе. Я изменил свой код, чтобы работать с тем, как выполняются повторные тесты.)

def mergelists5(data):
    """Check each number in our arrays only once, merging when we find
    a number we have seen before.
    """

    bins = range(len(data))  # Initialize each bin[n] == n
    nums = dict()

    data = [set(m) for m in data ]  # Convert to sets    
    for r, row in enumerate(data):
        for num in row:
            if num not in nums:
                # New number: tag it with a pointer to this row's bin
                nums[num] = r
                continue
            else:
                dest = locatebin(bins, nums[num])
                if dest == r:
                    continue # already in the same bin

                if dest > r:
                    dest, r = r, dest   # always merge into the smallest bin

                data[dest].update(data[r]) 
                data[r] = None
                # Update our indices to reflect the move
                bins[r] = dest
                r = dest 

    # Filter out the empty bins
    have = [ m for m in data if m ]
    print len(have), "groups in result"
    return have


def locatebin(bins, n):
    """
    Find the bin where list n has ended up: Follow bin references until
    we find a bin that has not moved.
    """
    while bins[n] != n:
        n = bins[n]
    return n
4
ответ дан alexis 16 August 2018 в 00:43
поделиться
  • 1
    Этот код использует множество, чтобы обойти timeit повторение, но оно работает так же хорошо (немного быстрее, на самом деле), если вы просто добавляете списки друг к другу и отбрасываете дубликаты только тогда, когда have сконструирован. Таким образом, это может иметь преимущество быть более фортран-подобным (я никогда не думал, что я скажу это в положительном смысле! :-) – alexis 26 February 2012 в 16:25
  • 2
    (+1) Интересный анализ, и это также одно из самых быстрых решений. Я тестировал его среди всех остальных в различной ситуации в своём резюме :) – Rik Poggi 27 February 2012 в 16:05
  • 3
    Еще один отличный подход. – Developer 27 February 2012 в 16:36
  • 4
    Благодарю. Это должно быть лучше, если у вас нет толчков коллизий. Я фактически вынул структуру, которая оптимизирует отслеживание слияния, потому что данные, которые я тестировал, имеют только пару тысяч столкновений, и это не выглядело сложной задачей. Наверное, все зависит от того, как ваши данные действительно выглядят. – alexis 28 February 2012 в 02:14

Просто для удовольствия ...

def merge(mylists):
    results, sets = [], [set(lst) for lst in mylists if lst]
    upd, isd, pop = set.update, set.isdisjoint, sets.pop
    while sets:
        if not [upd(sets[0],pop(i)) for i in xrange(len(sets)-1,0,-1) if not isd(sets[0],sets[i])]:
            results.append(pop(0))
    return results

и моя переписка с лучшим ответом

def merge(lsts):
  sets = map(set,lsts)
  results = []
  while sets:
    first, rest = sets[0], sets[1:]
    merged = False
    sets = []
    for s in rest:
      if s and s.isdisjoint(first):
        sets.append(s)
      else:
        first |= s
        merged = True
    if merged: sets.append(first)
    else: results.append(first)
  return results
1
ответ дан ChessMaster 16 August 2018 в 00:43
поделиться
  • 1
    Ваш алгоритм неверен. Вы только обновляете первый набор. Попробуйте merge([[1], [2, 3], [3, 4]]). – agf 22 February 2012 в 22:34
  • 2
    @agf алгоритм в порядке, я думаю, но я попытался уменьшить код с помощью update = sets[0].update и isdisjoint = sets[0].isdisjoint, но в этом случае это не сработает, спасибо. – ChessMaster 23 February 2012 в 05:34
  • 3
    Да, это исправляет. Тот, который вы добавили, ужасно работает, хотя в моих тестах. – agf 24 February 2012 в 10:30

Мое решение хорошо работает в небольших списках и вполне читаемо без зависимостей.

def merge_list(starting_list):
    final_list = []
    for i,v in enumerate(starting_list[:-1]):
        if set(v)&set(starting_list[i+1]):
            starting_list[i+1].extend(list(set(v) - set(starting_list[i+1])))
        else:
            final_list.append(v)
    final_list.append(starting_list[-1])
    return final_list

Бенчмаркинг:

lists = [[1,2,3], [ 3,5,6], [8,9,10], [11,12,13]]

% timeit merge_list (lists)

100000 циклов, лучше всего 3: 4,9 мкс на петлю

0
ответ дан Chrismit 16 August 2018 в 00:43
поделиться

Вот реализация, использующая структуру данных с разделенными наборами (в частности, дизъюнктный лес), благодаря подсказке putstorm в слияющих множествах, которые имеют хотя бы один элемент в общий . Я использую сжатие пути для улучшения скорости (~ 5%); это не совсем необходимо (и это предотвращает find хвостом рекурсивный, что может замедлить работу). Обратите внимание, что я использую dict для представления непересекающегося леса; учитывая, что данные int s, массив также работал бы, хотя это может быть не намного быстрее.

def merge(data):
    parents = {}
    def find(i):
        j = parents.get(i, i)
        if j == i:
            return i
        k = find(j)
        if k != j:
            parents[i] = k
        return k
    for l in filter(None, data):
        parents.update(dict.fromkeys(map(find, l), find(l[0])))
    merged = {}
    for k, v in parents.items():
        merged.setdefault(find(v), []).append(k)
    return merged.values()

Этот подход сопоставим с другими лучшими алгоритмами тестов Rik.

13
ответ дан Community 16 August 2018 в 00:43
поделиться
  • 1
    На самом деле у меня создалось впечатление, что это уже проблема, поэтому я не вижу большого смысла в возрождении этой старой темы. Однако, поскольку я также играл с идеей использования библиотеки графов для этого, я интегрировал это решение в свой бенчмарк. К сожалению, похоже, что он слишком плохо конкурирует, похоже, что хакеры Python неплохо справились с реализацией и оптимизацией наборов :) – Niklas B. 20 February 2012 в 02:12
  • 2
    Спасибо за ваш другой подход. Очень полезно принести networkx для этой цели. Это очень хороший пакет. – Developer 21 February 2012 в 13:11
  • 3
    @NiklasB. cheers =) когда я смотрел на вопрос, казалось, что никто на самом деле не написал рабочего решения, но, я думаю, я читал его неправильно. – Katriel 21 February 2012 в 13:25
  • 4
    Мне нравится networkx. Кстати, это могло быть проще, если для каждого списка вы просто добавили ребро между первым элементом в списке и всеми остальными элементами в списке. Это могло быть около 4 строк кода. – robert king 21 February 2012 в 14:38
  • 5
    @robertking не стесняйтесь редактировать =) ... Я просто написал, что пришло в голову! – Katriel 21 February 2012 в 16:19
  • 6
    как насчет моей переписки Никласа Б. ответ. просто задаваясь вопросом, актуальны ли соответствующие тайминги. – ChessMaster 26 February 2012 в 22:59
  • 7
    @ChessMaster: Иногда это немного лучше, другие немного хуже, поэтому я не ставлю его среди результатов. Если вам интересно, вы можете попробовать это самостоятельно, по ссылке есть git repository со всеми функциями слияния в файле с именем core.py. Каждая функция с именем, заканчивающимся на _merge, будет автоматически загружена. Я просто подтолкнул тебя, так что ты найдешь его уже в режиме пропуска. :) – Rik Poggi 27 February 2012 в 00:38
  • 8
    Спасибо за ваши большие усилия. – Developer 27 February 2012 в 16:29
  • 9
    Иногда я действительно удивляюсь, как много качественных усилий и знаний помещается в ответах на этом сайте. Действительно хорошая работа, скомпилировавшая эту компиляцию! – Niklas B. 27 February 2012 в 22:33
  • 10
    FWIW Кажется, я считаю, что это занимает примерно 3-4 раза дольше. Угадайте (как было отмечено ранее), это сильно зависит от того, на каких установках вы его тестируете. – DSM 22 August 2012 в 03:02
  • 11
    Где ответ никлас-б? Ни один из 14 ответов на этой странице не является niklas-b ... – tommy.carstensen 28 April 2015 в 18:36

Это можно решить в O (n), используя алгоритм объединения-поиска. При первых двух строках ваших данных края, используемые в объединении-нахождении, следующие пары: (0,1), (1,3), (1,0), (0,3), (3,4 ), (4,5), (5,10)

0
ответ дан ekardes 16 August 2018 в 00:43
поделиться

Использование матричных манипуляций

Позвольте мне предисловие к этому ответу со следующим комментарием:

ЭТО НЕПРАВИЛЬНЫЙ СПОСОБ ДЕЛАТЬ ЭТО. ЭТО ПРЕДОТВРАТЬ НА ЧИСЛЕННУЮ НЕУСТОЙЧИВОСТЬ И БОЛЬШЕ СКОРОСТЬ, ЧТО ДРУГИЕ МЕТОДЫ ПРЕДСТАВЛЕНЫ, ИСПОЛЬЗУЮТСЯ НА ВАШЕМ СОБСТВЕННОМ РИСКЕ.

Сказанное, я не мог устоять перед решением проблемы с динамической точки зрения (и я надеюсь, вы получите свежий взгляд на проблему). В теории это должно работать все время, но вычисления собственных значений могут часто терпеть неудачу. Идея состоит в том, чтобы рассматривать ваш список как поток из строк в столбцы. Если две строки имеют общую ценность, между ними существует соединительный поток. Если бы мы думали об этих потоках как о воде, мы бы увидели, что потоки кластеризуются в маленькие пулы, когда между ними существует соединительный путь. Для простоты я собираюсь использовать меньший набор, хотя он также работает с вашим набором данных:

from numpy import where, newaxis
from scipy import linalg, array, zeros

X = [[0,1,3],[2],[3,1]]

Нам нужно преобразовать данные в потоковый граф. Если строка i втекает в значение j , поместим ее в матрицу. Здесь у нас есть 3 строки и 4 уникальных значения:

A = zeros((4,len(X)), dtype=float)
for i,row in enumerate(X):
    for val in row: A[val,i] = 1

В общем, вам нужно будет изменить 4, чтобы записать количество уникальных значений, которые у вас есть. Если набор представляет собой список целых чисел, начиная с 0, как у нас, вы можете просто сделать это самым большим числом. Теперь мы выполняем разложение по собственным значениям. SVD, если быть точным, поскольку наша матрица не является квадратной.

S  = linalg.svd(A)

Мы хотим сохранить только часть 3х3 этого ответа, поскольку она будет представлять поток пулов. На самом деле нам нужны только абсолютные значения этой матрицы; нам остается только, есть ли поток в этом пространстве cluster .

M  = abs(S[2])

Мы можем представить эту матрицу M как марковскую матрицу и сделать ее явной по нормировке строк. Как только мы это получим, мы вычисляем (слева) разложение собственного значения. этой матрицы.

M /=  M.sum(axis=1)[:,newaxis]
U,V = linalg.eig(M,left=True, right=False)
V = abs(V)

Теперь несвязная (неэргодическая) марковская матрица обладает приятным свойством, что для каждого несвязного кластера существует собственное значение единицы. Собственные векторы, связанные с этими значениями единства, являются теми, которые мы хотим:

idx = where(U > .999)[0]
C = V.T[idx] > 0

Я должен использовать .999 из-за вышеупомянутой числовой неустойчивости. На этом мы закончили! Каждый независимый кластер может теперь вывести соответствующие строки:

for cluster in C:
    print where(A[:,cluster].sum(axis=1))[0]

Что дает, как и предполагалось:

[0 1 3]
[2]

Измените X на lst, и вы get: [ 0 1 3 4 5 10 11 16] [2 8].


Добавление

Почему это может быть полезно? Я не знаю, откуда берутся ваши базовые данные, но что происходит, когда соединения не являются абсолютными? У строки 1 есть запись 3 80% времени - как бы вы обобщили проблему? Метод потока выше работал бы очень хорошо и был бы полностью параметризован этим значением .999, чем дальше от единства, тем слабее ассоциация.


Визуальное представление

Поскольку изображение стоит 1K слов, вот графики матриц A и V для моего примера и ваши lst соответственно. Обратите внимание, как в V разбивается на два кластера (это блок-диагональная матрица с двумя блоками после перестановки), так как для каждого примера было только два уникальных списка!

My Example [/g9] Your sample data [/g10]


Быстрая реализация

Оглядываясь назад, я понял, что вы можете пропустить шаг SVD и вычислить только один decomp:

M = dot(A.T,A)
M /=  M.sum(axis=1)[:,newaxis]
U,V = linalg.eig(M,left=True, right=False)

Преимущество этого метода (помимо скорости) состоит в том, что M теперь является симметричным, поэтому вычисление может быть более быстрым и точным (никаких мнимых значений не беспокоить)

7
ответ дан Hooked 16 August 2018 в 00:43
поделиться
  • 1
    Спасибо за ответ. Это требует более глубокого чтения. Поэтому я заинтересован в тестировании и просмотре того, что происходит. – Developer 3 February 2012 в 04:37
  • 2
    Как вы создали эти действительно красивые изображения? – Zachary Young 3 February 2012 в 04:53
  • 3
    @ZacharyYoung проверить MatPlotLib для построения графиков, см. Gallery. – Developer 3 February 2012 в 05:20
  • 4
    @Developer: Ха, это здорово! Я видел тонны SO-вопросов о MatPlotLib, но никогда не потрудился проверить это. Похоже, я только что нашел свой новый любимый тег SO :) – Zachary Young 3 February 2012 в 07:05
  • 5
    На этапе A = zeros((4,len(X)), dtype=float) нам нужно знать, сколько уникальной ценности существует во всем мегалисте! В приведенном примере это было «4», как о многих нечетких больших списках списков. Как найти уникальные ценности среди них? – Developer 3 February 2012 в 07:49

Это будет мой обновленный подход :

def merge(data):
    sets = (set(e) for e in data if e)
    results = [next(sets)]
    for e_set in sets:
        to_update = []
        for i,res in enumerate(results):
            if not e_set.isdisjoint(res):
                to_update.insert(0,i)

        if not to_update:
            results.append(e_set)
        else:
            last = results[to_update.pop(-1)]
            for i in to_update:
                last |= results[i]
                del results[i]
            last |= e_set

    return results

Примечание: во время слияния пустые списки будут удалены.

Обновление: Надежность.

Вам нужно два теста для 100% надежности успеха:

  • Убедитесь, что все результирующие множества взаимно разделены:
    merged = [{0, 1, 3, 4, 5, 10, 11, 16}, {8, 2}, {8}]
    
    from itertools import combinations
    for a,b in combinations(merged,2):
        if not a.isdisjoint(b):
            raise Exception(a,b)    # just an example
    
  • Убедитесь, что объединенный набор охватывает исходные данные. (как было предложено katrielalex)

Я думаю, что это займет некоторое время, но, возможно, это будет стоить того, если вы хотите быть на 100% уверенным.

1
ответ дан Rik Poggi 16 August 2018 в 00:43
поделиться
  • 1
    lst = [[65, 17, 5, 30, 79, 56, 48, 62], [6, 97, 32, 93, 55, 14, 70, 32], [75, 37, 83, 34, 9, 19, 14, 64], [43, 71], [], [89, 49, 1, 30, 28, 3, 63], [35, 21, 68, 94, 57, 94, 9, 3], [16], [29, 9, 97, 43], [17, 63, 24]] неверно результат [set([1, 3, 5, **9**, 17, 21, 24, 28, 29, 30, 35, 43, 48, 49, 56, 57, 62, 63, 65, 68, 79, 89, 94, 97]), set([6, **9**, 14, 19, 32, 34, 37, 55, 64, 70, 75, 83, 93, 97]), set([43, 71]), set(), set([16])]. – Developer 2 February 2012 в 17:47
  • 2
    Если вы попробуете данные списки, вы увидите, что 9 существует в двух наборах выходов. Поэтому код страдает от проблемы, изначально упомянутой в вопросе, а не надежности! – Developer 2 February 2012 в 18:01
  • 3
    @Developer: Понятно, это потому, что есть список, в котором есть два разных номера, каждый из которых совпадает с двумя распущенными наборами. Я посмотрю на это. – Rik Poggi 2 February 2012 в 18:11
  • 4
    @Rik: Я не могу воспроизвести ваши тайминги. Даже с моей фиксированной версией разница составляет всего 10% или около того (я добавил контрольный показатель к моему ответу). Пожалуйста, добавьте тестовый код, иначе это не очень полезно. – Niklas B. 2 February 2012 в 18:15
  • 5
    @NiklasBaumstark: Я исправил свой код и разместил свой код времени, если у него есть проблема, дайте мне знать. Также кажется, что иногда наши решения разные, потому что ваш не добавляет пустой набор, но я не уверен, может быть, это моя ошибка. – Rik Poggi 2 February 2012 в 18:46

Во-первых, я не совсем уверен, справедливы ли тесты:

Добавление следующего кода в начало моей функции:

c = Counter(chain(*lists))
    print c[1]
"88"

Это означает, что из всего значения во всех списках, есть только 88 различных значений. Обычно в реальном мире дубликаты встречаются редко, и вы ожидаете гораздо более отличных значений. (конечно, я не знаю, где ваши данные из этого не могут делать предположения).

Поскольку дубликаты чаще встречаются, это означает, что сеты менее вероятно, будут непересекающимися. Это означает, что метод set.isdisjoint () будет намного быстрее, потому что только после нескольких тестов он обнаружит, что наборы не пересекаются.

Сказав все это, я верю, что представленные методы, которые используют disjoint являются самыми быстрыми в любом случае, но я просто говорю, вместо того, чтобы быть в 20 раз быстрее, возможно, они должны быть только в 10 раз быстрее, чем другие методы с различным тестовым тестированием.

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

Я думал, что я закажу все

import heapq
from itertools import chain
def merge6(lists):
    for l in lists:
        l.sort()
    one_list = heapq.merge(*[zip(l,[i]*len(l)) for i,l in enumerate(lists)]) #iterating through one_list takes 25 seconds!!
    previous = one_list.next()

    d = {i:i for i in range(len(lists))}
    for current in one_list:
        if current[0]==previous[0]:
            d[current[1]] = d[previous[1]]
        previous=current

    groups=[[] for i in range(len(lists))]
    for k in d:
        groups[d[k]].append(lists[k]) #add a each list to its group

    return [set(chain(*g)) for g in groups if g] #since each subroup in each g is sorted, it would be faster to merge these subgroups removing duplicates along the way.


lists = [[1,2,3],[3,5,6],[8,9,10],[11,12,13]]
print merge6(lists)
"[set([1, 2, 3, 5, 6]), set([8, 9, 10]), set([11, 12, 13])]""



import timeit
print timeit.timeit("merge1(lsts)", setup=setup, number=10)
print timeit.timeit("merge4(lsts)", setup=setup, number=10)
print timeit.timeit("merge6(lsts)", setup=setup, number=10)
5000 lists, 5 classes, average size 74, max size 1000
1.26732238315
5000 lists, 5 classes, average size 74, max size 1000
1.16062907437
5000 lists, 5 classes, average size 74, max size 1000
30.7257182826
1
ответ дан robert king 16 August 2018 в 00:43
поделиться
  • 1
    Ваша точка верна для используемых данных. Данные могут иметь любую форму отношений, то есть 0 - n, объединенные списки. Статистически, однако, от 10% до 30% представляют интерес. – Developer 24 February 2012 в 04:11
  • 2
    Не работает с этим входом – timdiels 23 December 2015 в 13:15

Вот функция (Python 3.1), чтобы проверить, является ли результат функции слияния ОК. Он проверяет:

  • Являются ли результирующие множества непересекающимися? (количество элементов объединения == сумма чисел элементов)
  • Являются ли элементы результата такими же, как и для входных списков?
  • Является ли каждый список входных данных подмножеством результирующий набор?
  • Является ли каждый результат минимальным, т. е. невозможно разбить его на два меньших множества?
  • Он делает not проверить, есть ли пустые наборы результатов - я не знаю, хотите ли вы их или нет ...

.

from itertools import chain

def check(lsts, result):
    lsts = [set(s) for s in lsts]
    all_items = set(chain(*lsts))
    all_result_items = set(chain(*result))
    num_result_items = sum(len(s) for s in result)
    if num_result_items != len(all_result_items):
        print("Error: result sets overlap!")
        print(num_result_items, len(all_result_items))
        print(sorted(map(len, result)), sorted(map(len, lsts)))
    if all_items != all_result_items:
        print("Error: result doesn't match input lists!")
    if not all(any(set(s).issubset(t) for t in result) for s in lst):
        print("Error: not all input lists are contained in a result set!")

    seen = set()
    todo = list(filter(bool, lsts))
    done = False
    while not done:
        deletes = []
        for i, s in enumerate(todo): # intersection with seen, or with unseen result set, is OK
            if not s.isdisjoint(seen) or any(t.isdisjoint(seen) for t in result if not s.isdisjoint(t)):
                seen.update(s)
                deletes.append(i)
        for i in reversed(deletes):
            del todo[i]
        done = not deletes
    if todo:
        print("Error: A result set should be split into two or more parts!")
        print(todo)
1
ответ дан WolframH 16 August 2018 в 00:43
поделиться
  • 1
    Это было бы аккуратно, если бы вы могли написать это на модульном тестовом языке =) – Katriel 20 February 2012 в 18:34
1
ответ дан robert king 29 October 2018 в 06:59
поделиться
3
ответ дан agf 29 October 2018 в 06:59
поделиться
13
ответ дан Community 29 October 2018 в 06:59
поделиться
13
ответ дан Community 29 October 2018 в 06:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: