Jerry Cohen
Jerry Cohen

Reputation: 133

Adding individual items from two lists into a new list

Write a function that expects two lists of integers, a and b, as parameters and returns a list. The function should merge the elements of both input lists by index and return them as tuples in a new list. If one list is shorter than the other, the last element of the shorter list should be repeated as often as necessary. If one or both lists are empty, the empty list should be returned.

Please consider the following examples:

merge([0, 1, 2], [5, 6, 7]) # should return [(0, 5), (1, 6), (2, 7)]
merge([2, 1, 0], [5, 6])    # should return [(2, 5), (1, 6), (0, 6)]
merge([], [2, 3])           # should return []

You can assume that the parameters are always valid lists and you do not need to provide any kind of input validation.

My attempt:

def merge(a, b):
    if len(a) == 0 or len(b) == 0:
        mergelist = []
    elif len(a) > len(b):
        for i in range(0, len(b)):
            mergelist = [a[i], b[i]]
        for i in range(len(b), len(a)):
            mergelist = [a[i], b[len(b)]]
    elif len(b) > len(a):
        for i in range(0, len(a)):
            mergelist = [a[i], b[i]]
        for i in range(len(a), len(b)):
            mergelist = [a[len(a)], b[i]]

    return mergelist

print(merge([0, 1, 2], [5, 6]))

Can someone tell me why my code is wrong? My IDE says

the list index is out of range

but i checked it over and over, it isn't,,,i think.

EDIT: I didn't think so many people would help me and give me their version of the code. They are extremely helpful. Thank you all so much!!!

Upvotes: 2

Views: 704

Answers (8)

cards
cards

Reputation: 5033

A different implementation, used itertools.zip_longest

from itertools import zip_longest

def merge(a, b):
    # check if list empty
    if not a or not b:
        return []
    
    # merging 
    if len(a) == len(b):
        return zip(a, b)   
    elif len(a) > len(b):
        fill_term = b[-1]
    else:
        fill_term = a[-1]
    return list(zip_longest(a, b, fillvalue=fill_term))


l = merge([1, 2, 3], [0, 4])

A zip-free implementation. Shallow copy of list was used.

def merge(a, b):
    # check if list empty
    if not a or not b:
        return []
    
    # normalize the size of list
    diff = abs(len(a) - len(b))
    if len(a) > len(b):
        fill_term = b[-1]
        b = b.copy()    # shallow copy
        b.extend([fill_term]*diff)
    elif len(a) < len(b):
        fill_term = a[-1]
        a = a.copy()    # shallow copy
        a.extend([fill_term]*diff)
    
    # pairing
    return [(a_term, b[i]) for i, a_term in enumerate(a)]

To avoid shallow copies, i.e. list.copy, do for example b = b + [fill_term]*diff to create a new list, same for a

Upvotes: 1

Adrian Ang
Adrian Ang

Reputation: 580

b[len(b)] is out of range
and
a[len(a)] is out of range

The last element should be b[len(b)-1] and a[len(a)-1]
or
b[-1] and a[-1]

Upvotes: 2

Xin Cheng
Xin Cheng

Reputation: 1458

Can someone tell me why my code is wrong

b[len(b)] cause the issue. use the b[-1] instead.

How about

def merge(a,b):
    if len(a) == len(b) or not a or not b: return [ (x, y) for x, y in zip(a, b) ]
    return [ (x, y) for x, y in zip(a + [a[-1]] * (len(b) - len(a)), b) ] if len(a) < len(b) else [ (x, y) for x, y in zip(a, b + [b[-1]] * (len(a) - len(b))) ]
    

Or

# if [(x,y)] and [(y,x)] are both acceptable result, then we can use this function.
def merge(a,b):
    if len(a) == len(b) or not a or not b: return [ (x, y) for x, y in zip(a, b) ]
    [a, b] = [a, b] if len(a) < len(b) else [b, a]
    return [ (x, y) for x, y in zip(a + [a[-1]] * (len(b) - len(a)), b)]
  • Demo

    >>> print(merge([0, 1, 2], [5, 6, 7])) # should return [(0, 5), (1, 6), (2, 7)]
    [(0, 5), (1, 6), (2, 7)]
    >>> print(merge([2, 1, 0], [5, 6]))    # should return [(2, 5), (1, 6), (0, 6)]
    [(2, 5), (1, 6), (0, 6)]
    >>> print(merge([], [2, 3]))
    []
    

Upvotes: 1

Ilya
Ilya

Reputation: 802

This solution is fairy optimized as it is not extending a and b in memory.

def merge(a, b):
    if not a or not b:
        return []
    
    merged_list = list(zip(a, b))
    if len(a) < len(b):
        merged_list += [(a[-1], bb) for bb in b[len(a):]]
    elif len(a) > len(b):
        merged_list += [(b[-1], aa) for aa in a[len(b):]]
    
    return merged_list

Upvotes: 2

Quentin Bracq
Quentin Bracq

Reputation: 89

I am not sure what you are trying to do with your for loops inside your conditional statements and as a rule of thumb, we try to avoid the use of loops as much as possible although sometimes it is inevitable indeed.

So, your code breaks there b[len(b)] because indexes start at 0. Let's take one of your examples, you have b = [5,6], 5 (index 0), 6 (index 1) & len(b) = 2. Do you see why this cannot work? Fair mistake, we've all been there :)

My proposed solution:

def merge_unequal_lists(longest_list, shortest_list):
    first_part = [(longest_list[i], shortest_list[i]) for i in 
    range(len(shortest_list))]
    second_part = [(longest_list[i], shortest_list[-1]) for i in 
    range(len(shortest_list), len(longest_list))]
    mergeList = first_part + second_part
    return mergeList

def new_merge(a, b):
    mergelist = []
    if len(a) == 0 or len(b) == 0:
        return mergelist
    if len(a) == len(b):
        mergeList = list(zip(a, b))
    elif len(a) > len(b):
        mergeList = merge_unequal_lists(a, b)
    elif len(b) > len(a):
        mergeList = merge_unequal_lists(b, a)
        return mergeList

A few points, I really recommend that you start using the zip() function, very useful, especially for your use case. Also, list comprehensions are way more efficient than for loops, they come in very handy for any use case. Finally, following the DRY (Don't Repeat Yourself) principles, I tried to not repeat myself, hence the creation of an extra function, which does the same thing for two of your conditions, len(a) > len(b) & len(b) > len(a).

I hope this was helpful.

Upvotes: 2

mujjiga
mujjiga

Reputation: 16926

A shorter variant that does the same:

def merge(a,b):
  if len(a) == 0 or len(b) == 0:
    return []
  elif len(a) >= len(b):
    b = b + [b[-1]]*(len(a)-len(b))
  else:
    a = a + [a[-1]]*(len(b)-len(a))
  return list(zip(a,b))
  
print(merge([0, 1, 2], [5, 6]))
print(merge([5, 6], [0, 1, 2]))
print(merge([0, 1, 2], [5, 6, 7]))
print(merge([2, 1, 0], [5, 6]))
print(merge([], [2, 3])) 

output:

[(0, 5), (1, 6), (2, 6)]
[(5, 0), (6, 1), (6, 2)]
[(0, 5), (1, 6), (2, 7)]
[(2, 5), (1, 6), (0, 6)]
[]

Upvotes: 1

Dmitriy Neledva
Dmitriy Neledva

Reputation: 864

you can use zip() for solving this problem

def merge(a,b):
    zipped = list(zip(a,b))

    if a and b:
        if len(a) == len(b):
            return zipped
        else:
            zipped.append((a[-1],b[-1]))
            return zipped
    else:
        return []



print(merge([0, 1, 2], [5, 6]))
print(merge([5, 6], [0, 1, 2]))
print(merge([0, 1, 2], [5, 6, 7])) # should return [(0, 5), (1, 6), (2, 7)]
print(merge([2, 1, 0], [5, 6]))    # should return [(2, 5), (1, 6), (0, 6)]
print(merge([], [2, 3]))           # should return []

#output:
# [(0, 5), (1, 6), (2, 6)]
# [(5, 0), (6, 1), (6, 2)]
# [(0, 5), (1, 6), (2, 7)]
# [(2, 5), (1, 6), (0, 6)]
# []

but this function won't work if some of lists is longer than the other one by 2 or more items

Upvotes: 1

blackraven
blackraven

Reputation: 5637

There are a few things to update in your code:

(1) change this format mergelist = [a[i], b[i]] to this format mergelist.append((a[i], b[i]))

(2) need to take care of the case when len(a) == len(b)

(3) list index is out of range because list index starts with zero. For example if len(a) is 2, the allowed index in range would be 0 and 1 only. So 2 is out of range.

Try this code:

def merge(a, b):
    mergelist = []
    if len(a) == 0 or len(b) == 0:
        return mergelist
    elif len(a) >= len(b):    #takes care of len(a)==len(b)
        for i in range(0, len(b)):
            mergelist.append((a[i], b[i]))
        for i in range(len(b), len(a)):
            mergelist.append((a[i], b[len(b)-1]))    #index minus 1
    elif len(b) > len(a):
        for i in range(0, len(a)):
            mergelist.append((a[i], b[i]))
        for i in range(len(a), len(b)):
            mergelist.append((a[len(a)-1], b[i]))    #index minus 1
    return mergelist

print(merge([0, 1, 2], [5, 6]))
print(merge([5, 6], [0, 1, 2]))
print(merge([0, 1, 2], [5, 6, 7])) # should return [(0, 5), (1, 6), (2, 7)]
print(merge([2, 1, 0], [5, 6]))    # should return [(2, 5), (1, 6), (0, 6)]
print(merge([], [2, 3]))           # should return []

Output:

[(0, 5), (1, 6), (2, 6)]
[(5, 0), (6, 1), (6, 2)]
[(0, 5), (1, 6), (2, 7)]
[(2, 5), (1, 6), (0, 6)]
[]

Upvotes: 4

Related Questions