DEV Community

Erik Anderson
Erik Anderson

Posted on

How did they do all that in one line? A Codewars challenge explanation

Today I worked on a challenge on Codewars and saw a very concise solution submitted by another user on the site. I'll first just show the code, then explain what's going on.

def up_array(arr):
  if not arr or min(arr) < 0 or max(arr) > 9:
    return None
  else:
    return [int(y) for y in str(int("".join([str(x) for x in arr])) + 1)]
Enter fullscreen mode Exit fullscreen mode

The challenge can be found here, but the basic idea is to take a list of integers, add one to the value represented by that list, and return a new list with that new value.

Let's break down that solution.

First, there's an if statement with multiple parts. if starts it off. Then there is the expression not arr. This is nice. It takes advantage of the fact that data types like list can have a boolean interpretation. In this case, if the array is empty, not arr will evaluate to True (because arr would evaluate to False).

This is attached to the next bit by or. The next two parts are more straightforward. If the minimum of the array is less than zero or if the maximum of the array is greater than nine, the if statement will evaluate to True.

So, if the list is empty or contains elements out of range, the function will return None.

Now the real meat of it.

return [int(y) for y in str(int("".join([str(x) for x in arr])) + 1)]
Wheh! That's a mouthful.

At the center of the parenthesis and brackets, we have: str(x) for x in arr. That seems straightforward enough. It seems like this would be functionally equivalent to the following (which is pseudo code):

y = []
for x in arr:
    y = str(x)
Enter fullscreen mode Exit fullscreen mode

The point is, I think that str(x) for x in arr would return an array of strings which have been converted from the integers in arr.

Let's test this.

In      arr = [2,3,9]

In      [str(x) for x in arr]
Out     ['2', '3', '9']
Enter fullscreen mode Exit fullscreen mode

Yes. That's how it works.

Next step: "".join([str(x) for x in arr]). It looks like we are joining an empty string to the array of strings. Or maybe we are joining the array of strings into one array with the empty string between them. I can test this.

In[4]:     [str(x) for x in arr]
Out[4]:    ['2', '3', '9']

In[5]:     "".join([str(x) for x in arr])
Out[5]:    '239'


In[6]      "-".join([str(x) for x in arr])
Out[6]:    '2-3-9'
Enter fullscreen mode Exit fullscreen mode

Yes. It joins together the strings of the array, with either nothing ("") or a dash ("-") between the strings.

What next?
We have (int("".join([str(x) for x in arr])) + 1). This is pretty straight forward. Take the result from above, convert it to an integer, then add one. Cool.

Next, we have str(int("".join([str(x) for x in arr])) + 1). This is an easy step. We're just taking the resulting integer and converting it to a string.

This next bit is a bit more tricky. [int(y) for y in str(int("".join([str(x) for x in arr])) + 1)]. I believe this does something analogous to:

new_list = []
for y in my_string:
    y = int(y)
    new_list.append(y)
Enter fullscreen mode Exit fullscreen mode

So it should take each character from the string, convert that to an integer, and put it into a list. Let's test it.

In[7]:    [int(y) for y in str(int("".join([str(x) for x in arr])) + 1)]
Out[7]:   [2, 4, 0]
Enter fullscreen mode Exit fullscreen mode

Yes. that's what it does. Good.

Then there's just one more step: return [int(y) for y in str(int("".join([str(x) for x in arr])) + 1)]. That means take the result of all that code, and return it as the function output.

Summary

So, to summarize that code, we have:

def up_array(arr):
  if not arr or min(arr) < 0 or max(arr) > 9:
    return None
  else:
    return [int(y) for y in str(int("".join([str(x) for x in arr])) + 1)]
Enter fullscreen mode Exit fullscreen mode

Which, if I translate to pseudo code, would look something like:

define function up_array as a function of the array, arr:
    if there are problems with the input:
        return nothing
    otherwise:
        take a list of integers, convert each to a string and put them
        together into a string, convert that string to an int, add one to 
        that int, and convert that result to a string. Then pull out the 
        characters of that string one by one, converting each into a 
        string and populating a list with those strings.
        Return that list.
Enter fullscreen mode Exit fullscreen mode

I learned a lot from picking this apart, and I hope you learned something from reading it.

Discussion (3)

Collapse
phlash909 profile image
Phil Ashby

Nested list comprehensions, nice :)

I wonder if the casts between int and str are necessary though.. perhaps there is a solution involving a more traditional add and carry approach?

Collapse
ekand profile image
Erik Anderson Author

Yeah, I thought it was a neat solution. :)

I'm not familiar with add and carry approaches, but it's interesting to consider whether it's possible without the casts.

Collapse
phlash909 profile image
Phil Ashby • Edited

I got bored and had a go. Please do not look at my code.....:)

def up_array(arr):
    res = reduce(lambda r, x: (r[0]+[(x+r[1])%10],(x+r[1])/10), arr[::-1], ([],1))
    return (res[0]+[1])[::-1] if res[1]>0 else res[0][::-1]

print up_array([2, 3, 9])
print up_array([9, 9, 9])
print up_array([9, 9, 0])
print up_array([2, 9, 9])
print up_array([9,9,9,9,9,9,9,9])