1
\$\begingroup\$
def combine_in_place(first, second):
    combined = []
    maxLength = max(len(first), len(second))
    for i in range(maxLength):
        if len(first) < i+1:
            combined.append(second[i])
        elif len(second) < i+1:
            combined.append(first[i])
        else:
            if not first[i]:
                combined.append(second[i])
            elif not second[i]:
                combined.append(first[i])
            else:
                combined.append(first[i] + second[i])

    return combined


print combine_in_place([['f'], ['j', 't'], [], ['s'], [], []], [['h', 'd'], [], [], []])
    # prints: [['f', 'h', 'd'], ['d', 't'], [], ['s'], [], []]

So I know to program (been programming in Java for some time now). I am trying to learn Python but I am not sure what's the best way to do some stuff. My question is:
How to write the function in a more Pythonic way?

\$\endgroup\$
7
  • 2
    \$\begingroup\$ The title should describe what the code does. Please edit it. Maybe you missed the placeholder on the title element: "State the task that your code accomplishes. Make your title distinctive.". Also from How to Ask: "State what your code does in your title, not your main concerns about it.". \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Jan 6 '20 at 22:41
  • \$\begingroup\$ (Sorry for jumping in and coercing the title during suggested edit review.) \$\endgroup\$ – greybeard Jan 7 '20 at 7:35
  • \$\begingroup\$ I know to program & combine_in_place([['f'], ['j', 't'], …], [['h', 'd'], …])& # prints: [['f', 'h', 'd'], ['d', 't'], …] hm, hm, hm, hm. \$\endgroup\$ – greybeard Jan 7 '20 at 7:37
  • \$\begingroup\$ What does in_place mean in the name? Surely our definition differ as you are returning a new list. \$\endgroup\$ – 301_Moved_Permanently Jan 7 '20 at 12:53
  • \$\begingroup\$ I'm fairly shocked that no-one's dropped the 1 line itertools way. It's just return map(chain.from_iterable, zip_longest(*args, fillvalue=())). \$\endgroup\$ – Peilonrayz Jan 7 '20 at 13:06
4
\$\begingroup\$

I would write something like that:

def combine(first, second):
    ret_list = []
    for i, j in zip(first, second):
        add = []
        if i is not None:
            add.extend(i)
        if j is not None:
            add.extend(j)
        ret_list.append(add)
    return ret_list

I don't know if it is the most python-ish way but it is ok I think...

I mean you can also write something like this:

def combine(first, second):
    return [(i.extend(j) if not (i is None or j is None) else i if j is None else j) for i, j in zip(first, second)]

But you cannot read and debug that. So I would strongly recommend against it.

Btw: Did not test the last one so I hope I did not forget a closing bracket ;)

Edit:

In order to keep all elements you have to add something to the end. The zip() function stops iterating after the shortest element reached its end. This is why the following method is needed instead of zip:

def combine(first, second):
    ret_list = []
    for i, j in itertools.zip_longest(first, second):
        add = []
        if i is not None:
            add.extend(i)
        if j is not None:
            add.extend(j)
        ret_list.append(add)
    return ret_list

Note: Itertools simply must be imported first. (For Python2 it is itertools.izip_longest()...)

\$\endgroup\$
4
  • \$\begingroup\$ This works but I want to keep empty lists at the end as well (which I did not make clear in my question). I edited my question to include that request. \$\endgroup\$ – Shehab Ellithy Jan 6 '20 at 22:05
  • \$\begingroup\$ When I tested the combine function, it removed the empty ones at the end. \$\endgroup\$ – Shehab Ellithy Jan 6 '20 at 22:19
  • \$\begingroup\$ @ShehabEllithy yeah forgot something. Wait a sec. \$\endgroup\$ – Cedric Jan 6 '20 at 22:20
  • \$\begingroup\$ Perfect, that is what I needed. \$\endgroup\$ – Shehab Ellithy Jan 7 '20 at 6:43
3
\$\begingroup\$

Here's how I might do it:

from itertools import chain
from typing import List

def combine_in_place(*lists: List[List[str]]) -> List[List[str]]:
    return [
        list(chain(*[a[i] if i < len(a) else [] for a in lists])) 
        for i in range(max(len(a) for a in lists))
    ]

Some general principles I've attempted to follow:

  1. When you're operating on multiple things, try to express them as a single iterable rather than individual named variables
  2. If you can replace a bunch of individual if x < y with a single call to min() or max(), do that
  3. It's generally preferable to build a list through comprehension rather than using extend/append.
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Since you reached for itertools, I’d use zip_longest with a fillvalue of [] or (). Then you can have the simpler [list(chain.from_iterable(elements)) for elements in zip_longest(*lists, fillvalue=[])] \$\endgroup\$ – 301_Moved_Permanently Jan 7 '20 at 13:01
2
\$\begingroup\$

The Pythonic way may include:
 • know and follow your Python Enhancement Proposals and
 • know and exploit your Python Standard Library;
 • follow sound development practices is debatably language oblivious

The code presented does not violate PEP 8Style Guide for Python Code
It does not follow PEP 257Docstring Conventions
or PEP 484Type Hints

Documenting code, in Python using docstrings, is one of my pet programming peeves:
Note how implementations in answers follow the one in the question in not combining in place, neither in the inner lists, nor in the outer one.
Note also how _in_place got omitted in Cedced_Bro's accepted answer.
No (previous) answer has suggested using/returning a generator or allowing an arbitrary number of lists, given the advantages, this, too, may be due to lacking documentation of combine_in_place().

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.