5

Let's say I have a string like:

'abcdefgha'

I'd like to find the index of the next character a after the index 2 (in a circular manner). Meaning it should find index 7 in this case (via mystr.index('a', 2)); however, in this case:

'abcdefgh'

it should return index 0. Is there any such built-in function?

wjandrea
  • 28,235
  • 9
  • 60
  • 81
user129393192
  • 797
  • 1
  • 8

3 Answers3

5

There isn't a builtin for this, but you can easily write a function:

def index_circular(s: str, sub: str, n: int) -> int:
    try:
        # Search starting from n
        return s.index(sub, n)
    except ValueError:
        # Wrap around and search from the start, until n
        return s.index(sub, 0, n+len(sub)-1)

In use:

>>> n = 2
>>> c = 'a'
>>> index_circular('abcdefgha', c, n)
8
>>> index_circular('abcdefgh', c, n)
0
>>> index_circular('bcdefgh', c, n)
Traceback (most recent call last):
    ...
ValueError: substring not found

(Note that 'a' actually occurs at index 8, not 7, in the first case.)

Note: In the second s.index call, I'm setting the end parameter in order to avoid searching parts of the string that have already been searched. This is a bit of a premature optimization, but it's also a bit of a clarification about exactly which parts of the string are being searched in each step. The +len(sub)-1 is to allow for multi-character sub that spans index n, like:

>>> index_circular('abc', 'ab', 1)
0
wjandrea
  • 28,235
  • 9
  • 60
  • 81
  • Is this typical Python style? For a function to throw an exception instead of returning an error (perhaps only returning positive indices upon success and `-1` when it can't find it)? – user129393192 Aug 13 '23 at 20:36
  • 1
    @user129393192 You yourself mentioned index() instead of find(), suggesting you prefer an exception. – Kelly Bundy Aug 13 '23 at 20:38
  • I think the simple `s.index(sub)` works @wjandrea – user129393192 Aug 13 '23 at 20:50
  • 1
    @user129393192 I edited to clarify why I'm setting the `end` parameter. – wjandrea Aug 13 '23 at 21:05
  • 1
    @user129393192 Yes, this is typical. One way to put it is, ["if your method can't do what its name says it does, throw."](/a/1153149/4518341) It's also related to [EAFP](//docs.python.org/3/glossary.html#term-EAFP). – wjandrea Aug 13 '23 at 21:17
  • 1
    The multi-char support seems broken/misleading, as it fails on `index_circular('abc', 'ca', 1)`. – Kelly Bundy Aug 13 '23 at 21:58
  • 1
    @KellyBundy It only looks for substrings that exist in the original string. The question is only about single characters so I didn't bother thinking too hard about it. – wjandrea Aug 13 '23 at 22:28
3

Python has no built-in function for this circular index lookup, so you'll need to implement one yourself.

Custom implementation

One way to do circular lookup is to leverage the str.index combined with a modular approach to "wrap around" the string.

Here's the complete example.

def circular_index(s, char, start):
    try:
        # Try finding the character after the start index.
        return s.index(char, start)
    except ValueError:
        # If not found, try finding from the beginning
        # This will raise ValueError if not found again
        return s.index(char)

# Test
s1 = 'abcdefgha'
s2 = 'abcdefgh'

print(circular_index(s1, 'a', 2))  # prints 8
print(circular_index(s2, 'a', 2))  # prints 0
miloserdow
  • 1,051
  • 1
  • 7
  • 27
  • What's the point of a default value of `0` for `start`? In that case if the substring isn't found, you'll end up doing `s.index` twice. The caller should just use `s.index` in the first place in that case, no? – wjandrea Aug 13 '23 at 20:49
  • 1
    @wjandrea yeah, you're right, I'll edit this out – miloserdow Aug 13 '23 at 20:53
  • @wjandrea I think even better would be (for both of you) to handle that case within the new function. – Lover of Structure Aug 13 '23 at 21:26
  • 1
    @LoverofStructure If I were writing for a library then I might, but for this question, it's not super relevant. Like, I'd be willing to assume the caller is setting `start != 0` otherwise they'd just use `str.index` in the first place. – wjandrea Aug 13 '23 at 21:33
-1

You can try find() or rfind():

def cir_idx(string, sub, n):
    res = string.find(sub, n)
    if res == -1:
        res = string.find(sub)
    return res

print(cir_idx('abcdefgh', 'a', 2)) # 0
print(cir_idx('abcdefgha', 'a', 2)) # 8
print(cir_idx('abcdefghaa', 'a', 2)) # 8
print(cir_idx('aabcdefgh', 'a', 2)) # 0
print(cir_idx('aaabcdefgh', 'a', 2)) # 2
print(cir_idx('bcdefgh', 'a', 2)) # -1
Arifa Chan
  • 947
  • 2
  • 6
  • 23