I'm trying to extract a substring from a string in Python.
The front end to be trimmed is static and easy to implement, but the rear end has a counter that can run from "_0" to "_9999".
With my current code, the counter still gets included in the substring.
import re
text = "fastq_runid_0dc971f49c42ffb1412caee485f8421a1f9a26ed_0.fastq"
print(text)
substring= re.search('runid_(.*)_*.fas', text).group(0)
print(substring)
Returns
0dc971f49c42ffb1412caee485f8421a1f9a26ed_0.fas
Alternatively,
substring= re.search(r"(?<=runid_).*?(?=_*.fastq)", text).group(0)
returns
0dc971f49c42ffb1412caee485f8421a1f9a26ed_0
Works better but the counter "_0" is still added.
How do I make a robust trim that trims the multi-character counter?
In your regex (?<=runid_).*?(?=_*.fastq) there is a little problem. You have written _* which means zero or more underscores which will make underscore optional and will skip it matching and your .*? will eat _0 too within it which is why in your result you get _0 too. I think you meant _.* and also you should escape the . just before fastq so your updated regex should become this,
(?<=runid_).+(?=_\d{1,4}\.fas)
Demo
Your updated python code,
import re
text = "fastq_runid_0dc971f49c42ffb1412caee485f8421a1f9a26ed_0.fastq"
print(text)
substring= re.search('(?<=runid_).+(?=_\d{1,4}\.fas)', text).group(0)
print(substring)
Prints,
0dc971f49c42ffb1412caee485f8421a1f9a26ed
Also, alternatively, you can use a simple regex without lookarounds and capture the text from first group using this regex,
runid_([^_]+)(?=_\d{1,4}\.fas)
Demo
Your python code with text picking from group(1) instead of group(0)
import re
text = "fastq_runid_0dc971f49c42ffb1412caee485f8421a1f9a26ed_0.fastq"
print(text)
substring= re.search('runid_([^_]+)(?=_\d{1,4}\.fas)', text).group(1)
print(substring)
In this case too it prints,
0dc971f49c42ffb1412caee485f8421a1f9a26ed
You don't need look behind and look ahead to achieve that.
\d{1,4} means min 1 max 4 digits, otherwise it wont match
fastq_runid_(.+)_\d{1,4}\.fastq
https://regex101.com/r/VneElM/1
import re
text = "fastq_runid_0dc971f49c42ffb1412caee485f8421a1f9a26ed_999.fastq"
print(text)
substring= re.search('fastq_runid_(\w+)_(\d+)\.fastq', text)
print(substring.group(1), substring.group(2))
group(1) will give what you want, group(2) will give the counter.
Related
I want to substitute from 'datasets/4/image-3.jpg' to 'datasets/4/image-1.jpg'. Are there any ways to do it by using re.sub? Or should I try something else like .split("/")[-1]? I had tried below but end up getting 'datasets/1/image-1.jpg', but I want to keep the /4/ instead of /1/.
My Code
import re
employee_name = 'datasets/4/image-3.jpg'
label = re.sub('[0-9]', "1", employee_name)
print(label)
Output
datasets/1/image-1.jpg
Expected Input
datasets/4/image-3.jpg
Expected Output
datasets/4/image-1.jpg
You can use
re.sub(r'-\d+\.jpg', '-1.jpg', text)
Note: If the match is always at the end of string, append the $ anchor at the end of the regex. If there can be other text after and you need to make sure there are no word chars after jpg, add a word boundary, \b. If you want to match other extensions, use a group, e.g. (?:jpe?g|png).
Regex details
-\d+ - one or more digits
\.jpg - .jpg string.
See the regex demo (code generator link).
Using the python re.sub, is there a way I can extract the first alpha numeric characters and disregard the rest form a string that starts with a special character and might have special characters in the middle of the string? For example:
re.sub('[^A-Za-z0-9]','', '#my,name')
How do I just get "my"?
re.sub('[^A-Za-z0-9]','', '#my')
Here I would also want it to just return 'my'.
re.sub(".*?([A-Za-z0-9]+).*", r"\1", str)
The \1 in the replacement is equivalent to matchobj.group(1). In other words it replaces the whole string with just what was matched by the part of the regexp inside the brackets. $ could be added at the end of the regexp for clarity, but it is not necessary because the final .* will be greedy (match as many characters as possible).
This solution does suffer from the problem that if the string doesn't match (which would happen if it contains no alphanumeric characters), then it will simply return the original string. It might be better to attempt a match, then test whether it actually matches, and handle separately the case that it doesn't. Such a solution might look like:
matchobj = re.match(".*?([A-Za-z0-9]+).*", str)
if matchobj:
print(matchobj.group(1))
else:
print("did not match")
But the question called for the use of re.sub.
Instead of re.sub it is easier to do matching using re.search or re.findall.
Using re.search:
>>> s = '#my,name'
>>> res = re.search(r'[a-zA-Z\d]+', s)
>>> if res:
... print (res.group())
...
my
Code Demo
This is not a complete answer. [A-Za-z]+ will give give you ['my','name']
Use this to further explore: https://regex101.com/
This question showed how to replace a regex with another regex like this
$string = '"SIP/1037-00000014","SIP/CL-00000015","Dial","SIP/CL/61436523277,45"';
$$pattern = '["SIP/CL/(\d*),(\d*)",]';
$replacement = '"SIP/CL/\1|\2",';
$string = preg_replace($pattern, $replacement, $string);
print($string);
However, I couldn't adapt that pattern to solve my case where I want to remove the full stop that lies between 2 words but not between a word and a number:
text = 'this . is bad. Not . 820'
regex1 = r'(\w+)(\s\.\s)(\D+)'
regex2 = r'(\w+)(\s)(\D+)'
re.sub(regex1, regex2, text)
# Desired outcome:
'this is bad. Not . 820'
Basically I like to remove the . between the two alphabet words. Could someone please help me with this problem? Thank you in advance.
These expressions might be close to what you might have in mind:
\s[.](?=\s\D)
or
(?<=\s)[.](?=\s\D)
Test
import re
regex = r"\s[.](?=\s\D)"
test_str = "this . is bad. Not . 820"
print(re.sub(regex, "", test_str))
Output
this is bad. Not . 820
If you wish to explore/simplify/modify the expression, it's been
explained on the top right panel of
regex101.com. If you'd like, you
can also watch in this
link, how it would match
against some sample inputs.
Firstly, you can't really take PHP and apply it directly to Python, for obvious reasons.
Secondly, it always helps to specify which version of Python you're using as APIs change. Luckily in this instance, the API of re.sub has remained the same between Python 2.x and Python 3.
Onto your issue.
The second argument to re.sub is either a string or a function. If you pass in regex2 it'll just replace regex1 with the string contents of regex2, it won't apply regex2 as a regex.
If you want to use groups derived from the first regex (similar to your example, which is using \1 and \2 to extract the first and second matching group from the first regex), then you'd want to use a function, which takes a match object as its sole argument, which you could then use to extract matching groups and return them as part of the replacement string.
I have a string with some markup which I'm trying to parse, generally formatted like this.
'[*]\r\n[list][*][*][/list][*]text[list][*][/list]'
I want to match the asterisks within the [list] tags so I can re.sub them as [**] but I'm having trouble forming an expression to grab them. So far, I have:
match = re.compile('\[list\].+?\[/list\]', re.DOTALL)
This gets everything within the list, but I can't figure out a way to narrow it down to the asterisks alone. Any advice would be massively appreciated.
You may use a re.sub and use a lambda in the replacement part. You pass the match to the lambda and use a mere .replace('*','**') on the match value.
Here is the sample code:
import re
s = '[*]\r\n[list][*][*][/list][*]text[list][*][/list]'
match = re.compile('\[list].+?\[/list]', re.DOTALL)
print(match.sub(lambda m: m.group().replace('*', '**'), s))
# = > [*]
# [list][**][**][/list][*]text[list][**][/list]
See the IDEONE demo
Note that a ] outside of a character class does not have to be escaped in Python re regex.
I need to build a program that can read multiple lines of code, and extract the right information from each line.
Example text:
no matches
one match <'found'>
<'one'> match <found>
<'three'><'matches'><'found'>
For this case, the program should detect <'found'>, <'one'>, <'three'>, <'matches'> and <'found'> as matches because they all have "<" and "'".
However, I cannot work out a system using regex to account for multiple matches on the same line. I was using something like:
re.search('^<.*>$')
But if there are multiple matches on one line, the extra "'<" and ">'" are taken as part of the .*, without counting them as separate matches. How do I fix this?
This works -
>>> r = re.compile(r"\<\'.*?\'\>")
>>> r.findall(s)
["<'found'>", "<'one'>", "<'three'>", "<'matches'>", "<'found'>"]
Use findall instead of search:
re.findall( r"<'.*?'>", str )
You can use re.findall and match on non > characters inside of the angle brackets:
>>> re.findall('<[^>]*>', "<'three'><'matches'><'found'>")
["<'three'>", "<'matches'>", "<'found'>"]
Non-greedy quantifier '?' as suggested by anubhava is also an option.