Python reverse-stride slicing

Question:

A specific example of my question is, “How can I get ‘3210’ in this example?”


>>> foo = '0123456'
>>> foo[0:4]
'0123'
>>> foo[::-1]
'6543210'
>>> foo[4:0:-1] # I was shooting for '3210' but made a fencepost error, that's fine, but...
'4321'
>>> foo[3:-1:-1] # How can I get '3210'?
''
>>> foo[3:0:-1]
'321'

It seems strange that I can write foo[4:0:-1], foo[5:1:-1], etc. and get what I would expect, but there’s no way to write the slice so that I get ‘3210’.

A makeshift way of doing this would be foo[0:4][::-1], but this creates two string objects in the process. I will be performing this operation literally billions of times, so every string operation is expensive.

I must be missing something silly and easy. Thanks for your help!

Asked By: eblume

||

Answers:

Simply exclude the end range index…

>>> foo[3::-1]
'3210'

Ironically, about the only option I think you didn’t try.

Answered By: Andrew White

If you’re looking for something a little more human-readable than extended slice notation:

>>> foo = '0123456'
>>> ''.join(reversed(foo[0:4]))
'3210'
Answered By: Aaron Dufour

Omit the end index in your slice notation:

>>> foo = '0123456'
>>> foo[3::-1]
'3210'

If you have to do this many times, create a slice object that you can use over and over

>>> first_4_items_reversed = slice(3,None,-1)
>>> foo[first_4_items_reversed]
'3210'
Answered By: PaulMcG

In addition to the above solutions, you can do something like:

foo = '0123456'
foo[-4::-1]

I guess if foo is going to be changing lengths, this may not be the best solution, but if the length is static it would work.

Answered By: jack

You can use s[::-1] to reverse the entire string. But if you want to reverse each substring with some fixed length, you can first extract the substring and then reverse the entire substring. For example, let’s assume we need to check whether each substring with length 3 of string foo is a palindrome, we can do it like this:

>>> foo = '0102030'
>>> for i in range(len(foo)-3):
...     if foo[i:i+3] == foo[i:i+3][::-1]:
...         print(foo[i:i+3], 'is a palindrome')
...     else:
...         print(foo[i:i+3], 'is not a palindrome')
...
010 is a palindrome
102 is not a palindrome
020 is a palindrome
203 is not a palindrome
030 is a palindrome

If you want to check if a substring is palindrome like this:

if foo[i:i+3] == foo[i+2:i-1:-1]:
    ...

you will not be able to handle the case of i being 0, since you are actually comparing foo[0:3] with foo[2:-1:-1], which is equivalent to foo[2:n-1:-1], which in turn is an empty string.

The only drawback of the first solution is that it uses a little more memory but it’s no big deal.

Answered By: Luan Gong

After reading the “technical documentation” (here) – specifically the sentence:

If either bound is negative, the sequence’s length is added to it.

I decided to try this, and it worked:

>>> foo = '0123456'
>>> foo[3:-1-len(foo):-1]
'3210'
>>>

So I think the best answer to programmatically determine the “end point” would be to provide a well named helper function that makes it clear that its arguments are always treated like positive offsets, maybe special_slice()

I think the clarity of this ‘special’ case is extremely important since lots of common and significant use cases depend on the default behavior of negative offsets (i.e. adding the length to them). Personally I frequently use a ‘-1’ end point to mean: stop just before last element.

So, based on your comment:

… algorithm that works somewhat like as follows: foo[i:i-4:-1], and starts with a high ‘i’ and walks down.

I might make the following:

def slice_by_len(data, start, length, step=1):
    end = start + length if step > 0 else start - length
    if end < 0:
        # Fix the negative offset to get what we really want
        end -= len(data)
    return data[start:end:step]

And then call it for each slice required:

foo_part = slice_by_len(foo, i, 4, -1)

The above could easily go in a loop over values of ‘i’

Answered By: CrashNeb
s="this is my world"
pattern=re.findall(r'S+',s)
a=[]
for i in range(len(pattern)):
    a.append((pattern[i][::-1]))
print (a)
print (" ".join(a))
Answered By: Vishwanath

Given:

>>> foo = '0123456'

The desired string 3210 is from index 3rd to the 0-th characters:

>>> stop_idx=0
>>> start_idx=3

Here are two generic solutions:

  1. Take the forward slice then reverse it:

    >>> foo[stop_idx:start_idx+1][::-1]
    '3210'
    
  2. Based on this answer, use a negative step and stop 1 element before the first element (plus the stop offset):

    >>> foo[start_idx:stop_idx-len(foo)-1:-1]
    '3210'
    
    >>> a[start_idx:stop_idx-len(a)-1:-1]
    [2, 1]
    

Comparing execution times, the first version is faster:

>>> timeit.timeit('foo[stop_idx:start_idx+1][::-1]', setup='foo="012345"; stop_idx=0; start_idx=3', number=10_000_000)
1.7157553750148509
>>> timeit.timeit('foo[start_idx:stop_idx-len(foo)-1:-1]', setup='foo="012345"; stop_idx=0; start_idx=3', number=10_000_000)
1.9317215870250948
Answered By: Tom Hale
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.