简体   繁体   中英

Why is my code for sorting a list via reduce in Python throwing an error?

I thought of a one-liner to sort a list in Python, but it hasn't been working. I thought that I could use reduce to go through the list and swap elements as needed, like in bubble sort. But the interpreter is being frustrating today:

from functools import  reduce

def reduce_sort(*lst):
    sorter = lambda x, y: y, x if x > y else x, y
    return reduce(sorter, lst)

reduce_sort(5, 4, 6, 7, 1)

This is giving me NameError: name 'x' is not defined

Does anyone have any idea why this is happening?

sorter is a tuple:

import ast

print(ast.dump(
  ast.parse("lambda x, y: y, x if x > y else x, y")
))

Try it online!

It actually parses to this:

Module(body=[Expr(value=
  Tuple(elts=[
    Lambda(
      args=arguments(posonlyargs=[], args=[arg(arg='x', annotation=None, type_comment=None), arg(arg='y', annotation=None, type_comment=None)], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
      body=Name(id='y', ctx=Load())
    ),
    IfExp(
      test=Compare(left=Name(id='x', ctx=Load()), ops=[Gt()], comparators=[Name(id='y', ctx=Load())]),
      body=Name(id='x', ctx=Load()),
      orelse=Name(id='x', ctx=Load())
    ),
    Name(id='y', ctx=Load())
  ],
ctx=Load()))],type_ignores=[])

So it's equivalent to

sorter = (lambda x, y: y), (x if x > y else x), y

...where the second expression, x if x > y else x , will raise an exception because it doesn't know what x is.

You should explicitly specify that the if expression evaluates to tuples:

sorter = lambda x, y: (y, x) if x > y else (x, y)

Now sorter is a function, but the code as a whole won't work because on the second iteration of reduce x will become a tuple (one of those returned by the if expression) that'll be compared to y , which is an integer. Such operation is not possible, so you'll get another error, now at runtime.

Sorting with functools.reduce is a good idea.

Why your code currently doesn't work

However, once you start combining two or more elements from the initial list into a sorted sublist, your function sorter = lambda x, y: y, x if x > y else x, y is no longer adequate: it only knows how to combine two elements into a length-2 list; it doesn't know how to combine two sorted lists into a sorted list.

Example:

initial list =   [17, 45, 100, 96, 29, 15, 0, 32, 4, 100, 61, 11, 85, 96, 2, 75, 88, 51, 16, 27, 68, 74, 99, 27, 83, 38, 96, 40, 73, 99, 33, 36, 86, 54, 50, 36, 44, 56, 0, 62, 62, 87, 64, 14, 63, 78, 98, 64, 60, 57, 56, 80, 80, 32, 40, 51, 64, 29, 21, 43, 63, 44, 35, 25, 37, 10, 13, 9, 34, 10, 39, 70, 14, 33, 49, 16, 80, 50, 14, 82, 51, 82, 67, 10, 20, 6, 59, 5, 31, 62, 83, 92, 13, 59, 71, 65, 1, 25, 78, 45]
began reducing = [[17, 45], [96, 100], [15, 29], [0, 32], 4, 100, 61, 11, 85, 96, 2, 75, 88, 51, 16, 27, 68, 74, 99, 27, 83, 38, 96, 40, 73, 99, 33, 36, 86, 54, 50, 36, 44, 56, 0, 62, 62, 87, 64, 14, 63, 78, 98, 64, 60, 57, 56, 80, 80, 32, 40, 51, 64, 29, 21, 43, 63, 44, 35, 25, 37, 10, 13, 9, 34, 10, 39, 70, 14, 33, 49, 16, 80, 50, 14, 82, 51, 82, 67, 10, 20, 6, 59, 5, 31, 62, 83, 92, 13, 59, 71, 65, 1, 25, 78, 45]
sorter([15, 29], [0, 32]) = ???

How to fix this sorter function? What we want is to be able to merge two sorted lists into a sorted list. Does this ring a bell? Yes! It's the merge function from mergesort .

We could implement it ourselves... Or we can look in the python standard modules if it isn't already implemented somewhere. It is. It's called heapq.merge .

Replacing sorter with heapq.merge

Let's replace your sorter with heapq.merge :

import random
import functools
import heapq
ll = [random.randint(0,100) for i in range(100)]
list(functools.reduce(heapq.merge, ll))
# TypeError: 'int' object is not iterable

Oops! heapq.merge expects two iterables. Initially our list doesn't contains lists, it contains single elements. We need to replace these single elements with singleton lists or singleton tuples:

import random
import functools
import heapq
ll = [(random.randint(0,100),) for i in range(100)]
list(functools.reduce(heapq.merge, ll))
# [3, 3, 4, 5, 5, 5, 7, 8, 8, 10, 11, 13, 15, 15, 15, 15, 16, 16, 17, 18, 19, 20, 20, 20, 24, 25, 25, 27, 27, 28, 30, 30, 30, 30, 31, 32, 33, 33, 38, 38, 39, 39, 41, 42, 43, 43, 43, 46, 46, 47, 49, 50, 51, 52, 53, 55, 62, 63, 63, 64, 64, 64, 65, 66, 67, 68, 69, 71, 72, 72, 72, 72, 73, 73, 73, 74, 78, 79, 82, 82, 83, 83, 86, 86, 86, 86, 89, 91, 92, 93, 95, 96, 96, 96, 97, 98, 99, 99, 100, 100]

It works! Note the ( ,) construct inside of ll that makes singleton tuples. If you don't like tuples and would prefer lists, you can use [ ] instead of ( ,) .

Of course, you want to be able to sort a list that contains numbers, not singleton tuples. So let's add lambda x: (x,) or lambda x: [x] to our mix:

The final solution

import functools
import heapq
def reduce_sort(ll):
  list(functools.reduce(heapq.merge, map(lambda x: (x,), ll)))

import random
ll = [random.randint(0,100) for i in range(100)]
reduce_sort(ll)
# [3, 3, 4, 5, 5, 5, 7, 8, 8, 10, 11, 13, 15, 15, 15, 15, 16, 16, 17, 18, 19, 20, 20, 20, 24, 25, 25, 27, 27, 28, 30, 30, 30, 30, 31, 32, 33, 33, 38, 38, 39, 39, 41, 42, 43, 43, 43, 46, 46, 47, 49, 50, 51, 52, 53, 55, 62, 63, 63, 64, 64, 64, 65, 66, 67, 68, 69, 71, 72, 72, 72, 72, 73, 73, 73, 74, 78, 79, 82, 82, 83, 83, 86, 86, 86, 86, 89, 91, 92, 93, 95, 96, 96, 96, 97, 98, 99, 99, 100, 100]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM