Подтвердить что ты не робот

Объединение списков, содержащих общие элементы

Мой ввод - это список списков. Некоторые из них имеют общие элементы, например.

L = [['a','b','c'],['b','d','e'],['k'],['o','p'],['e','f'],['p','a'],['d','g']]

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

Конечный результат должен быть:

L = [['a','b','c','d','e','f','g','o','p'],['k']] 
4b9b3361

Ответ 1

Вы можете видеть свой список как обозначение для графика, т.е. ['a','b','c'] - это граф с тремя узлами, связанными друг с другом. Проблема, которую вы пытаетесь решить, заключается в поиске связанных компонентов в этом графике.

Вы можете использовать NetworkX для этого, что имеет то преимущество, что он почти гарантированно корректен:

l = [['a','b','c'],['b','d','e'],['k'],['o','p'],['e','f'],['p','a'],['d','g']]

import networkx 
from networkx.algorithms.components.connected import connected_components


def to_graph(l):
    G = networkx.Graph()
    for part in l:
        # each sublist is a bunch of nodes
        G.add_nodes_from(part)
        # it also imlies a number of edges:
        G.add_edges_from(to_edges(part))
    return G

def to_edges(l):
    """ 
        treat `l` as a Graph and returns it edges 
        to_edges(['a','b','c','d']) -> [(a,b), (b,c),(c,d)]
    """
    it = iter(l)
    last = next(it)

    for current in it:
        yield last, current
        last = current    

G = to_graph(l)
print connected_components(G)
# prints [['a', 'c', 'b', 'e', 'd', 'g', 'f', 'o', 'p'], ['k']]

Чтобы решить эту проблему самостоятельно, вам нужно преобразовать список во что-то графическое, так что вы можете также использовать networkX с самого начала.

Ответ 2

Алгоритм:

  • возьмите первый набор A из списка
  • для каждого другого набора B в списке, если B имеет общий элемент с A join B в A; удалить B из списка
  • повторить 2. пока не будет больше перекрываться с A
  • положить A в outpup
  • повторите 1. с остальным списком

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

l = [['a', 'b', 'c'], ['b', 'd', 'e'], ['k'], ['o', 'p'], ['e', 'f'], ['p', 'a'], ['d', 'g']]

out = []
while len(l)>0:
    first, *rest = l
    first = set(first)

    lf = -1
    while len(first)>lf:
        lf = len(first)

        rest2 = []
        for r in rest:
            if len(first.intersection(set(r)))>0:
                first |= set(r)
            else:
                rest2.append(r)     
        rest = rest2

    out.append(first)
    l = rest

print(out)

Ответ 3

Я столкнулся с той же проблемой, связанной с попыткой объединить списки с общими значениями. Этот пример может быть тем, что вы ищете. Он только перебирает списки один раз и обновляет набор результатов по мере его появления.

lists = [['a','b','c'],['b','d','e'],['k'],['o','p'],['e','f'],['p','a'],['d','g']]
lists = sorted([sorted(x) for x in lists]) #Sorts lists in place so you dont miss things. Trust me, needs to be done.

resultslist = [] #Create the empty result list.

if len(lists) >= 1: # If your list is empty then you dont need to do anything.
    resultlist = [lists[0]] #Add the first item to your resultset
    if len(lists) > 1: #If there is only one list in your list then you dont need to do anything.
        for l in lists[1:]: #Loop through lists starting at list 1
            listset = set(l) #Turn you list into a set
            merged = False #Trigger
            for index in range(len(resultlist)): #Use indexes of the list for speed.
                rset = set(resultlist[index]) #Get list from you resultset as a set
                if len(listset & rset) != 0: #If listset and rset have a common value then the len will be greater than 1
                    resultlist[index] = list(listset | rset) #Update the resultlist with the updated union of listset and rset
                    merged = True #Turn trigger to True
                    break #Because you found a match there is no need to continue the for loop.
            if not merged: #If there was no match then add the list to the resultset, so it doesnt get left out.
                resultlist.append(l)
print resultlist

#

resultset = [['a', 'b', 'c', 'd', 'e', 'g', 'f', 'o', 'p'], ['k']]

Ответ 4

Я думаю, что это можно решить, моделируя проблему как graph. Каждый подсписок является node и разделяет ребро с другим node, только если два подписок имеют некоторый общий элемент. Таким образом, объединенный подсписок в основном представляет собой подключенный компонент на графике. Слияние всех из них - это просто поиск всех подключенных компонентов и их перечисление.

Это можно сделать простым прохождением по графику. Оба BFS и DFS могут быть использованы, но я используя DFS здесь, поскольку для меня он несколько короче.

l = [['a','b','c'],['b','d','e'],['k'],['o','p'],['e','f'],['p','a'],['d','g']]
taken=[False]*len(l)
l=map(set,l)

def dfs(node,index):
    taken[index]=True
    ret=node
    for i,item in enumerate(l):
        if not taken[i] and not ret.isdisjoint(item):
            ret.update(dfs(item,i))
    return ret

def merge_all():
    ret=[]
    for i,node in enumerate(l):
        if not taken[i]:
            ret.append(list(dfs(node,i)))
    return ret

print merge_all()

Ответ 5

Моя попытка. Функциональный взгляд на него.

#!/usr/bin/python
from collections import defaultdict
l = [['a','b','c'],['b','d','e'],['k'],['o','p'],['e','f'],['p','a'],['d','g']]
hashdict = defaultdict(int)

def hashit(x, y):
    for i in y: x[i] += 1
    return x

def merge(x, y):
    sums = sum([hashdict[i] for i in y])
    if sums > len(y):
        x[0] = x[0].union(y)
    else:
        x[1] = x[1].union(y)
    return x


hashdict = reduce(hashit, l, hashdict)
sets = reduce(merge, l, [set(),set()])
print [list(sets[0]), list(sets[1])]

Ответ 6

Как Йохен Ритцел указал, вы ищете связанные компоненты на графике. Вот как вы могли реализовать его без использования библиотеки графов:

from collections import defaultdict

def connected_components(lists):
    neighbors = defaultdict(set)
    seen = set()
    for each in lists:
        for item in each:
            neighbors[item].update(each)
    def component(node, neighbors=neighbors, seen=seen, see=seen.add):
        nodes = set([node])
        next_node = nodes.pop
        while nodes:
            node = next_node()
            see(node)
            nodes |= neighbors[node] - seen
            yield node
    for node in neighbors:
        if node not in seen:
            yield sorted(component(node))

L = [['a','b','c'],['b','d','e'],['k'],['o','p'],['e','f'],['p','a'],['d','g']]
print list(connected_components(L))

Ответ 7

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

#!/usr/bin/python


def clink(l, acc):
  for sub in l:
    if sub.__class__ == list:
      clink(sub, acc)
    else:
      acc[sub]=1

def clunk(l):
  acc = {}
  clink(l, acc)
  print acc.keys()

l = [['a', 'b', 'c'], ['b', 'd', 'e'], ['k'], ['o', 'p'], ['e', 'f'], ['p', 'a'], ['d', 'g']]

clunk(l)

Результат выглядит так:

['a', 'c', 'b', 'e', 'd', 'g', 'f', 'k', 'o', 'p']

Ответ 8

Это, пожалуй, более простой/быстрый алгоритм и, кажется, хорошо работает -

l = [['a', 'b', 'c'], ['b', 'd', 'e'], ['k'], ['o', 'p'], ['e', 'f'], ['p', 'a'], ['d', 'g']]

len_l = len(l)
i = 0
while i < (len_l - 1):
    for j in range(i + 1, len_l):

        # i,j iterate over all pairs of l elements including new 
        # elements from merged pairs. We use len_l because len(l)
        # may change as we iterate
        i_set = set(l[i])
        j_set = set(l[j])

        if len(i_set.intersection(j_set)) > 0:
            # Remove these two from list
            l.pop(j)
            l.pop(i)

            # Merge them and append to the orig. list
            ij_union = list(i_set.union(j_set))
            l.append(ij_union)

            # len(l) has changed
            len_l -= 1

            # adjust 'i' because elements shifted
            i -= 1

            # abort inner loop, continue with next l[i]
            break

    i += 1

print l
# prints [['k'], ['a', 'c', 'b', 'e', 'd', 'g', 'f', 'o', 'p']]

Ответ 9

Я нашел itertools быстрый вариант для слияния списков, и он решил эту проблему для меня:

import itertools

LL = set(itertools.chain.from_iterable(L)) 
# LL is {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'k', 'o', 'p'}

for each in LL:
  components = [x for x in L if each in x]
  for i in components:
    L.remove(i)
  L += [list(set(itertools.chain.from_iterable(components)))]

# then L = [['k'], ['a', 'c', 'b', 'e', 'd', 'g', 'f', 'o', 'p']]

Для больших наборов, сортирующих LL по частоте от наиболее распространенных элементов до минимума, можно немного ускорить процесс