Capturing multiple optional groups in a regex both repeating and non repeating - python

I have to match an expression similar to these
STAR 13
STAR 13, 23
STAR 1, 2 and 3 and STAR 1
But only capture the digits.
The number of digits is unspecified.
I've tried with STAR(?:\s*(?:,|and)\s*(#\d+))+
But it doesn't seem to capture the terms exactly.
No other dependencies could be added. Just the re module only.
The problem is a much larger one where STAR is another regular expression which has already been solved. Please don't bother about it and just consider it as a letter combination. Just include the letters STAR in regular expressions.

If you don't know the number of the digit r'[0-9]+' to specifie 1 digit or more. And to capture all number, you can use : r'(\d+)'
Do it with one regex:
re.findall("STAR ([0-9]+),? ?([0-9]+)? ?a?n?d? ?([0-9]+)?",a)
[('13', '', '')]
[('13', '23', '')]
[('1', '2', '3'), ('1', '', '')]
May be esaier and cleaner resultut with two step, first you need to have variable in a list like that:
tab = ["STAR 13","STAR 13, 23","STAR 1, 2 and 3 and STAR 1"]
list = filter(lambda x: re.match("^STAR",x),tab)
list_star = filter(lambda x: re.match("^STAR",x),tab)
for i in list_star:
re.findall(r'\d+', i)
['13']
['13', '23']
['1', '2', '3', '1']
You just need to put it in a new list after that my_digit += re.findall(r'\d+', i)
In 1 line:
import functools
tab = ["STAR 13","STAR 13, 23","STAR 1, 2 and 3 and STAR 1"]
digit=functools.reduce(lambda x,y: x+re.findall("\d+",y),filter(lambda x: re.match("^STAR ",x),tab),[])
['13', '13', '23', '1', '2', '3', '1']

Related

python regular expression optional but mandatory if character precedes

I am trying to capture something along the lines of
1/2x1 + 3x2 - 4/5x3
I will strip the spaces before hand so it is not necessary to capture them in the regular expression. The concern that's happening is that I want the preceding coefficient to have the option of being a fraction. So if I see a / then it must have \d+ following it. I don't necessarily care to capture the /.
Ideally I would extract the groups as such:
# first match
match.groups(1)
('1', '2', 'x1')
#second match
('+', '3', 'x2')
#third match
('-', '4', '5', 'x3')
Something that is (sort of) working is ([+-])?(\d)+(\/\d)?([a-zA-Z]+\d+). However I don't love that it also captures the preceding '/'
Example output:
>>> regexp = re.compile('([+-])?(\d)+(\/\d)?([a-zA-Z]+\d+)')
>>> expr = '1/2a3+1/8x2-4x3'
>>> match = regexp.search(expr)
>>> match.groups(1)
(1, '1', '/2', 'a3')
>>> expr = expr.replace(match.group(0), '')
>>> match = regexp.search(expr)
>>> match.groups(1)
('+', '1', '/8', 'x2')
>>> expr = expr.replace(match.group(0), '')
>>> match = regexp.search(expr)
>>> match.groups(1)
('-', '4', 1, 'x3')
In the first match, what does the first element 1 mean? I see the same thing in the third match, third element. In both of these - that particular "group" is missing. So is that just a way of being like "I matched, but I didn't match anything"?
Another issue with the above regex, is it makes the [+-] optional. I want it to be optional on the first term, but it is mandatory on subsequent terms.
Anyways the above is usable, I'll need to peel off the /, and I can sanitize the input to ensure the +- are always there, but it's not as elegant as I'm sure it can be.
Thanks for any help
You could rework your regex slightly to use capturing groups only for things you want to capture and then use re.findall to extract all matches at once:
regexp = re.compile(r'([+-])?(\d+)(?:/(\d))?([a-zA-Z]+\d+)')
res = regexp.findall(expr)
Output:
[
('', '1', '2', 'a3'),
('+', '1', '8', 'x2'),
('-', '4', '', 'x3')
]
Note when there is no fraction (or sign on the first value) there may be empty values ('') in the tuple, you could (if required) filter that out e.g.
[tuple(filter(lambda x:x, tup)) for tup in res]
# [('1', '2', 'a3'), ('+', '1', '8', 'x2'), ('-', '4', 'x3')]
however then you would face the difficulty of knowing which value in each tuple corresponded to which part of the expression.

Regex Ignore Match in the middle

I have string that I want to parse.
The digit (0-99) are appended at the end of the string with '_' delimiter.
The same pattern can also be in the middle of the string with the same delimiter.
For example,
#1 ..._ADDXNT_5_6_7_8_9_10_11_12_1
#2 ...X_VSVFT_0_5_ADL_R_
This is the regex I have
((?<=_)\d{1,2})
It works for #1 and parses it out ['5', '6', '7', '8', '9', '10', '11', '12', '1']
However, it also parses ['0', '5'] which I don't want.
For #2, it shouldn't match.
How can get it to only parse the end appended digits and not in the middle?
for i in ['_ADTCNT_5_6_7_8_9_10_11_12_1', 'X_VDDFEFSET_0_5_ALL_C_']:
match = re.findall(r"(?<=)\d{1,2}", i)
print(match)
You may use this regex with a positive lookahead:
(?<=_)\d{1,2}(?=(?:_\d{1,2})*$)
RegEx Demo
RegEx Details:
(?<=_): Positive lookbehind to assert that we have a _` at previous position
\d{1,2}: Match 1 or 2 digits
(?=(?:_\d{1,2})*$): Positive lookahead to assert that we have 0 or more of _<digits> strings ahead till end
i think this will work :
(?<=_)\d{1,2}(?!.*[A-Za-z])
and this (?!.*[A-Za-z]) mean not followed by alphabetic characters
Another option is to repeatedly match all underscores followed by 1+ digits till the end of the string, and then split on _
(?:_\d{1,2})+$
Regex demo | Python demo
import re
for i in ['_ADTCNT_5_6_7_8_9_10_11_12_1', 'X_VDDFEFSET_0_5_ALL_C_']:
result = re.search(r"(?:_\d{1,2})+$", i)
if result:
print([x for x in result.group().split("_") if x])
Output
['5', '6', '7', '8', '9', '10', '11', '12', '1']

Regex for split or findall each digit python

What is the best solution to split this str var into a continuous number list
My solution :
>>> str
> '2223334441214844'
>>> filter(None, re.split("(0+)|(1+)|(2+)|(3+)|(4+)|(5+)|(6+)|(7+)|(8+)|(9+)", str))
> ['222', '333', '444', '1', '2', '1', '4', '8', '44']
The more flexible way would be to use itertools.groupby which is made to match consecutive groups in iterables:
>>> s = '2223334441214844'
>>> import itertools
>>> [''.join(group) for key, group in itertools.groupby(s)]
['222', '333', '444', '1', '2', '1', '4', '8', '44']
The key would be the single key that is being grouped on (in your case, the digit). And the group is an iterable of all the items in the group. Since the source iterable is a string, each item is a character, so in order to get back the fully combined group, we need to join the characters back together.
You could also repeat the key for the length of the group to get this output:
>>> [key * len(list(group)) for key, group in itertools.groupby(s)]
['222', '333', '444', '1', '2', '1', '4', '8', '44']
If you wanted to use regular expressions, you could make use of backreferences to find consecutive characters without having to specify them explicitly:
>>> re.findall('((.)\\2*)', s)
[('222', '2'), ('333', '3'), ('444', '4'), ('1', '1'), ('2', '2'), ('1', '1'), ('4', '4'), ('8', '8'), ('44', '4')]
For finding consecutive characters in a string, this is essentially the same that groupby will do. You can then filter out the combined match to get the desired result:
>>> [x for x, *_ in re.findall('((.)\\2*)', s)]
['222', '333', '444', '1', '2', '1', '4', '8', '44']
One solution without regex (that is not specific to digits) would be to use itertools.groupby():
>>> from itertools import groupby
>>> s = '2223334441214844'
>>> [''.join(g) for _, g in groupby(s)]
['222', '333', '444', '1', '2', '1', '4', '8', '44']
If you only need to extract consecutive identical digits, you may use a matching approach using r'(\d)\1*' regex:
import re
s='2223334441214844'
print([x.group() for x in re.finditer(r'(\d)\1*', s)])
# => ['222', '333', '444', '1', '2', '1', '4', '8', '44']
See the Python demo
Here,
(\d) - matches and captures into Group 1 any digit
\1* - a backreference to Group 1 matching the same value, 0+ repetitions.
This solution can be customized to match any specific consecutive chars (instead of \d, you may use \S - non-whitespace, \w - word, [a-fA-F] - a specific set, etc.). If you replace \d with . and use re.DOTALL modifier, it will work as the itertools solutions posted above.
Use a capture group and backreference.
str = '2223334441214844'
import re
print([i[0] for i in re.findall(r'((\d)\2*)', str)])
\2 matches whatever the (\d) capture group matched. The list comprehension is needed because when the RE contains capture groups, findall returns a list of the capture groups, not the whole match. So we need an extra group to get the whole match, and then need to extract that group from the result.
What about without importing any external module ?
You can create your own logic in pure python without importing any module Here is recursive approach,
string_1='2223334441214844'
list_2=[i for i in string_1]
def con(list_1):
group = []
if not list_1:
return 0
else:
track=list_1[0]
for j,i in enumerate(list_1):
if i==track[0]:
group.append(i)
else:
print(group)
return con(list_1[j:])
return group
print(con(list_2))
output:
['2', '2', '2']
['3', '3', '3']
['4', '4', '4']
['1']
['2']
['1']
['4']
['8']
['4', '4']

Finding overlapping sequence with regular expressions with Python

I'm trying to extract numbers and both previous and following characters (excluding digits and whitespaces) of a string. The expected return of the function is a list of tuples, with each tuple having the shape:
(previous_sequence, number, next_sequence)
For example:
string = '200gr T34S'
my_func(string)
>>[('', '200', 'gr'), ('T', '34', 'S')]
My first iteration was to use:
def my_func(string):
res_obj = re.findall(r'([^\d\s]+)?(\d+)([^\d\s]+)?', string)
But this function doesn't do what I expect when I pass a string like '2AB3' I would like to output [('','2','AB'), ('AB','3','')] and instead, it is showing [('','2','AB'), ('','3','')], because 'AB' is part of the previous output.
How could I fix this?
Since there is no overlapping numbers, a single trailing
assertion should be all you need.
Something like ([^\d\s]+)?(\d+)(?=([^\d\s]+)?)
This ([^\d\s]*)(\d+)(?=([^\d\s]*)) if you care about
the difference between NULL and the empty string.
Instead of modifier + and ? you can simply use * :
>>> re.findall(r'([^\d\s]*)(\d+)([^\d\s]*)',string)
[('', '200', 'gr'), ('T', '34', 'S')]
But if you mean to match the overlapped strings you can use a positive look ahead to fine all the overlapped matches :
>>> re.findall(r'(?=([^\d\s]*)(\d+)([^\d\s]*))','2AB3')
[('', '2', 'AB'), ('AB', '3', ''), ('B', '3', ''), ('', '3', '')]
Another way can be using regex and functions!
import re
#'200gr T34S' '2AB3'
def s(x):
tmp=[]
d = re.split(r'\s+|(\d+)',x)
d = ['' if v is None else v for v in d] #remove None
t_ = [i for i in d if len(i)>0]
digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
nms = [i for i in t_ if i[0] in digits]
for i in nms:
if d.index(i)==0:
tmp.append(('',i,d[d.index(i)+1]))
elif d.index(i)==len(d):
tmp.append((d[d.index(i)-1],i,''))
else:
tmp.append((d[d.index(i)-1],i,d[d.index(i)+1]))
return tmp
print s('2AB3')
Prints-
[('', '2', 'AB'), ('AB', '3', '')]

python split string number in digits

Hi I would like to split the following string "1234" in ['1', '2', '3', '4'] in python.
My current approach is using re module
import re
re.compile('(\d)').split("1234")
['', '1', '', '2', '', '3', '', '4', '']
But i get some extra empty strings. I am not an expert in regular expressions, what could be a proper regular expression in python to accomplish my task?
Please give me some advices.
Simply use list function, like this
>>> list("1234")
['1', '2', '3', '4']
The list function iterates the string, and creates a new list with all the characters in it.
Strings are by default character lists:
>>> nums = "1234"
>>> for i in nums:
... print i
...
1
2
3
4
>>> nums[:-1]
'123'

Categories

Resources