84

I got some simple code:

def find(str, ch):
    for ltr in str:
        if ltr == ch:
            return str.index(ltr)
find("ooottat", "o")

The function only return the first index. If I change return to print, it will print 0 0 0. Why is this and is there any way to get 0 1 2?

Paradox
  • 738
  • 12
  • 30
William Xing
  • 863
  • 1
  • 6
  • 5
  • 8
    Just so it is said, don't use str as a variable name, since it is an existing keyword in Python. – Silas Ray Jun 20 '12 at 15:00
  • 3
    @sr2222: `str` is not a keyword, it's just a builtin name (keywords are impossible to use as variable names) – Wooble Jun 20 '12 at 15:04
  • 5
    @Wooble Fair enough. Doesn't change the fact that using it as a variable name is bad practice. :) – Silas Ray Jun 20 '12 at 15:09

12 Answers12

136

This is because str.index(ch) will return the index where ch occurs the first time. Try:

def find(s, ch):
    return [i for i, ltr in enumerate(s) if ltr == ch]

This will return a list of all indexes you need.

P.S. Hugh's answer shows a generator function (it makes a difference if the list of indexes can get large). This function can also be adjusted by changing [] to ().

Lev Levitsky
  • 63,701
  • 20
  • 147
  • 175
26

I would go with Lev, but it's worth pointing out that if you end up with more complex searches that using re.finditer may be worth bearing in mind (but re's often cause more trouble than worth - but sometimes handy to know)

test = "ooottat"
[ (i.start(), i.end()) for i in re.finditer('o', test)]
# [(0, 1), (1, 2), (2, 3)]

[ (i.start(), i.end()) for i in re.finditer('o+', test)]
# [(0, 3)]
Jon Clements
  • 138,671
  • 33
  • 247
  • 280
19

Lev's answer is the one I'd use, however here's something based on your original code:

def find(str, ch):
    for i, ltr in enumerate(str):
        if ltr == ch:
            yield i

>>> list(find("ooottat", "o"))
[0, 1, 2]
Community
  • 1
  • 1
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
15
def find_offsets(haystack, needle):
    """
    Find the start of all (possibly-overlapping) instances of needle in haystack
    """
    offs = -1
    while True:
        offs = haystack.find(needle, offs+1)
        if offs == -1:
            break
        else:
            yield offs

for offs in find_offsets("ooottat", "o"):
    print offs

results in

0
1
2
Hugh Bothwell
  • 55,315
  • 8
  • 84
  • 99
4
def find_idx(str, ch):
    yield [i for i, c in enumerate(str) if c == ch]

for idx in find_idx('babak karchini is a beginner in python ', 'i'):
    print(idx)

output:

[11, 13, 15, 23, 29]
Babak Karchini
  • 155
  • 1
  • 8
4

Get all the position in just one line

word = 'Hello'
to_find = 'l'

# in one line
print([i for i, x in enumerate(word) if x == to_find])
Darkstar Dream
  • 1,649
  • 1
  • 12
  • 23
1

As the rule of thumb, NumPy arrays often outperform other solutions while working with POD, Plain Old Data. A string is an example of POD and a character too. To find all the indices of only one char in a string, NumPy ndarrays may be the fastest way:

def find1(str, ch):
  # 0.100 seconds for 1MB str 
  npbuf = np.frombuffer(str, dtype=np.uint8) # Reinterpret str as a char buffer
  return np.where(npbuf == ord(ch))          # Find indices with numpy

def find2(str, ch):
  # 0.920 seconds for 1MB str 
  return [i for i, c in enumerate(str) if c == ch] # Find indices with python
Anton K
  • 4,658
  • 2
  • 47
  • 60
0
x = "abcdabcdabcd"
print(x)
l = -1
while True:
    l = x.find("a", l+1)
    if l == -1:
        break
    print(l)
Azat Ibrakov
  • 9,998
  • 9
  • 38
  • 50
  • 4
    While this code may answer the question, providing information on how and why it solves the problem improves its long-term value. – L_J Aug 23 '18 at 09:56
  • @Arvind : Welcome to Stackoverflow. Would you mind to extend your answer for fellow programmers to understand; how exactly it helps to solve the problem. – Nagama Inamdar Aug 23 '18 at 10:25
0

This is slightly modified version of Mark Ransom's answer that works if ch could be more than one character in length.

def find(term, ch):
    """Find all places with ch in str
    """
    for i in range(len(term)):
        if term[i:i + len(ch)] == ch:
            yield i
dashesy
  • 2,596
  • 3
  • 45
  • 61
0

All the other answers have two main flaws:

  1. They do a Python loop through the string, which is horrifically slow, or
  2. They use numpy which is a pretty big additional dependency.
def findall(haystack, needle):
    idx = -1
    while True:
        idx = haystack.find(needle, idx+1)
        if idx == -1:
            break
        yield idx

This iterates through haystack looking for needle, always starting at where the previous iteration ended. It uses the builtin str.find which is much faster than iterating through haystack character-by-character. It doesn't require any new imports.

Jonathan
  • 1,864
  • 17
  • 26
0

To embellish the five-star one-liner posted by @Lev and @Darkstar:

word = 'Hello'
to_find = 'l'
print(", ".join([str(i) for i, x in enumerate(word) if x == to_find]))

This just makes the separation of index numbers more obvious.
Result will be: 2, 3

Dr. C.
  • 76
  • 6
-1

You could try this

def find(ch,string1):
    for i in range(len(string1)):
        if ch == string1[i]:
            pos.append(i)        
aizaz
  • 3,056
  • 9
  • 25
  • 57