0

A user types a string containing regular expressions like this one:

'I have the string "(.*)"'

or

'when user enters (\d+) times text "(.*)" truncate spaces'

I need to count each matching brackets' occurrence as user types, so above texts would return count one for the first text and 2 for the second. On the other hand a bracket without a matching one shouldn't be counted:

'I am in the middle of writing this ('

Also I would like to avoid counting nested brackets. As this code will be executed in certain circumstances on every keystroke in vim (it's a part of a snippet for UltiSnips, so when I create the snippet and enter given placeholder this count function should evaluate what I type on each new char) it needs to be fast ;)

To sum up requirements:

  1. Count bracket pairs
  2. Do not count bracket without a matching one
  3. Do not count nested brackets
  4. Count fast ;)

As requested - here is my initial effort to make this work: https://gist.github.com/3142334

It works, but unfortunately it counts inner brackets too, so I need to tweak it more.

Here is another solution that counts only outer brackets:

def fb(string, c=0):
    left_bracket = string.find("(")
    if left_bracket > -1:
        string = string[left_bracket + 1:]
        right_bracket = string.find(")")
        if right_bracket > -1:
            if string[:right_bracket].find("(") == -1:
                c += 1
            string = string[right_bracket + 1:]
        return fb(string, c)
    else:
        return c
pielgrzym
  • 1,535
  • 3
  • 17
  • 27
  • can you post your so far work? – tuxuday Jul 19 '12 at 08:34
  • Please add to your summary of requirements this: `0: Show what I've thought of or done so far` – mhawke Jul 19 '12 at 08:35
  • Right now I'm only experimenting in ipython console. I think this one will have to be recursive to catch the problem in a maintainable way. – pielgrzym Jul 19 '12 at 08:36
  • Usually you'd use something like a stack to do this sort of bracket pairing. Is state maintained between keystrokes in your vim environment, or do you need to (re)process the entire string on each keystroke? – mhawke Jul 19 '12 at 08:41
  • I need to reprocess the string each time unfortunately :) – pielgrzym Jul 19 '12 at 08:42
  • The nested bracket requirement cannot be satisfied just with regular expressions, you'll need some kind of parsing intelligence. A rude approximation can be something like `s.count("(") - min(0, s.count("(") - s.count(")"))` where `s` is the input string. – C2H5OH Jul 19 '12 at 08:42
  • Your requirement for ignoring incomplete brackets and nested makes it vague - what do you expect the result would be for `(()())`? 1 or 2? – zenpoy Jul 19 '12 at 09:42
  • @zenpoy 1 :) last solution fixes that :) – pielgrzym Jul 19 '12 at 12:57
  • But it seems that the solution will not give you the desired answer (2 or is it supposed to be 1? I still can't decide) for this example: `(()()` – zenpoy Jul 19 '12 at 13:24
  • @zenpoy good point - my treats this as 1, but it seems more sensible to treat this as two brackets - one containing incomplete bracket :) – pielgrzym Jul 19 '12 at 14:57
  • it depends on the sentence: `(-: This is me (smiling) today (at noon)` vs. `This is me (crying (today) :-( at noon)` – zenpoy Jul 19 '12 at 15:20
  • this function will work only on certain types of sentences - I don't suspect smileys in them - only regular expression brackets like: (\d+) or more complex ([-\w+]+). check out http://lettuce.it - these are the texts in @step() decorator :) for now the function seems to work (https://github.com/pielgrzym/vimrc/blob/master/UltiSnips/python.snippets#L57 - and definitions are in the top of file) :) – pielgrzym Jul 19 '12 at 17:35
  • There is already a similar question on the subject: https://stackoverflow.com/questions/546433/regular-expression-to-match-outer-brackets. Regular expressions are not the solution. – Emmanuel Jul 19 '12 at 08:55
  • I'm trying to count regular expressions occurences - I don't imply actually using regular expressions here (and it's clear to me they're a bad choice for this task). – pielgrzym Jul 19 '12 at 08:57
  • That's what I meant :-) this was just a summary of the best solution there ! – Emmanuel Jul 19 '12 at 09:32

3 Answers3

2

For this kinds of tasks using Stack ADT is useful. Whenever you see an opening bracket put it to the stack, and when you see the closing bracket pop it from the stack and increment the counter.

Asterisk
  • 3,436
  • 2
  • 32
  • 52
  • 2
    Actually one might also use a simple counter for the task, but the general idea is very helpfull :) thanks :) – pielgrzym Jul 19 '12 at 09:00
1

Here is one stack based solution. Only counting the braces from whole string when the input is some control character would be faster, but that is tricky because of text selections made before typing and other reasons. Also this isn't very pythonic, but it seems to work and it's relatively fast:

#!/usr/bin/env python

import re

def bracecounter(s):
    count = 0; open = 0; braces = []
    for c in s:
        if c in '()':
            braces.append(c)
            if c == '(':
                open += 1
            else:
                if ''.join(braces[-2:]) == '()':
                    braces = braces[:-2]
                    if open == 1:
                        count += 1
                    open -= 1
                else:
                    pass # closing brace without matching opening brace
    return count

fix = [
    (1, 'I have the string "(.*)"'),
    (2, 'when user enters (\d+) times text "(.*)" truncate spaces'),
    (0, 'I am in the middle of writing this ('),
    (1, ') Nested ((braces) will (not) count))))))).'),
    ]

def test():
    for exp, s in fix:
        res = bracecounter(s)
        assert exp == res, "Brace count %s != %s for '%s'" % (res, exp, s)

if __name__ == '__main__':
    test()
peterhil
  • 1,319
  • 1
  • 9
  • 14
1

I was solving a bracket counting homework problem and using a list as a stack was the way I figured it out, code below:

def bracket_match(text):

    stack = []
    pairs = 0

    #iterate through the string
    for letter in text:

        if letter == '(':
            stack.append(letter)

        elif letter == ')':

            if len(stack) == 0:
                pass
            else:
                stack.pop()
                pairs += 1

    return pairs

bracket_match('())(')