How can I reverse a list in Python?

后端 未结 30 2550
既然无缘
既然无缘 2020-11-21 22:32

How can I do the following in Python?

array = [0, 10, 20, 40]
for (i = array.length() - 1; i >= 0; i--)

I need to have the elements of a

30条回答
  •  囚心锁ツ
    2020-11-21 23:18

    Summary of Methods with Explanation and Timing Results

    There are three different built-in ways to reverse a list. Which method is best depends on whether you need to:

    1. Reverse an existing list in-place (altering the original list variable)
      • Best solution is object.reverse() method
    2. Create an iterator of the reversed list (because you are going to feed it to a for-loop, a generator, etc.)
      • Best solution is reversed(object) which creates the iterator
    3. Create a copy of the list, just in the reverse order (to preserve the original list)
      • Best solution is using slices with a -1 step size: object[::-1]

    From a speed perspective, it is best to use the built-in functions to reverse a list. In this case, they are 2 to 8 times faster on short lists (10 items), and up to ~300+ times faster on long lists compared to a manually-created loop or generator. This makes sense as they are written in a native language (i.e. C), have experts creating them, scrutiny, and optimization. They are also less prone to defects and more likely to handle edge and corner cases.

    Test Script

    Put all the code snippets in this answer together to make a script that will run the different ways of reversing a list that are described below. It will time each method while running it 100,000 times. The results are shown in the last section for lists of length 2, 10, and 1000 items.

    from timeit import timeit
    from copy import copy
    
    def time_str_ms(t):
        return '{0:8.2f} ms'.format(t * 1000)
    

    Method 1: Reverse in place with obj.reverse()

    If the goal is just to reverse the order of the items in an existing list, without looping over them or getting a copy to work with, use the .reverse() function. Run this directly on a list object, and the order of all items will be reversed:

    Note that the following will reverse the original variable that is given, even though it also returns the reversed list back. i.e. you can create a copy by using this function output. Typically, you wouldn't make a function for this, but the timing script requires it.

    We test the performance of this two ways - first just reversing a list in-place (changes the original list), and then copying the list and reversing it afterward to see if that is the fastest way to create a reversed copy compared to the other methods.

    def rev_in_place(mylist):
        mylist.reverse()
        return mylist
    
    def rev_copy_reverse(mylist):
        a = copy(mylist)
        a.reverse()
        return a
    

    Method 2: Reverse a list using slices obj[::-1]

    The built-in index slicing method allows you to make a copy of part of any indexed object.

    • It does not affect the original object
    • It builds a full list, not an iterator

    The generic syntax is: [first_index:last_index:step]. To exploit slicing to create a simple reversed list, use: [::-1]. When leaving an option empty, it sets them to defaults of the first and last element of the object (reversed if the step size is negative).

    Indexing allows one to use negative numbers, which count from the end of the object's index backwards (i.e. -2 is the second to last item). When the step size is negative, it will start with the last item and index backward by that amount.

    def rev_slice(mylist):
        a = mylist[::-1]
        return a
    

    Method 3: Reverse a list with the reversed(obj) iterator function

    There is a reversed(indexed_object) function:

    • This creates a reverse index iterator, not a list. Great if you are feeding it to a loop for better performance on large lists
    • This creates a copy and does not affect the original object

    Test with both a raw iterator, and creating a list from the iterator.

    def reversed_iterator(mylist):
        a = reversed(mylist)
        return a
    
    def reversed_with_list(mylist):
        a = list(reversed(mylist))
        return a
    

    Method 4: Reverse list with Custom/Manual indexing

    As the timing shows, creating your own methods of indexing is a bad idea. Use the built-in methods unless you really do need to do something custom. This simply means learning the built-in methods.

    That said, there is not a huge penalty with smaller list sizes, but when you scale up the penalty becomes tremendous. The code below could be optimized, I'm sure, but it can't ever match the built-in methods as they are directly implemented in a native language.

    def rev_manual_pos_gen(mylist):
        max_index = len(mylist) - 1
        return [ mylist[max_index - index] for index in range(len(mylist)) ]
    
    def rev_manual_neg_gen(mylist):
        ## index is 0 to 9, but we need -1 to -10
        return [ mylist[-index-1] for index in range(len(mylist)) ]
    
    def rev_manual_index_loop(mylist):
        a = []
        reverse_index = len(mylist) - 1
        for index in range(len(mylist)):
            a.append(mylist[reverse_index - index])
        return a
        
    def rev_manual_loop(mylist):
        a = []
        reverse_index = len(mylist)
        for index, _ in enumerate(mylist):
            reverse_index -= 1
            a.append(mylist[reverse_index])
        return a
    

    Timing each method

    Following is the rest of the script to time each method of reversing. It shows reversing in place with obj.reverse() and creating the reversed(obj) iterator are always the fastest, while using slices is the fastest way to create a copy.

    It also proves not to try to create a way of doing it on your own unless you have to!

    loops_to_test = 100000
    number_of_items = 10
    list_to_reverse = list(range(number_of_items))
    if number_of_items < 15:
        print("a: {}".format(list_to_reverse))
    print('Loops: {:,}'.format(loops_to_test))
    # List of the functions we want to test with the timer, in print order
    fcns = [rev_in_place, reversed_iterator, rev_slice, rev_copy_reverse,
            reversed_with_list, rev_manual_pos_gen, rev_manual_neg_gen,
            rev_manual_index_loop, rev_manual_loop]
    max_name_string = max([ len(fcn.__name__) for fcn in fcns ])
    for fcn in fcns:
        a = copy(list_to_reverse) # copy to start fresh each loop
        out_str = ' | out = {}'.format(fcn(a)) if number_of_items < 15 else ''
        # Time in ms for the given # of loops on this fcn
        time_str = time_str_ms(timeit(lambda: fcn(a), number=loops_to_test))
        # Get the output string for this function
        fcn_str = '{}(a):'.format(fcn.__name__)
        # Add the correct string length to accommodate the maximum fcn name
        format_str = '{{fx:{}s}} {{time}}{{rev}}'.format(max_name_string + 4)
        print(format_str.format(fx=fcn_str, time=time_str, rev=out_str))
    

    Timing Results

    The results show that scaling works best with the built-in methods best suited for a given task. In other words, as the object element count increases, the built-in methods begin to have far superior performance results.

    You are also better off using the best built-in method that directly achieves what you need than to string things together. i.e. slicing is best if you need a copy of the reversed list - it's faster than creating a list from the reversed() function, and faster than making a copy of the list and then doing an in-place obj.reverse(). But if either of those methods are really all you need, they are faster, but never by more than double the speed. Meanwhile - custom, manual methods can take orders of magnitude longer, especially with very large lists.

    For scaling, with a 1000 item list, the reversed() function call takes ~30 ms to setup the iterator, reversing in-place takes just ~55 ms, using the slice method takes ~210 ms to create a copy of the full reversed list, but the quickest manual method I made took ~8400 ms!!

    With 2 items in the list:

    a: [0, 1]
    Loops: 100,000
    rev_in_place(a):             24.70 ms | out = [1, 0]
    reversed_iterator(a):        30.48 ms | out = 
    rev_slice(a):                31.65 ms | out = [1, 0]
    rev_copy_reverse(a):         63.42 ms | out = [1, 0]
    reversed_with_list(a):       48.65 ms | out = [1, 0]
    rev_manual_pos_gen(a):       98.94 ms | out = [1, 0]
    rev_manual_neg_gen(a):       88.11 ms | out = [1, 0]
    rev_manual_index_loop(a):    87.23 ms | out = [1, 0]
    rev_manual_loop(a):          79.24 ms | out = [1, 0]
    

    With 10 items in the list:

    rev_in_place(a):             23.39 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    reversed_iterator(a):        30.23 ms | out = 
    rev_slice(a):                36.01 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    rev_copy_reverse(a):         64.67 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    reversed_with_list(a):       50.77 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    rev_manual_pos_gen(a):      162.83 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    rev_manual_neg_gen(a):      167.43 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    rev_manual_index_loop(a):   152.04 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    rev_manual_loop(a):         183.01 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    

    And with 1000 items in the list:

    rev_in_place(a):             56.37 ms
    reversed_iterator(a):        30.47 ms
    rev_slice(a):               211.42 ms
    rev_copy_reverse(a):        295.74 ms
    reversed_with_list(a):      418.45 ms
    rev_manual_pos_gen(a):     8410.01 ms
    rev_manual_neg_gen(a):    11054.84 ms
    rev_manual_index_loop(a): 10543.11 ms
    rev_manual_loop(a):       15472.66 ms
    

    提交回复
    热议问题