Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mix two lists python

Tags:

python

list

I am trying to create a function to mix two lists in python, or I rather say put the element of list2 into list1. It is required that in the output list, no more than two elements next two each other have the same value

For example:

list1 = [1,1,1,1,1]
list2 = [2,2,2,2,2]
output = [1,1,2,1,2,2,1,2,1,2]

Wrong output example:

# There are more than two '1' standing next two each other
output = [1,1,1,2,2,1,2,1,2,2]

Here is my solution:

def guyGenerator(toughGuy,softGuy):
    i = 0
    while len(softGuy) > 0:
        temp = softGuy[:1]
        while i < len(toughGuy) - 1:
            if toughGuy[i] == toughGuy[i + 1] == 2:
                toughGuy.insert(random.randint(i, i + 1), temp[0])
            i = i + 1
        softGuy = softGuy[1:]
    return toughGuy

The issue is getting the output with more than two of the same elements standing next two each other or the length of the output list is longer than combine length of two lists

For example one of my output

[2, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 2, 1, 2]

What am I doing wrong here ?

Edit 1

Clodion asked me so I put this up here

The results can be random, as long as they satisfy the requirement in the question. The order of the elements does not matter as long as no more than two elements standing next to each other have the same value

Edit 2

I am trying to mess around by create a iterable class, using Clodion solution. Here is the new code:

import random

class GuyGenerator:
    def __init__(self,toughGuyList,softGuyList):
        self.toughGuyList = toughGuyList
        self.softGuyList = softGuyList
    def __iter__(self):
        return self
    def __next__(self):
        listSum = self.toughGuyList + self.softGuyList
        while True:
            res = random.sample(listSum,len(listSum))
            if not any([res[i-2]==res[i-1]==res[i] for i in range(len(listSum))]):
                break
        return res

toughGuy = ['tough','tough','tough','tough','tough','tough','tough','tough']
softGuy = ['soft','soft','soft','soft','soft','soft','soft','soft']
for guy in GuyGenerator(toughGuy,softGuy):
    print(guy)

The result turned out quite well, except for the code executing unlimited and I have to use KeyboardInterrupt to stop the code. What am I doing wrong in this case ? I would be grateful with a thorough explain since I am a newbie in Python and Iterator

Edit 3

Solved the iterator problem, easier than I think. It turned out that what returned in the iter is what the class return when calling in the for-loop

Solution:

def __iter__(self):    
    listSum = self.listRandom1 + self.listRandom2
    while True:
        self.res = random.sample(listSum,len(listSum))
        if not any([self.res[i-2]==self.res[i-1]==self.res[i] for i in range(len(listSum))]):
            break
    return iter(self.res)
like image 894
Tuan Dinh Avatar asked Jul 29 '15 18:07

Tuan Dinh


2 Answers

You could implement a greedy algorithm which tries to yield the most common item as frequently as possible (i.e. up to twice), and then yield the next most common item when necessary.

This has two advantages over search by random shuffle:

  • The greedy algorithm is much faster as the length of items increases:

    In [223]: %timeit list(intermix([1]*10+[2]*5))
    10000 loops, best of 3: 39.8 µs per loop
    
    In [222]: %timeit intermix_random([1]*10+[2]*5)
    100 loops, best of 3: 6.85 ms per loop
    
  • It can identify when there is no solution, whereas a random shuffle search loops forever if visited shuffles are not cached.


import collections
def intermix(items, nconsecutive=2):
    counter = collections.Counter(items)
    # sort from most common to least common
    items = sorted(items, key=counter.get, reverse=True)
    N = len(items)
    count = 0
    # remember the last two values
    last = []
    for i in range(N):
        val = items[i]
        if len(last) < nconsecutive:
            if last and val == last[-1]:
                last.append(val)
            else:
                last = [val]
            counter[val] -= 1
            yield val
        else:
            # last is full; find a different value
            for j in range(i, N):
                if items[j] != last[-1]:
                    items[i], items[j] = items[j], items[i]
                    val = items[i]
                    last = [val]
                    counter[val] -= 1
                    # as items are yielded, the meaning of "most common" can change.
                    items[i+1:] = sorted(items[i+1:], key=counter.get, reverse=True)
                    yield val
                    break
            else:
                raise ValueError('No solution possible')

In [184]: list(intermix([1,1,1,1,1,2,2,2,2,2]))
Out[184]: [1, 1, 2, 2, 1, 2, 2, 1, 2, 1]

In [185]: list(intermix([1,0,1,1,2,1,0,1,1,1,2]))
Out[185]: [1, 1, 0, 1, 1, 2, 1, 1, 2, 1, 0]

In [186]: list(intermix([1,0,1,1,2,1,0,1,1,1,1,1,1,2]))
Out[186]: [1, 1, 0, 1, 1, 2, 1, 1, 2, 1, 1, 0, 1, 1]

In [187]: list(intermix([1,0,1,1,2,1,0,1,1,1,1,1,1,1,2]))
ValueError: No solution possible

In [188]: list(intermix([1,0,1,1,2,1,0,1,1,1,1,1,1,1,2], nconsecutive=3))
Out[188]: [1, 1, 1, 0, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 0]
like image 119
unutbu Avatar answered Oct 12 '22 23:10

unutbu


You can use shuffle from random:

list1=[1,1,1,1,1]
list2=[2,2,2,2,2]
import random
lst = list1+list2
print(lst)
while True:
    random.shuffle(lst)
    if not any([lst[i-2]==lst[i-1]==lst[i]for i in range(len(lst))]):
        break
print(lst)

Result:

[1, 2, 1, 1, 2, 1, 2, 2, 1, 2]

Explanation: I add the two list and suffle it. Then I repeat shuffle until there's not 3 same consecutive numbers. As comment point out, it may take a long time but it's easy to make a limit of iterations.
Trying to explain why your code don't work:

def guyGenerator(toughGuy,softGuy):
    import random
    i = 0
    while len(softGuy) > 0:
        temp = softGuy[:1]
        while i < len(toughGuy)- 1:
            if toughGuy[i] == toughGuy[i + 1] == 2:
                # you insert temp[0]: it can be inserted before i
                # for lst = [0, 1, 2, 3]
                # lst.insert(0, "X") result in ["X", 0, 1, 2, 3]
                # But then, the next time, you'll test exactly the
                # same thing: because you increment i and have move all
                # the items of toughGuy one char to the right
                # and that, without reducing softGuy len.
                toughGuy.insert(random.randint(i, i + 1), temp[0])
            i = i + 1
        softGuy = softGuy[1:]
    return toughGuy

print(guyGenerator([2,2,2,2,2], [1,1,1,1,1]))

Well? I'm clear?

lst = [1, 2, 3, 4, 5]
>>> lst.insert(0, "X")
>>> lst
['X', 1, 2, 3, 4, 5]
>>> lst = [1, 2, 3, 4, 5]
>>> lst.insert(1, "X")
>>> lst
[1, 'X', 2, 3, 4, 5]
>>> 

If randint give i, then "X" is inserted before the items.
You increment i and you so i locate exactly the same element:

>>> lst = [1, 2, 3, 4, 5]
>>> lst.insert(0, "X")
>>> lst
['X', 1, 2, 3, 4, 5]
>>> lst.insert(1, "X")
>>> lst
['X', 'X', 1, 2, 3, 4, 5]
>>> 

That's the problem, because you're still in the same loop!!! So you can insert more than one temp[0] before reducing softGuy by one element. Yes?
Any suggestion would be welcome!

like image 44
Clodion Avatar answered Oct 12 '22 22:10

Clodion