问题
I was given this as an exercise. I could of course sort a list by using sorted() or other ways from Python Standard Library, but I can't in this case. I think I'm only supposed to use reduce().
from functools import reduce
arr = [17, 2, 3, 6, 1, 3, 1, 9, 5, 3]
sorted_arr = reduce(lambda a,b : (b,a) if a > b else (a,b), arr)
The error I get:
TypeError: '>' not supported between instances of 'tuple' and 'int'
Which is expected, because my reduce function inserts a tuple into the int array, instead of 2 separate integers. And then the tuple gets compared to an int...
Is there a way to insert back 2 numbers into the list, and only run the function on every second number in the list? Or a way to swap the numbers with using reduce()?
Documentation says very little about the reduce function, so I am out of ideas right now. https://docs.python.org/3/library/functools.html?highlight=reduce#functools.reduce
回答1:
Here is one way to sort the list using reduce
:
arr = [17, 2, 3, 6, 1, 3, 1, 9, 5, 3]
sorted_arr = reduce(
lambda a, b: [x for x in a if x <= b] + [b] + [x for x in a if x > b],
arr,
[]
)
print(sorted_arr)
#[1, 1, 2, 3, 3, 3, 5, 6, 9, 17]
At each reduce step, build a new output list which concatenates a list of all of the values less than or equal to b
, [b]
, and a list of all of the values greater than b
. Use the optional third argument to reduce
to initialize the output to an empty list.
回答2:
I think you're misunderstanding how reduce works here. Reduce is synonymous to right-fold in some other languages (e.g. Haskell). The first argument expects a function which takes two parameters: an accumulator and an element to accumulate.
Let's hack into it:
arr = [17, 2, 3, 6, 1, 3, 1, 9, 5, 3]
reduce(lambda xs, x: [print(xs, x), xs+[x]][1], arr, [])
Here, xs
is the accumulator and x
is the element to accumulate. Don't worry too much about [print(xs, x), xs+[x]][1]
– it's just there to print intermediate values of xs
and x
. Without the printing, we could simplify the lambda to lambda xs, x: xs + [x]
, which just appends to the list.
The above outputs:
[] 17
[17] 2
[17, 2] 3
[17, 2, 3] 6
[17, 2, 3, 6] 1
[17, 2, 3, 6, 1] 3
[17, 2, 3, 6, 1, 3] 1
[17, 2, 3, 6, 1, 3, 1] 9
[17, 2, 3, 6, 1, 3, 1, 9] 5
[17, 2, 3, 6, 1, 3, 1, 9, 5] 3
As we can see, reduce
passes an accumulated list as the first argument and a new element as the second argument.(If reduce
is still boggling you, How does reduce work? contains some nice explanations.)
Our particular lambda inserts a new element into the accumulator on each "iteration". This hints at insertion sort:
def insert(xs, n):
"""
Finds first element in `xs` greater than `n` and returns an inserted element.
`xs` is assumed to be a sorted list.
"""
for i, x in enumerate(xs):
if x > n:
return xs[:i] + [n] + xs[i:]
return xs + [n]
sorted_arr = reduce(insert, arr, [])
print(sorted_arr)
This prints the correctly sorted array:
[1, 1, 2, 3, 3, 3, 5, 6, 9, 17]
Note that a third parameter to reduce
(i.e. []
) was specified as we initialise the sort should with an empty list.
回答3:
Ninjad! But yes, it's an insertion sort.
def insert(acc, e):
for i, x in enumerate(acc):
if x > e:
acc.insert(i, e)
return acc
acc.append(e)
return acc
reduce(insert, [1, 2, 6, 4, 7, 3, 0, -1], [])
outputs
[-1, 0, 1, 2, 3, 4, 6, 7]
回答4:
After some thinking I concluded that it is also possible to do swap-based sort, if you are allowed to use reduce
more than once. Namely:
from functools import reduce
arr = [17, 2, 3, 6, 1, 3, 1, 9, 5, 3]
def func(acc,x):
if not acc:
return [x]
if acc[-1]<x:
return acc+[x]
else:
return acc[:-1]+[x]+acc[-1:]
def my_sort(x):
moresorted = reduce(func,x,[])
print(moresorted)
if x==moresorted:
return moresorted
else:
return my_sort(moresorted)
print('arr:',arr)
arr_sorted = my_sort(arr)
print('arr sorted:',arr_sorted)
Output:
arr: [17, 2, 3, 6, 1, 3, 1, 9, 5, 3]
[2, 3, 6, 1, 3, 1, 9, 5, 3, 17]
[2, 3, 1, 3, 1, 6, 5, 3, 9, 17]
[2, 1, 3, 1, 3, 5, 3, 6, 9, 17]
[1, 2, 1, 3, 3, 3, 5, 6, 9, 17]
[1, 1, 2, 3, 3, 3, 5, 6, 9, 17]
[1, 1, 2, 3, 3, 3, 5, 6, 9, 17]
arr sorted: [1, 1, 2, 3, 3, 3, 5, 6, 9, 17]
I placed print(moresorted)
inside func
for educational purposes, you could remove it if you wish.
Now explanation: my_sort
is recursive function, with every run of it list become more and more sorted. func
which is used as function in reduce
does append new element and then swaps 2 last elements of list if they are not in ascending order.
This mean in every run of my_sort
number "travels" rightward until in take place where next number is bigger.
if not acc
is required for starting - notice that third argument of reduce
is []
meaning that during first execution of func
in each reduce
first argument for func is []
, so asking acc[-1]<x
? would result in error.
回答5:
Let's understand this
(1)Usage of Reduce is basically to reduce the expression to a single final value
(2)reduce() stores the intermediate result and only returns the final summation value
(3)We will take the smallest element using reduce, append it to sorted_list and remove from the original list
(4)Now the reduce will work on the rest of the elements and repeat step 3 again
(5)while list_nums: will run until the list becomes empty
list_of_nums = [1,19,5,17,9]
sorted_list=[]
while list_of_nums:
maxvalue=reduce(lambda x,y: x if x<y else y,list_of_nums)
sorted_list.append(maxvalue)
list_of_nums.remove(maxvalue)
print(sorted_list)
[1, 5, 9, 17, 19]
来源:https://stackoverflow.com/questions/56045986/is-it-possible-to-sort-a-list-with-reduce