Python regex - get contents in between - python

I have a word/text file containing,
1. 10 Liter sample of an ideal gas is expanded reversibly and isothermally at 300k from initial pressure of 10atm to final pressure of 1atm. The heat absorbed by gas during the process is approximately.
(A)15kJ
(B)23kJ
(C)32kJ
(D)50kJ
[Answer]:(B)
[QuestionType]:single_correct
2. Which of the following statement is correct
(A)Li is hander than the other alkali metals.
(B)In solvay process NH3 is recovered when the solution containing NH4Cl is treated with H2O.
(C)Na2CO3 is pearl ash.
(D)Berylium and Aluminium ions do not have strong tendency to form complexes like
[Answer]:(C)
[QuestionType]:single_correct
I need to get each question in a separate list starting from question number to [QuestionType].
( 1. to [QuestionType])
Output :
[[1. 10 Liter sample of an ideal gas is expanded reversibly and isothermally at 300k from initial pressure of 10atm to final pressure of 1atm. The heat absorbed by gas during the process is approximately.,(A)15kJ,(B)23kJ,(C)32kJ,(D)50kJ,[Answer]:(B),[QuestionType]:single_correct],
[2. Which of the following statement is correct,(A)Li is hander than the other alkali metals.,(B)In solvay process NH3 is recovered when the solution containing NH4Cl is treated with H2O.,(C)Na2CO3 is pearl ash.,(D)Berylium and Aluminium ions do not have strong tendency to form complexes like ,[Answer]:(C),[QuestionType]:single_correct]]
I tried in for loop but cant able to get contents in between
import docx
import re
doc = docx.Document("QnA.docx")
for i in doc.paragraphs:
if re.match(r"^[0-9]+[.]+",i.text):
print(i.text) # matched number condition
if re.match(r"(^\[QuestionType\])",i.text):
print(i.text) # matched QuestionType condition

You might use a single pattern, starting the match with 1 or more digits and a dot.
Then continue matching all the lines that do not start with [QuestionType] and finally match that line.
^\d+\..*(?:\r?\n(?!\[QuestionType]).*)*\r?\n\[QuestionType]:.*
See a regex demo and a Python demo
For example
import re
regex = r"^\d+\..*(?:\r?\n(?!\[QuestionType]).*)*\r?\n\[QuestionType]:.*"
s = ("1. 10 Liter sample of an ideal gas is expanded reversibly and isothermally at 300k from initial pressure of 10atm to final pressure of 1atm. The heat absorbed by gas during the process is approximately.\n"
"(A)15kJ\n"
"(B)23kJ\n"
"(C)32kJ\n"
"(D)50kJ\n\n"
"[Answer]:(B)\n\n"
"[QuestionType]:single_correct\n\n"
"2. Which of the following statement is correct\n\n"
"(A)Li is hander than the other alkali metals.\n"
"(B)In solvay process NH3 is recovered when the solution containing NH4Cl is treated with H2O.\n"
"(C)Na2CO3 is pearl ash.\n"
"(D)Berylium and Aluminium ions do not have strong tendency to form complexes like \n\n"
"[Answer]:(C)\n\n"
"[QuestionType]:single_correct")
print(re.findall(regex, s, re.M))
Output
['1. 10 Liter sample of an ideal gas is expanded reversibly and isothermally at 300k from initial pressure of 10atm to final pressure of 1atm. The heat absorbed by gas during the process is approximately.\n(A)15kJ\n(B)23kJ\n(C)32kJ\n(D)50kJ\n\n[Answer]:(B)\n\n[QuestionType]:single_correct', '2. Which of the following statement is correct\n\n(A)Li is hander than the other alkali metals.\n(B)In solvay process NH3 is recovered when the solution containing NH4Cl is treated with H2O.\n(C)Na2CO3 is pearl ash.\n(D)Berylium and Aluminium ions do not have strong tendency to form complexes like \n\n[Answer]:(C)\n\n[QuestionType]:single_correct']

First, you get content of each question using regex. After, you split \n for content of each question.
You could try following regex.
\d+\.[\s\S]+?QuestionType.*
I also try to test on python.
import re
content = '''1. 10 Liter sample of an ideal gas is expanded reversibly and isothermally at 300k from initial pressure of 10atm to final pressure of 1atm. The heat absorbed by gas during the process is approximately.
(A)15kJ
(B)23kJ
(C)32kJ
(D)50kJ
[Answer]:(B)
[QuestionType]:single_correct
2. Which of the following statement is correct
(A)Li is hander than the other alkali metals.
(B)In solvay process NH3 is recovered when the solution containing NH4Cl is treated with H2O.
(C)Na2CO3 is pearl ash.
(D)Berylium and Aluminium ions do not have strong tendency to form complexes like
[Answer]:(C)
[QuestionType]:single_correct
'''
splitQuestion = re.findall(r"\d+\.[\s\S]+?QuestionType.*", content)
result = [];
for eachQuestion in splitQuestion:
result.append(eachQuestion.split("\n"))
print(result)
Result.
[['1. 10 Liter sample of an ideal gas is expanded reversibly and isothermally at 300k from initial pressure of 10atm to final pressure of 1atm. The heat absorbed by gas during the process is approximately.', '(A)15kJ', '(B)23kJ', '(C)32kJ', '(D)50kJ', '', '[Answer]:(B)', '', '[QuestionType]:single_correct'], ['2. Which of the following statement is correct', '', '(A)Li is hander than the other alkali metals.', '(B)In solvay process NH3 is recovered when the solution containing NH4Cl is treated with H2O.', '(C)Na2CO3 is pearl ash.', '(D)Berylium and Aluminium ions do not have strong tendency to form complexes like ', '', '[Answer]:(C)', '', '[QuestionType]:single_correct']]

Related

How to reduce computational cost with Regex function

I am trying to use regex to extract sentences containing specific words, and sentences before and next to them. My code works, but it takes 20 seconds for each txt and I have about a million txt files. Is it possible to get the same result in less time? Any other relalted suggestions are also welcome. Thanks!
My current thought is to extract paragraphs containing these target words first, then use nltk to tokenize target paragraphs and extract the target sentences and sentences before and next them.
Here is my demoļ¼š
import re, nltk
txt = '''There is widespread agreement that screening for breast cancer, when combined with appropriate follow-up, will reduce mortality from the disease. How we measure response to treatment is called the 5-year survival rate, or the percentage of people who live 5 years after being diagnosed with cancer. According to information provided by National Cancer Institute, Cancer stage at diagnosis, which refers to extent of a cancer in the body, determines treatment options and has a strong influence on the length of survival. In general, if the cancer is found only in the part of the body where it started it is localized (sometimes referred to as stage 1). If it has spread to a different part of the body, the stage is regional or distant . The earlier female breast cancer is caught, the better chance a person has of surviving five years after being diagnosed. For female breast cancer, 62.5 are diagnosed at the local stage. The 5-year survival for localized female breast cancer is 98.8 . It decreases from 98.8 to 85.5 after the cancer has spread to the lymph nodes (stage 2), and to 27.4
(stage 4) after it has spread to other organs such as the lung, liver or brain. A major problem with current detection methods is that studies have shown that mammography does not detect 10 -20 of breast cancers that are detected by physical exam alone, which may be attributed to a falsely negative mammogram.
Breast cancer screening is generally recommended as a routine part of preventive healthcare for women over the age of 20 (approximately 90 million in the United States). Besides skin cancer, breast cancer is the most commonly diagnosed cancer among U.S. women. For these women, the American Cancer Society (ACS) has published guidelines for breast cancer screening including: (i) monthly breast self-examinations for all women over the age of 20; (ii) a clinical breast exam (CBE) every three years for women in their 20s and 30s; (iii) a baseline mammogram for women by the age of 40; and (iv) an annual mammogram for women age 40 or older (according to the American College of Radiology). Unfortunately, the U.S. Preventive Task Force Guidelines have stirred confusion by recommending biennial screening mammography for women ages 50-74.
Each year, approximately eight million women in the United States require diagnostic testing for breast cancer due to a physical symptom, such as a palpable lesion, pain or nipple discharge, discovered through self or physical examination (approximately seven million) or a non-palpable lesion detected by screening x-ray mammography
(approximately one million). Once a physician has identified a suspicious lesion in a woman's breast, the physician may recommend further diagnostic procedures, including a diagnostic x-ray mammography, an ultrasound study, a magnetic resonance imaging procedure, or a minimally invasive procedure such as fine needle aspiration or large core needle biopsy. In each case, the potential benefits of additional diagnostic testing must be balanced against the costs, risks and discomfort to the patient associated with undergoing the additional procedures.
'''
target_words = ['risks', 'discomfort', 'surviving', 'risking', 'risks', 'risky']
pattern = r'.*\b(?='+'|'.join(target_words) + r')\b.*'
target_paras = re.findall(pattern, txt, re.IGNORECASE)
# Function to extract sentences containing any target word and its neighbor sentences
def UncertaintySentences (paragraph):
sent_token = nltk.tokenize.sent_tokenize(paragraph)
keepsents = []
for i, sentence in enumerate(sent_token):
# sentences contain any target word
if re.search(pattern, sentence, re.IGNORECASE) != None:
try:
if i==0: # first sentence in a para, keep it and the one next to it
keepsents.extend([sent_token[i], sent_token[i+1]])
elif i!=len(sent_token)-1: # sentence in the middle, keep it ant the ones before and next to it
keepsents.extend([sent_token[i-1], sent_token[i], sent_token[i+1]])
else: # last sentence, keep it and the one before it
keepsents.extend([sent_token[i-1], sent_token[i]])
except: # para with only one sentence
keepsents = sent_token
# drop duplicate sentences
del_dup = []
[del_dup.append(x) for x in keepsents if x not in del_dup]
return(del_dup)
for para in target_paras:
uncertn_sents = UncertaintySentences(para)
print(uncertn_sents)
The final speed of your original regex is highly dependant on the data you are inspecting.
There's a problem with your regex:
r'.*\b(?='+'|'.join(target_words) + r')\b.*'
If there are many/big paragraphs with no keywords then the search process is very slow.
Why this happens?
Because your regex starts with .*
Your regex matches the whole paragraph and starts to backtrack characters one by one and tries to match the keywords while doing so. If there are no keywords at all, the backtracking process reaches the beginning of the paragraph.
Then, it advances one more character and repeats the whole process again (It reaches the end of string, backtracks to position 1), then advances to position 2 and repeats everything again...
You can better look at this process with this regex debugger:
https://regex101.com/r/boZLQU/1/debugger
Optimization
Just add an ^ to your regex, like this:
r'^.*\b(?='+'|'.join(target_words) + r')\b.*'
Note that we also need to use the M flag in order to make ^ behave as "beginning of line" instead of "beginning of string"
re.findall(pattern, txt, re.MULTILINE | re.IGNORECASE)
That way you'll just do the backtracking process one time instead of one for every character, which in the end should speed up the process a lot when searching through paragraphs that don't have any of the required keywords.
In terms of computational cost of the regex, it decreases from to
Here's a few ideas to optimize this code:
The target_words list can be converted to a set to make the in
operation more efficient.
The pattern variable can be precompiled using re.compile to make the
subsequent calls to re.findall and re.search faster.
The del_dup list comprehension can be replaced with a set() call to
remove duplicates more efficiently.
Maybe move the sent_token = nltk.tokenize.sent_tokenize(paragraph) out
of the loop of the UncertaintySentences function, so that the
tokenization operation is only performed once per paragraph.

How to match complete words for acronym using regex?

I want to only get complete words from acronyms with ( ) around them.
For example, there is a sentence
'Lung cancer screening (LCS) reduces NSCLC mortality';
->I want to get 'Lung cancer screening' as a result.
How can I do it with regex?
original question:
I want to remove repeated upper alphabets :
"HIV acquired immunodeficiency syndrome are at a particularly high risk of cervical cancer" => " acquired immunodeficiency syndrome are at a particularly high risk of cervical cancer"
Assuming you want to target 2 or more capital letters, I would use re.sub here:
inp = "Lung cancer screening (LCS) reduces NSCLC mortality"
output = re.sub(r'\s*(?:\([A-Z]+\)|[A-Z]{2,})\s*', ' ', inp).strip()
print(output) # Lung cancer screening reduces mortality
import re
s = 'HIV acquired immunodeficiency syndrome are at a particularly high risk of cervical cancer'
print(re.sub(r'([A-Z])', lambda pat:'', s).strip()) # Inline
according to #jensgram answer

How to extract the certain number and certain type of string following same pattern in an efficient way?

I would like to extract the year and the title from the publication records under the same pattern (author list. year. Title. Journal.)
For example for the following publication records text:
text=['Axley, J. 1988. Progress Toward a General Analytical Method for Predicting Indoor Air Pollution in Buildings: Indoor Air Quality Modeling Phase III Report. Gaithersburg, Maryland: National Bureau of Standards, NBSIR 88-3814.',
'Bearg, D.W. 1994. Second Generation Demand-Controlled Ventilation Systems. Proceedings of ASHRAE IAQ 94: 169-174.',
'Berg-Munch, B., G. Clausen and P.O. Fanger. 1986. Ventilation Requirements for the Control of Body Odor in Spaces Occupied by Women. Environment International 12: 195-199.']
The expected outputs will be
['1988','1994','1986'] and
['Progress Toward a General Analytical Method for Predicting Indoor Air Pollution in Buildings: Indoor Air Quality Modeling Phase III Report', 'Second Generation Demand-Controlled Ventilation Systems', 'Ventilation Requirements for the Control of Body Odor in Spaces Occupied by Women']
I am trying to use the regular function. My code is lengthy but could extract the expected outputs. I would like to know how to do this in an efficient way.
newline=[]
for line in range(len(text)):
key=text[line]
p1=r"[0-9][0-9][0-9][0-9]\..+?\."
pattern1 = re.compile(p1)
newline.append("".join(pattern1.findall(key)))
year = [x.split('.')[0] for x in newline]
title = [y.split('.')[1] for y in newline]
You can use 2 capturing groups:
\s(\d{4})\.\s*(.+)
RegEx Demo
RegEx Explanation:
\s: Match a whitespace
(\d{4}): Match 4 digits in capture group #1
\.: Match a dot
\s*: Match 0 or more whitespaces
(.+): Match 1+ of any any character in capture group #2
Code:
>>> import re
>>> text=['Axley, J. 1988. Progress Toward a General Analytical Method for Predicting Indoor Air Pollution in Buildings: Indoor Air Quality Modeling Phase III Report. Gaithersburg, Maryland: National Bureau of Standards, NBSIR 88-3814.',
'Bearg, D.W. 1994. Second Generation Demand-Controlled Ventilation Systems. Proceedings of ASHRAE IAQ 94: 169-174.',
'Berg-Munch, B., G. Clausen and P.O. Fanger. 1986. Ventilation Requirements for the Control of Body Odor in Spaces Occupied by Women. Environment International 12: 195-199.']
>>> years = []
>>> titles = []
>>> for s in text:
... m = re.search(r'\s(\d{4})\.\s*(.+)', s)
... if (m):
... years.append(m.group(1))
... titles.append(m.group(2))
...
>>> print (years)
['1988', '1994', '1986']
>>> print (titles)
[
'Progress Toward a General Analytical Method for Predicting Indoor Air Pollution in Buildings: Indoor Air Quality Modeling Phase III Report. Gaithersburg, Maryland: National Bureau of Standards, NBSIR 88-3814.',
'Second Generation Demand-Controlled Ventilation Systems. Proceedings of ASHRAE IAQ 94: 169-174.',
'Ventilation Requirements for the Control of Body Odor in Spaces Occupied by Women. Environment International 12: 195-199.'
]

How can I remove numbers that may occur at the end of words in a text

I have text data to be cleaned using regex. However, some words in the text are immediately followed by numbers which I want to remove.
For example, one row of the text is:
Preface2 Contributors4 Abrreviations5 Acknowledgements8 Pes
terminology10 Lessons learnt from the RUPES project12 Payment for
environmental service and it potential and example in Vietnam16
Chapter Integrating payment for ecosystem service into Vietnams policy
and programmes17 Chapter Creating incentive for Tri An watershed
protection20 Chapter Sustainable financing for landscape beauty in
Bach Ma National Park 24 Chapter Building payment mechanism for carbon
sequestration in forestry a pilot project in Cao Phong district of Hoa
Binh province Vietnam26 Chapter 5 Local revenue sharing Nha Trang Bay
Marine Protected Area Vietnam28 Synthesis and Recommendations30
References32
The first word in the above text should be 'preface' instead of 'preface2' and so on.
line = re.sub(r"[A-Za-z]+(\d+)", "", line)
This, however removes the words as well as seen:
Pes Lessons learnt from the RUPES Payment for environmental service
and it potential and example in Chapter Integrating payment for
ecosystem service into Vietnams policy and Chapter Creating incentive
for Tri An watershed Chapter Sustainable financing for landscape
beauty in Bach Ma National Park 24 Chapter Building payment mechanism
for carbon sequestration in forestry a pilot project in Cao Phong
district of Hoa Binh province Chapter 5 Local revenue sharing Nha
Trang Bay Marine Protected Area Synthesis and
How can I capture only the numbers that immediately follow words?
You can capture the text part and substitute the word with that captured part. It simply writes:
re.sub(r"([A-Za-z]+)\d+", r"\1", line)
You could try lookahead assertions to check for words before your numbers. Try word boundaries (\b) at the end of forcing your regex to only match numbers at the end of a word:
re.sub(r'(?<=\w+)\d+\b', '', line)
Hope this helps
EDIT:
Sorry about the glitch, mentioned in the comments about matching numbers that are NOT preceeded by words as well. That is because (sorry again) \w matches alphanumeric characters instead of only alphabetic ones. Depending on what you would like to delete you can use the positive version
re.sub(r'(?<=[a-zA-Z])\d+\b', '', line)
to only check for english alphabetic characters (you can add characters to the [a-zA-Z] list) preceeding your number or the negative version
re.sub(r'(?<![\d\s])\d+\b', '', line)
to match anything that is NOT \d (numbers) or \s (spaces) before your desired number. This will also match punctuation marks though.
Try this:
line = re.sub(r"([A-Za-z]+)(\d+)", "\\2", line) #just keep the number
line = re.sub(r"([A-Za-z]+)(\d+)", "\\1", line) #just keep the word
line = re.sub(r"([A-Za-z]+)(\d+)", r"\2", line) #same as first one
line = re.sub(r"([A-Za-z]+)(\d+)", r"\1", line) #same as second one
\\1 will match the word, \\2 the number. See: How to use python regex to replace using captured group?
below, I'm proposing a working sample of code that might solve your problem.
Here's the snippet:
import re
# I'will write a function that take the test data as input and return the
# desired result as stated in your question.
def transform(data):
"""Replace in a text data words ending with number.""""
# first, lest construct a pattern matching those words we're looking for
pattern1 = r"([A-Za-z]+\d+)"
# Lest construct another pattern that will replace the previous in the final
# output.
pattern2 = r"\d+$"
# Let find all matching words
matches = re.findall(pattern1, data)
# Let construct a list of replacement for each word
replacements = []
for match in matches:
replacements.append(pattern2, '', match)
# Intermediate variable to construct tuple of (word, replacement) for
# use in string method 'replace'
changers = zip(matches, replacements)
# We now recursively change every appropriate word matched.
output = data
for changer in changers:
output.replace(*changer)
# The work is done, we can return the result
return output
For test purpose, we run the above function with your test data:
data = """
Preface2 Contributors4 Abrreviations5 Acknowledgements8 Pes terminology10 Lessons
learnt from the RUPES project12 Payment for environmental service and it potential and
example in Vietnam16 Chapter Integrating payment for ecosystem service into Vietnams
policy and programmes17 Chapter Creating incentive for Tri An watershed protection20
Chapter Sustainable financing for landscape beauty in Bach Ma National Park 24 Chapter
Building payment mechanism for carbon sequestration in forestry a pilot project in Cao
Phong district of Hoa Binh province Vietnam26 Chapter 5 Local revenue sharing Nha Trang
Bay Marine Protected Area Vietnam28 Synthesis and Recommendations30 References32
"""
result = transform(data)
print(result)
And the result looks like this:
Preface Contributors Abrreviations Acknowledgements Pes terminology Lessons learnt from
the RUPES project Payment for environmental service and it potential and example in
Vietnam Chapter Integrating payment for ecosystem service into Vietnams policy and
programmes Chapter Creating incentive for Tri An watershed protection Chapter
Sustainable financing for landscape beauty in Bach Ma National Park 24 Chapter Building
payment mechanism for carbon sequestration in forestry a pilot project in Cao Phong
district of Hoa Binh province Vietnam Chapter 5 Local revenue sharing Nha Trang Bay
Marine Protected Area Vietnam Synthesis and Recommendations References
You can create a range of numbers as well:
re.sub(r"[0-9]", "", line)

Use regex to extract unit number

I have a list of descriptions and I want to extract the unit information using regular expression
I watched a video on regex and here's what I got
import re
x = ["Four 10-story towers - five 11-story residential towers around Lake Peterson - two 9-story hotel towers facing Devon Avenue & four levels of retail below the hotels",
"265 rental units",
"10 stories and contain 200 apartments",
"801 residential properties that include row homes, town homes, condos, single-family housing, apartments, and senior rental units",
"4-unit townhouse building (6,528 square feet of living space & 2,755 square feet of unheated garage)"]
unit=[]
for item in x:
extract = re.findall('[0-9]+.unit',item)
unit.append(extract)
print unit
This works with string ends in unit, but I also strings end with 'rental unit','apartment','bed' and other as in this example.
I could do this with multiple regex, but is there a way to do this within one regex?
Thanks!
As long as your not afraid of making a hideously long regex you could use something to the extent of:
compiled_re = re.compile(ur"(\d*)-unit|(\d*)\srental unit|(\d*)\sbed|(\d*)\sappartment")
unit = []
for item in x:
extract = re.findall(compiled_re, item)
unit.append(extract)
You would have to extend the regex pattern with a new "|" followed by a search pattern for each possible type of reference to unit numbers. Unfortunately, if there is very low consistency in the entries this approach would become basically unusable.
Also, might I suggest using a regex tester like Regex101. It really helps determining if your regex will do what you want it to.

Categories

Resources