This question already has answers here:
Indices of matching parentheses in Python
(5 answers)
Closed 6 years ago.
The Problem:
I am attempting to write some python code that searches through a list and returns the index of a matching bracket. For example:
array = ["(","foo",")","(","bar","(",")",")"]
f(0) => 2
f(1) => ERROR: Not a bracket.
f(2) => 0
f(3) => 7
My Feeble Attempts:
I tried looping through the list and finding the closest bracket, but then I realised it didn't work when you had loops inside loops (loopception). I have also tried adding a counter that adds one to the counter if it's a new bracket ( and takes one if it's a close bracket ), then checks to see if it equals -1, but that doesn't work.
Previous Code:
while True:
if(count == -1):
iterator = j+1
break
else:
j += 1
print j
if(commands[j] == "("):
count += 1
if(commands[j] == ")"):
count -= 1
Where iterator is the input and commands is the array
Assuming the array holds a correct sequence of opening/closing backets:
array = ["(","foo",")","(","bar","(",")",")"]
bracketPositions = []
for i, item in enumerate(array):
if i == 0 and item == ')':
print("Non sense ! Exit")
break
if item == '(':
bracketPositions.append(i)
elif item ==')':
if len(bracketPositions) > 0:
openingPosition = bracketPositions.pop()
print(openingPosition, '-->', i)
else:
print('ERROR: Not a bracket. Word is: %s.' % item)
Prints:
ERROR: Not a bracket (foo).
0 --> 2
ERROR: Not a bracket (bar).
5 --> 6
3 --> 7
With the counter variable you were on the right track, but it's difficult to say what exactly went wrong without seeing the entire code. Basically, what you have to do is: Determine in which direction to go and what to look out for. Initialize the number of matching parens to find as 1, then iterate through the array. If you find the original parens again, increment the counter, if you find the counterpart, decrement the counter. If the counter reaches zero, return the current position.
You can try something like this:
def match(array, pos):
try:
step = {"(": +1, ")": -1} [array[pos]] # go left or right?
other = {"(": ")", ")": "("}[array[pos]] # what to look for?
count = 1 # number of 'other' we have to find
cur = pos # current position
while True:
cur += step # go one step further
if array[cur] == array[pos]: # nested parens
count += 1
if array[cur] == other: # found match (but maybe for nested)
count -= 1
if count == 0: # found match for original parens
return cur
except KeyError:
# not a ( or ) or no match found
return None
array = ["(","foo",")","(","bar","(",")",")"]
print([match(array, i) for i, _ in enumerate(array)])
# [2, None, 0, 7, None, 6, 5, 3]
Related
I've got a long string. This string contains a list, like such example
'[{"ex1": 0, "ex2":1}, {"ex3": 2, "ex4":3}]'
I can use json5.loads and then get the first element by using [0] on the list, but json5.loads takes a long time for longer strings. Is there a way to get just the first element without loading the entire list? (in this example it would be {"ex1": 0, "ex2":1}. Splitting by commas doesn't work for me since there are commas contained in dictionaries in the list. Thanks.
Does your string work with ast.literal_eval()? If it does, you could do
obj = ast.literal_eval(s)
# obj[0] gives the first dict
If not, you could loop through the string character-by-character and yield any substring when the number of open-brackets are equal to the number of close-brackets.
def get_top_level_dict_str(s):
open_br = 0
close_br = 0
open_index = 0
for i, c in enumerate(s):
if c == '{':
if open_br == 0: open_index = i
open_br += 1
elif c == '}':
close_br += 1
if open_br > 0 and open_br == close_br:
yield s[open_index:i+1]
open_br = close_br = 0
If you want to parse the resulting substrings to objects, you could use json5 like you already do, which is probably faster on the smaller string, or use ast.literal_eval()
x = get_top_level_dict_str(s)
# next(x) gives the substring
# then use json5 or ast.literal_eval()
If it'll definitely be that format, you can just search for the beginning and ending brackets.
mystr = '[{"ex1": 0, "ex2":1}, {"ex3": 2, "ex4":3}]'
first = mystr.index("{")
last = mystr.index("}")
extracted = mystr[first:last+1]
print(extracted)
this prints
'{"ex1": 0, "ex2":1}'
For a more complicated string:
mystr = '[{"ex1": {"ex1.33": -1, "ex1.66": -2}, "ex2":1}, {"ex3": 2, "ex4":3}]'
n_open = 0
n_close = 0
first = mystr.index("{")
for ii in range(len(mystr)):
if mystr[ii] == "{":
n_open += 1
elif mystr[ii] == "}":
n_close += 1
if n_open > 0 and n_open == n_close:
break
extracted = mystr[first:ii+1]
find() don't work in my code.
I don't know why find() does not recognize letter.
following is my code.
def search(Object, maze_row, letter) :
Object[0] += 1
Object[1] = maze_row.find(letter)
def main() :
row_col = "5 5"
input_maze = "#####\n#..B#\n#.#.#\n#RO.#\n#####"
maze = input_maze.split('\n')
red_ball = [-1,-1]
blue_ball = [-1,-1]
hole = [-1,-1]
for maze_row in maze :
search(red_ball, maze_row,'R')
search(blue_ball, maze_row,'B')
search(hole, maze_row,'O')
if(red_ball[1] >= 0 and blue_ball[1] >= 0 and hole[1] >= 0) :
break
print(red_ball, blue_ball, hole)
if __name__ == '__main__' :
main()
result is ([4, -1], [4, -1], [4, -1])
find() is finding your letter.
However, in subsequent loops of for maze_row in maze it is setting back Object[1] to -1 due to the fact that the element can't be found in the next maze_row.
You can solve this in a variety of ways, with the most concise being to change your search function:
def search(Object, maze_row, letter) :
Object[0] += 1
if Object[1] == -1:
Object[1] = maze_row.find(letter)
Edit: Indentation
There's nothing wrong with find.
The problem is that your code keeps looping over the rows even after you find a match and set the correct index.
The only condition that would break from the loop is when the three letters R, B, O all occurs in the same row, which obviously is impossible.
So when you try to print the three variables, you would see the result for the last row, which contains none of your target letters.
It would be simpler if you create a function to search for the row and column of a letter in a maze:
def search(maze, target):
for i, row in maze:
j = row.find(target)
if j != -1:
return i, j
return -1, -1
red_ball = search(maze, 'R')
I'm solving this HackerRank challenge:
Alice has a binary string. She thinks a binary string is beautiful if and only if it doesn't contain the substring '010'.
In one step, Alice can change a 0 to a 1 or vice versa. Count and print the minimum number of steps needed to make Alice see the string as beautiful.
So basically count the number of '010' occurrences in the string 'b' passed to the function.
I want to increment i by 2 once the if statement is true so that I don't include overlapping '010' strings in my count.
And I do realize that I can just use the count method but I wanna know why my code isn't working the way I want to it to.
def beautifulBinaryString(b):
count = 0
for i in range(len(b)-2):
if b[i:i+3]=='010':
count+=1
i+=2
return count
Input: 0101010
Expected Output: 2
Output I get w/ this code: 3
You are counting overlapping sequences. For your input 0101010 you find 010 three times, but the middle 010 overlaps with the outer two 010 sequences:
0101010
--- ---
---
You can't increment i in a for loop, because the for loop construct sets i at the top. Giving i a different value inside the loop body doesn't change this.
Don't use a for loop; you could use a while loop:
def beautifulBinaryString(b):
count = 0
i = 0
while i < len(b) - 2:
if b[i:i+3]=='010':
count += 1
i += 2
i += 1
return count
A simpler solution is to just use b.count("010"), as you stated.
If you want to do it using a for loop, you can add a delta variable to keep track of the number of positions that you have to jump over the current i value.
def beautifulBinaryString(b):
count = 0
delta = 0
for i in range(len(b)-2):
try:
if b[i+delta:i+delta+3]=='010':
count+=1
delta=delta+2
except IndexError:
break
return count
You don't need to count the occurrences; as soon as you find one occurrence, the string is "ugly". If you never find one, it's beautiful.
def is_beautiful(b):
for i in range(len(b) - 2):
if b[i:i+3] == '010':
return False
return True
You can also avoid the slicing by simply keeping track of whether you've started to see 010:
seen_0 = False
seen_01 = False
for c in b:
if seen_01 and c == '0':
return False
elif seen_1 and c == '1':
seen_01 = True
elif c == '0':
seen_0 = True
else:
# c == 1, but it doesn't follow a 0
seen_0 = False
seen_01 = False
return True
I have tried plenty of different methods to achieve this, and I don't know what I'm doing wrong.
reps=[]
len_charac=0
def longest_charac(strng)
for i in range(len(strng)):
if strng[i] == strng[i+1]:
if strng[i] in reps:
reps.append(strng[i])
len_charac=len(reps)
return len_charac
Remember in Python counting loops and indexing strings aren't usually needed. There is also a builtin max function:
def longest(s):
maximum = count = 0
current = ''
for c in s:
if c == current:
count += 1
else:
count = 1
current = c
maximum = max(count,maximum)
return maximum
Output:
>>> longest('')
0
>>> longest('aab')
2
>>> longest('a')
1
>>> longest('abb')
2
>>> longest('aabccdddeffh')
3
>>> longest('aaabcaaddddefgh')
4
Simple solution:
def longest_substring(strng):
len_substring=0
longest=0
for i in range(len(strng)):
if i > 0:
if strng[i] != strng[i-1]:
len_substring = 0
len_substring += 1
if len_substring > longest:
longest = len_substring
return longest
Iterates through the characters in the string and checks against the previous one. If they are different then the count of repeating characters is reset to zero, then the count is incremented. If the current count beats the current record (stored in longest) then it becomes the new longest.
Compare two things and there is one relation between them:
'a' == 'a'
True
Compare three things, and there are two relations:
'a' == 'a' == 'b'
True False
Combine these ideas - repeatedly compare things with the things next to them, and the chain gets shorter each time:
'a' == 'a' == 'b'
True == False
False
It takes one reduction for the 'b' comparison to be False, because there was one 'b'; two reductions for the 'a' comparison to be False because there were two 'a'. Keep repeating until the relations are all all False, and that is how many consecutive equal characters there were.
def f(s):
repetitions = 0
while any(s):
repetitions += 1
s = [ s[i] and s[i] == s[i+1] for i in range(len(s)-1) ]
return repetitions
>>> f('aaabcaaddddefgh')
4
NB. matching characters at the start become True, only care about comparing the Trues with anything, and stop when all the Trues are gone and the list is all Falses.
It can also be squished into a recursive version, passing the depth in as an optional parameter:
def f(s, depth=1):
s = [ s[i] and s[i]==s[i+1] for i in range(len(s)-1) ]
return f(s, depth+1) if any(s) else depth
>>> f('aaabcaaddddefgh')
4
I stumbled on this while trying for something else, but it's quite pleasing.
You can use itertools.groupby to solve this pretty quickly, it will group characters together, and then you can sort the resulting list by length and get the last entry in the list as follows:
from itertools import groupby
print(sorted([list(g) for k, g in groupby('aaabcaaddddefgh')],key=len)[-1])
This should give you:
['d', 'd', 'd', 'd']
This works:
def longestRun(s):
if len(s) == 0: return 0
runs = ''.join('*' if x == y else ' ' for x,y in zip(s,s[1:]))
starStrings = runs.split()
if len(starStrings) == 0: return 1
return 1 + max(len(stars) for stars in starStrings)
Output:
>>> longestRun("aaabcaaddddefgh")
4
First off, Python is not my primary language, but I can still try to help.
1) you look like you are exceeding the bounds of the array. On the last iteration, you check the last character against the character beyond the last character. This normally leads to undefined behavior.
2) you start off with an empty reps[] array and compare every character to see if it's in it. Clearly, that check will fail every time and your append is within that if statement.
def longest_charac(string):
longest = 0
if string:
flag = string[0]
tmp_len = 0
for item in string:
if item == flag:
tmp_len += 1
else:
flag = item
tmp_len = 1
if tmp_len > longest:
longest = tmp_len
return longest
This is my solution. Maybe it will help you.
Just for context, here is a recursive approach that avoids dealing with loops:
def max_rep(prev, text, reps, rep=1):
"""Recursively consume all characters in text and find longest repetition.
Args
prev: string of previous character
text: string of remaining text
reps: list of ints of all reptitions observed
rep: int of current repetition observed
"""
if text == '': return max(reps)
if prev == text[0]:
rep += 1
else:
rep = 1
return max_rep(text[0], text[1:], reps + [rep], rep)
Tests:
>>> max_rep('', 'aaabcaaddddefgh', [])
4
>>> max_rep('', 'aaaaaabcaadddddefggghhhhhhh', [])
7
Suppose I have a string:
x = '[1.3].[1.2]'
How do I find the first index of "." that is not within the square brackets ([])?
So for the above example the first "." is at index 5, it is not at index 2 since at index 2 the "." is within the square brackets.
I tried doing x.index(".") but that only returns the index of the first "." and that "." can be within brackets.
I also tried doing x.index('].[') + 1 but that would fail for this example:
x = '[[1.3].[9.10]].[1.2.[4.[5.6]]]'
x.index('].[') + 1
6
Since the first "." that is not within brackets is at index 13
If anyone can help me out with this that would be really appreciated.
What this is is just you have two strings starting with '[' and ending with ']' and you connect them using '.', so
s1 = "[1.2]"
s2 = "[2.3]"
s1 + "." + s2
and basically I'm trying to get the index of the '.' after the strings are connected.
A simple “parser” for this:
def findRootIndexes (s):
nested = 0
for i, c in enumerate(s):
if c == '[':
nested += 1
elif c == ']':
nested -= 1
elif c == '.' and nested == 0:
yield i
>>> list(findRootIndexes('[1.3].[1.2]'))
[5]
>>> list(findRootIndexes('[[1.3].[9.10]].[1.2.[4.[5.6]]]'))
[14]
>>> list(findRootIndexes('[1.2].[3.4].[5.6]'))
[5, 11]
This is essentially a pushdown automaton except that we don’t need to track different tokens but just the opening and closing bracket. So we just need to count how many open levels we still have.
If you want to take it even further, you can—as roippi suggested in the comments—add some syntax checking to prevent things like [[1.2]]]. Or you could also add some additional checks to make sure that an opening [ is always preceded by a dot or another opening [. To do this, you could make it a one-look-behind parser. Something like this:
nested = 0
last = None
for i, c in enumerate(s):
if c == '[':
if last not in (None, '[', '.'):
raise SyntaxError('Opening bracket must follow either `[` or `.`')
nested += 1
elif c == ']'
if nested == 0:
raise SyntaxError('Closing bracket for non-open group')
nested -= 1
elif c == '.' and nested == 0:
yield i
last = c
But of course, if you create that string yourself from components you know that are valid, such checks are not really necessary.
In this solution we're counting the opening brackets. This is the easiest way I can imagine:
x = '[[1.3].[9.10]].[1.2.[4.[5.6]]]'
brackets = 0
pos = 0
for y in x:
if y == '[':
brackets += 1
elif y == ']':
brackets -=1
if brackets == 0:
print(pos) # Find first occurence and break from the loop
break
pos += 1
Prints 13