简体   繁体   中英

Combine lists that have at least a number in common in a list of lists, Python

Consider this list of lists:

l = [ [1], [1], [1,2,3], [4,1], [5], [5], [6], [7,8,9], [7,6], [8,5] ]

I want to combine all the lists that have at least one number in common, that will be done iteratively until it finished and no dubletters will go with. The result will be:

combine(l) = [ [1,2,3,4], [5,6,7,8,9] ]

is there any neat way to do this, perhaps with itertools?

out = []
for l in lists:
    for o in out:
        if set(l).intersection(set(o)):
            o[:] = list(set(l) + set(o))  # Mutate, don't reassign temp var
            break
    else:
        out.append(l)

Not perfectly written, could be optimized and is not sorted, but should give you the idea of how to do it.

Maybe this?

l = [ [1], [1], [1,2,3], [4,1], [5], [5], [6], [7,8,9], [7,6], [8,5] ]
a, b = [], map(set, l)

while len(a) != len(b):
    a, b = b, []
    for x in a:
        for i, p in enumerate(b):
            if p & x:
                b[i] = p | x
                break
        else:
            b.append(x)        

print a
# [set([1, 2, 3, 4]), set([5, 6, 7, 8, 9])]

My naive attempt:

  1. merge all tuples when their intersection is not empty
  2. sort each tuple
  3. remove duplicate tuples
  4. repeat this until there are no more changes.

Example:

def combine_helper(l):
    l = map(set, l)
    for i, x in enumerate(l, 1):
        x = set(x)
        for y in l[i:]:
            if x & y:
                x = x | y
        yield tuple(sorted(x))

def combine(l):
    last_l = []
    new_l = l
    while last_l != new_l:
        last_l = new_l
        new_l = list(set(combine_helper(last_l)))
    return map(list, last_l)

l = [ [1], [1], [1,2,3], [4,1], [5], [5], [6], [7,8,9], [7,6], [8,5] ]
print combine(l)

Output:

$ python test.py
[[1, 2, 3, 4], [5, 6, 7, 8, 9]]

Recursion to the rescue! And don't forget reduce!

input_list = [ [1], [1], [1, 2, 3], [4, 1], [5], [5], [6], [7, 8, 9], 
               [7, 6], [8, 5] ]
def combine(input_list):
    input_list = map(set, input_list) # working with sets has some advantages
    reduced_list = reduce(combine_reduce, input_list, [])
    if len(reduced_list) == len(input_list):
        # return the whole thing in the original format (sorted lists)
        return map(sorted, map(list, reduced_list))
    else:
        # recursion happens here
        return combine(reduced_list)

def combine_reduce(reduced_list, numbers):
    '''
    find the set to add the numbers to or append as a new set.
    '''
    for sub_set in reduced_list:
        if sub_set.intersection(numbers):
            sub_set.update(numbers)
            return reduced_list
    reduced_list.append(numbers)
    return reduced_list

print combine(input_list)

Prints out:

$ python combine.py
[[1, 2, 3, 4], [5, 6, 7, 8, 9]]

We have two things going on here. The first is reduce : I'm using it to cook the list down, by fitting each element into the resulting list somewhere or appending it, if that didn't work. This does not do the whole job, though, so we repeat this process (recursion!) until reducing does not provide a shorter list.

Also, use of set allows for the handy intersection method. You will notice the line with map(set, input_list) is redundant in recursion. Extracting a wrapper function combine from the inner function combine_inner and placing the formatting / unformatting (from list to set and back) in the outer function is left as an exercise.

Its possible to introduce the following attempt:

  1. Make a list of 1-element sets with individual values present in the list. This is the output list.
  2. The l list is a recipe how items in the output list should be joined. eg If the output list is [{1}, {2}, {3}] and the first element in the l list is [1,3] , then all sets containing 1 and 3 in the output list should be joined: output = [{1,3}, {2}] .
  3. Repeat step 2. for every item on the l list.

Code:

l = [ [1], [1], [1,2,3], [4,1], [5], [5], [6], [7,8,9], [7,6], [8,5] ]

def compose(l):
    # the following will convert l into a list of 1-element sets:
    # e.g. [ {1}, {2}, ... ]
    r = sum(l, [])
    r = map(lambda x: set([x]), set(r))

    # for every item in l
    # find matching sets in r and join them together
    for item in map(set, l):
        outside = [x for x in r if not x & item]  # elements untouched
        inside = [x for x in r if x & item]       # elements to join
        inside = set([]).union(*inside)           # compose sets 
        r = outside + [inside]
    return r

Example:

>>> compose(l)
[set([1, 2, 3, 4]), set([8, 9, 5, 6, 7])]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM