0

I wrote a merge sort function and thought I was done.. but in the assignment it says that the function is supposed to sort the actual list instead of creating a copy (so call by value instead of reference I think?). Right now, it doesn't work because the list itself isn't changed.

def mergeSort(L, ascending = True):
    print('Mergesort, Parameter L:')
    print(L)
    result = []
    if len(L) == 1: 
        return L 
    mid = len(L) // 2 
    teilliste1 = mergeSort(L[:mid], ascending)
    teilliste2 = mergeSort(L[mid:], ascending)
    x, y = 0, 0
    while x < len(teilliste1) and y < len(teilliste2): 
        if (ascending and teilliste1[x] > teilliste2[y]) or (not ascending and teilliste1[x] < teilliste2[y]):
            result.append(teilliste2[y])  
            y = y + 1  
        else:
            result.append(teilliste1[x]) 
            x = x + 1  
    result = result + teilliste1[x:] 
    result = result + teilliste2[y:]
    return result

liste1 = list([3, 2, -1, 9, 17, 4, 1, 0])
mergeSort(liste1)
print(liste1) # result will be the unsorted list

What do I need to change in the function to make it call by value and sort the actual list?

I know I could do

mergeResult = mergeSort(liste1)
print(mergeResult)

but apparently I have to change the original parameter list.

Dennis
  • 57
  • 1
  • 2
  • 10
  • 2
    First, don't worry about "call by value" vs. "call by reference". There's really now way to define those terms that makes a meaningful rather than confusing distinction in Python. (Python always passes references to values. Which one is that? Who cares; what matters is what it does, not what labels you can coerce into sort of fitting…) – abarnert Jun 11 '18 at 17:42
  • 1
    you're not doing anything with the result you're returning. – Jean-François Fabre Jun 11 '18 at 17:42
  • or in the end `L[:] = result` and don't return anything – Jean-François Fabre Jun 11 '18 at 17:43
  • Anyway, you _could_ change this by wrapping the recursive function in an outer function that does `L[:] = mergesort(L, ascending)`, but that's probably not what your teacher is after. Instead, you're supposed to redo the function so that it returns nothing, copies nothing, and passes down, e.g., `L, start, stop` to the recursive calls. – abarnert Jun 11 '18 at 17:43
  • The mergesort algorithm basically requires auxiliary storage of at least half the length of the list to sort, and most implementations use the entire length of the list. Don't worry about it: sort your list into a new one, then assign the sorted list to the old one. – Rory Daulton Jun 11 '18 at 17:44
  • merge sort by definition uses an aux array, in this case list. You have to copy `result` over to `L` to sort the input array in place and reflect the change at the point of invocation. If not you need to modify your sort algorithm to one that sorts in place and operates on `L` directly, eg: quicksort – Srini Jun 11 '18 at 17:44
  • This really depends on the assignment. It is possible to do mergesort in-place with explicit aux storage on the side, instead of doing a copying sort. There's interesting research on how small that side storage can be, but I doubt that's part of the assignment; more likely it's just looking for any explicit side storage, to show that you can figure out how to transform the algorithm without breaking it, even if the result is a bit wasteful. (Either that, or the teacher just doesn't understand mergesort… which is, depressingly, also possible.) – abarnert Jun 11 '18 at 17:53

1 Answers1

2

There are two basic ways to write a recursive decomposition function. The immutable version calls itself on copies of two smaller parts, then reassembles and returns them; that's what you wrote. The mutable version calls itself on the actual input, then modifies that in-place, and returns nothing; that's what your teacher wants here.

Notice that, unlike some other sorting algorithms, mergesort can't be done with constant extra storage, only better than linear extra storage. (Logarithmic is possible, but complicated; I doubt your teacher is insisting on that.) And because of this, most mergesort algorithms you find in books, Wikipedia, etc. will be written as copying sorts rather than in-place sorts. Which means this is probably a bit of a "trick question", trying to see whether you can figure out how to convert from the well-known copying version of the algorithm into an in-place version with explicit extra storage.


You can always write an immutable algorithm and then mutate at the very end, e.g.:

def _mergesort(L, ascending):
    # your existing code

def mergesort(L, ascending=True):
    L[:] = _mergesort(L, ascending)

This gives you all the cost of immutability without the benefits. But it does mean you can write a variety of sort functions with the same API, which are all implemented in-place if that's a reasonable optimization, but not if it isn't, which seems to be what your teacher is after.


If you don't want a wrapper function, you can change the last line from:

return result

… to:

L[:] = result

However, because this changes the API, you also need to change your recursive calls to match. For example, you could do this:

teilliste1 = L[:mid]
mergeSort(teilliste1, ascending)
teilliste2 = L[mid:]
mergeSort(teilliste2, ascending)

In Python, a mutating recursive decomposition function often works by passing start and end indices down with the list, like this:

def mergesort(L, ascending=True, start=None, stop=None):
    if start is None: start = 0
    if stop is None: stop = len(L)

    if stop - start <= 1:
        return

    mid = (stop - start) // 2 + start 
    mergeSort(L[start:mid], ascending)
    mergeSort(L[mid:stop], ascending)

    # etc.

As mentioned above, the merging step is going to require some auxiliary storage. The simplest thing to do—and probably good enough for your assignment, even though it means linear space—is to just build up a left list and a right list and then assign them back into L[start:mid], L[mid:stop] = left, right.

Notice that this isn't all that different from the L[:] = result version above; it's really just a matter of using L itself, together with start and stop indices, in place of copies for the first half of the process, and then copying only at the end during the merge.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • I was given the parameters the way they look, I don't think I am supposed to change them. Which would work with creating a new list at the end. I'll ask the teacher again because both your solutions don't tell me much. I think if it was meant to be that way, we would have covered it. Seems to make the program much longer than it needs to be. – Dennis Jun 11 '18 at 18:10
  • @Dennis Honestly, it's always possible that your teacher didn't think things through (or even doesn't understand mergesort). This would be a great assignment to give with quicksort, where being able to convert between copying and (constant-storage) in-place is easy and instructive, but asking it with mergesort doesn't make as much sense. – abarnert Jun 11 '18 at 18:14
  • Part 2 of the assignment is actually to write a quicksort algorithm both ascending and descending. Still, the .py file provided looks like this liste1 = list([3, 2, -1, 9, 17, 4, 1, 0]) mergeSort(liste1) print(liste1) liste2 = list([3, 2, -1, 9, 17, 4, 1, 0]) quickSort(liste2, False) print(liste2) (I hope you can post code in comments) And it specifically says not to create a new list for both functions. – Dennis Jun 11 '18 at 18:23
  • @Dennis Code in comments doesn't get formatted, unfortunately. I think this is meant to encourage you to edit code that's relevant to your question (or answer) into the question (or answer) itself, trade long code chunks over chat instead of comments, etc. But even if it's uncommon that it makes sense to put code in a comment, sometimes it does—like yours right now—and it's pretty annoying. But anyway, I can tell what you meant. And honestly "don't create a new list" on a mergesort assignment really seems like a trick question or a mistake rather than a meaningful requirement at this level… – abarnert Jun 11 '18 at 18:26
  • @Dennis Anyway, since you said you could ask the teacher—that's almost certainly the best thing to do. Especially if you can make it clear that you've studied up on mergesort (e.g., read the Wikipedia page on it, far enough that you can point out that practical in-place mergesort with sublinear space is an open research problem). – abarnert Jun 11 '18 at 18:29
  • I already sent an e-mail. I'll comment again (or edit the original post) when I get an answer. Thanks for your time and explanations, though! – Dennis Jun 11 '18 at 18:31
  • I got an answer. It's not supposed to be in-place but the function needs to act like a void function, so the list that is called with the function gets sorted at the end. Apparently the goal is that if you call a sorting algorithm, you want to call it without knowing whether it's in-place or not. – Dennis Jun 12 '18 at 17:26
  • @Dennis Then you want my first answer, where you just assign `L[:] = …` at the end, instead of returning the new list to the caller. – abarnert Jun 12 '18 at 17:32
  • Is there any way to do this all in one function instead of creating a second one? While it works, I'm really not happy with it. – Dennis Jun 12 '18 at 17:43
  • @Dennis OK, edited (not tested; if it doesn't work, let me know…). – abarnert Jun 12 '18 at 17:50
  • It does! I'm trying to understand this.. what exactly happens when you use L[:] = result? Why does assigning result to a sliced L change anything? – Dennis Jun 12 '18 at 18:16
  • Using a slice of a list as an assignment target modifies the referenced part of the list in-place. See [the docs](https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types). For example, `lst[1:3] = [4, 5]` replaces elements #1 and #2 with the values 4 and 5, and `lst[:] = [4, 5]` replaces all of the elements so the list now just holds `[4, 5]`. Does that clarify things? – abarnert Jun 12 '18 at 18:34
  • Yes, that's actually pretty neat. I'll level with you, though.. I was pretty happy with my original solution and I still don't quite understand the need to change it. I'm glad it works this way but why make something unnecessarily complicated? Anyway, I'm off to write a quicksort algorithm. Thank you again for your help! – Dennis Jun 12 '18 at 18:41
  • @Dennis Personally, I agree with you. As I said, "This gives you all the cost of immutability without the benefits." And it's misleading—anyone who's intentionally choosing a mergesort for good reasons is likely to be surprised that it's acting like an in-place function. But I can kind of understand your teacher's point (at least what I think it is)—writing a suite of `foosort` functions that are all in-place, and maybe `foosorted` functions that are copying (which may be your next assignment?) isn't _too_ ridiculous, even if some of the individual functions are useless. – abarnert Jun 12 '18 at 18:50