Insert Colon between each element of a list python - python

[x[1] for x in matches]
x
newtest = [x2[-2:] for x2 in x]
newtest
I have a list
[u'asvbsMasd', u'abdhesMrty', u'ahdksC', u'ahdeO', u'ahdnL', u'ahddsS',]
now i want my list to be like a colon between where it finds a lower case and upper case
[u'asvbs:Masd', u'abdhes:Mrty', u'ahdks:C', u'ahde:Oqqq', u'ahdn:L', u'ahdds:S',]

You need to write a regex that matches <lowercase><uppercase> pair:
>>> import re
>>> r = re.compile(r'([a-z])([A-Z])')
Note the letters itself marked as a groups via (). If you have regex matching the pair and the neighbor letters as two separate groups, you may just use the substitution (\1 and \2 are places where matched groups are put into the substitution string):
>>> r.sub(r'\1:\2', u'asvbsMasd')
u'asvbs:Masd'
Then you can use list comprehension to apply that substitution to each element of a list:
>>> l = [u'asvbsMasd', u'abdhesMrty', u'ahdksC', u'ahdeO', u'ahdnL', u'ahddsS']
>>> [r.sub(r'\1:\2', s) for s in l]
[u'asvbs:Masd', u'abdhes:Mrty', u'ahdks:C', u'ahde:O', u'ahdn:L', u'ahdds:S']
Or if you want it wrapped into a function:
import re
re_lowerupper = r = re.compile(r'([a-z])([A-Z])')
def add_colons(l):
global re_lowerupper
return [re_lowerupper.sub(r'\1:\2', s) for s in l]
print add_colons([u'asvbsMasd', u'abdhesMrty', u'ahdksC', u'ahdeO', u'ahdnL', u'ahddsS'])
You may of course simplify it just to a single lambda, like in the next example.
One importand disclaimer, as I see you use Unicode strings: there is no easy way of finding arbitrary Unicode upper/lowercase character. There is no shorthand defined like for matching any digit (\d) or any alphanumeric character (\w). If you need to match diacritics too, you may need to list the lowercase and uppercase diacritics of your language explicitly in the regex, like:
re_lower = ur'[a-zßàáâãäåæçèéêëìíîïðñòóôõöùúûüýþÿāăąćĉčēĕėęěğģĥĩīĭįĵķļľŀłņňŋōŏőœŕŗřśŝşţťũūŭůűųŵŷźžǎǐǒǔǖǘǚǜǩǫǵǹȟȧȩȯȳəḅḋḍḑḟḡḣḥḧḩḱḳṃṕṗṙṛṡṣṫṭṽẁẃẅẇẉẍẏẑẓẗẘẙạẹẽịọụỳỵỹ]'
re_upper = ur'[A-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖÙÚÛÜÝÞĀĂĄĆĈČĒĔĖĘĚĞĢĤĨĪĬĮİĴĶĻĽĿŁŅŇŊŌŎŐŒŔŖŘŚŜŞŢŤŨŪŬŮŰŲŴŶŸŹŽƏǍǏǑǓǕǗǙǛǨǪǴǸȞȦȨȮȲḄḊḌḐḞḠḢḤḦḨḰḲṂṔṖṘṚṠṢṪṬṼẀẂẄẆẈẌẎẐẒẠẸẼỊỌỤỲỴỸ]'
re_lowerupper = re.compile('(%s)(%s)' % (re_lower, re_upper))
add_colons = lambda l: [re_lowerupper.sub(r'\1:\2', s) for s in l]
This should do the job for the Latin script European languages.

Related

Using Regexp to catch substring python

Let's assume I have some string like that:
x = 'Wish she could have told me herself. #NicoleScherzy #nicolescherzinger #OneLove #myfav #MyQueen :heavy_black_heart::heavy_black_heart: some string too :smiling_face:'
So, I want to get from that :
:heavy_black_heart:
:smiling_face:
To do that I did the following :
import re
result = re.search(':(.*?):', x)
result.group()
It only gives me the ':heavy_black_heart:' . How could I make it work ? If possible I want to store them in dictonary after I found all of them.
print re.findall(':.*?:', x) is doing the job.
Output:
[':heavy_black_heart:', ':heavy_black_heart:', ':smiling_face:']
But if you want to remove the duplicates:
Use:
res = re.findall(':.*?:', x)
dictt = {x for x in res}
print list(dictt)
Output:
[':heavy_black_heart:', ':smiling_face:']
You seem to want to match smilies that are some symbols in-between 2 :s. The .*? can match 0 symbols, and your regex can match ::, which I think is not what you would want to get. Besdies, re.search only returns one - the first - match, and to get multiple matches, you usually use re.findall or re.finditer.
I think you need
set(re.findall(r':[^:]+:', x))
or if you only need to match word chars inside :...::
set(re.findall(r':\w+:', x))
or - if you want to match any non-whitespace chars in between two ::
set(re.findall(r':[^\s:]+:', x))
The re.findall will find all non-overlapping occurrences and set will remove dupes.
The patterns will match :, then 1+ chars other than : ([^:]+) (or 1 or more letters, digits and _) and again :.
>>> import re
>>> x = 'Wish she could have told me herself. #NicoleScherzy #nicolescherzinger #OneLove #myfav #MyQueen :heavy_black_heart::heavy_black_heart: some string too :smiling_face:'
>>> print(set(re.findall(r':[^:]+:', x)))
{':smiling_face:', ':heavy_black_heart:'}
>>>
try this regex:
:([a-z0-9:A-Z_]+):
import re
x = 'Wish she could have told me herself. #NicoleScherzy #nicolescherzinger #OneLove #myfav #MyQueen :heavy_black_heart::heavy_black_heart: some string too :smiling_face:'
print set(re.findall(':.*?:', x))
output:
{':heavy_black_heart:', ':smiling_face:'}
Just for fun, here's a simple solution without regex. It splits around ':' and keeps the elements with odd index:
>>> text = 'Wish she could have told me herself. #NicoleScherzy #nicolescherzinger #OneLove #myfav #MyQueen :heavy_black_heart::heavy_black_heart: some string too :smiling_face:'
>>> text.split(':')[1::2]
['heavy_black_heart', 'heavy_black_heart', 'smiling_face']
>>> set(text.split(':')[1::2])
set(['heavy_black_heart', 'smiling_face'])

Regular Expression (find matching characters in order)

Let us say that I have the following string variables:
welcome = "StackExchange 2016"
string_to_find = "Sx2016"
Here, I want to find the string string_to_find inside welcome using regular expressions. I want to see if each character in string_to_find comes in the same order as in welcome.
For instance, this expression would evaluate to True since the 'S' comes before the 'x' in both strings, the 'x' before the '2', the '2' before the 0, and so forth.
Is there a simple way to do this using regex?
Your answer is rather trivial. The .* character combination matches 0 or more characters. For your purpose, you would put it between all characters in there. As in S.*x.*2.*0.*1.*6. If this pattern is matched, then the string obeys your condition.
For a general string you would insert the .* pattern between characters, also taking care of escaping special characters like literal dots, stars etc. that may otherwise be interpreted by regex.
This function might fit your need
import re
def check_string(text, pattern):
return re.match('.*'.join(pattern), text)
'.*'.join(pattern) create a pattern with all you characters separated by '.*'. For instance
>> ".*".join("Sx2016")
'S.*x.*2.*0.*1.*6'
Use wildcard matches with ., repeating with *:
expression = 'S.*x.*2.*0.*1.*6'
You can also assemble this expression with join():
expression = '.*'.join('Sx2016')
Or just find it without a regular expression, checking whether the location of each of string_to_find's characters within welcome proceeds in ascending order, handling the case where a character in string_to_find is not present in welcome by catching the ValueError:
>>> welcome = "StackExchange 2016"
>>> string_to_find = "Sx2016"
>>> try:
... result = [welcome.index(c) for c in string_to_find]
... except ValueError:
... result = None
...
>>> print(result and result == sorted(result))
True
Actually having a sequence of chars like Sx2016 the pattern that best serve your purpose is a more specific:
S[^x]*x[^2]*2[^0]*0[^1]*1[^6]*6
You can obtain this kind of check defining a function like this:
import re
def contains_sequence(text, seq):
pattern = seq[0] + ''.join(map(lambda c: '[^' + c + ']*' + c, list(seq[1:])))
return re.search(pattern, text)
This approach add a layer of complexity but brings a couple of advantages as well:
It's the fastest one because the regex engine walk down the string only once while the dot-star approach go till the end of the sequence and back each time a .* is used. Compare on the same string (~1k chars):
Negated class -> 12 steps
Dot star -> 4426 step
It works on multiline strings in input as well.
Example code
>>> sequence = 'Sx2016'
>>> inputs = ['StackExchange2015','StackExchange2016','Stack\nExchange\n2015','Stach\nExchange\n2016']
>>> map(lambda x: x + ': yes' if contains_sequence(x,sequence) else x + ': no', inputs)
['StackExchange2015: no', 'StackExchange2016: yes', 'Stack\nExchange\n2015: no', 'Stach\nExchange\n2016: yes']

Regex match key except one

I have a python list. It contains strings like items[number].some field. I want get all this strings except strings that match items[<number>].classification. How I can do this by regex or maybe there is another way?
As an example, I have something like:
data.items.[0].deliveryAddress.region
data.items.[0].classification.scheme
data.items.[0].classification.id
data.items.[0].description
And I want to stay only with :
data.items.[0].description
data.items.[0].deliveryAddress.region
To do this, I used this regex to match the strings you want to discard:
data.items.\[\d+\].classification
Say I have a Python list containing those items called l:
l = ["data.items.[0].deliveryAddress.region",
"data.items.[0].classification.scheme",
"data.items.[0].classification.id",
"data.items.[0].description"]
I can then use a list comprehension to only keep the values that don't match the regex, by using re.match.
>>> import re
>>> [x for x in l if not re.match(r"data.items.\[\d+\].classification", x)]
['data.items.[0].deliveryAddress.region', 'data.items.[0].description']
You could go for a negative lookahead combined with anchors:
^((?:.(?!classification))+)$
In Python code this would be:
import re
string = """
data.items.[0].deliveryAddress.region
data.items.[0].classification.scheme
data.items.[0].classification.id
data.items.[0].description
"""
rx = re.compile(r'^((?:.(?!classification))+)$', re.MULTILINE)
matches = rx.findall(string)
print matches
# ['data.items.[0].deliveryAddress.region', 'data.items.[0].description']
Obviously, this will work with a list as well:
import re
lst = ['data.items.[0].deliveryAddress.region',
'data.items.[0].classification.scheme',
'data.items.[0].classification.id',
'data.items.[0].description']
# no need for re.MULTILINE here
rx = re.compile(r'^((?:.(?!classification))+)$')
matches = [x for x in lst if rx.match(x)]
print matches
# ['data.items.[0].deliveryAddress.region', 'data.items.[0].description']
See a demo on regex101.com.

Python - Parse strings with variable repeating substring

I am trying to do something which I thought would be simple (and probably is), however I am hitting a wall. I have a string that contains document numbers. In most cases the format is ######-#-### however in some cases, where the single digit should be, there are multiple single digits separated by a comma (i.e. ######-#,#,#-###). The number of single digits separated by a comma is variable. Below is an example:
For the string below:
('030421-1,2-001 & 030421-1-002,030421-1,2,3-002, 030421-1-003')
I need to return:
['030421-1-001', '030421-2-001' '030421-1-002', '030421-1-002', '030421-2-002', '030421-3-002' '030421-1-003']
I have only gotten as far as returning the strings that match the ######-#-### pattern:
import re
p = re.compile('\d{6}-\d{1}-\d{3}')
m = p.findall('030421-1,2-001 & 030421-1-002,030421-1,2,3-002, 030421-1-003')
print m
Thanks in advance for any help!
Matt
Perhaps something like this:
>>> import re
>>> s = '030421-1,2-001 & 030421-1-002,030421-1,2,3-002, 030421-1-003'
>>> it = re.finditer(r'(\b\d{6}-)(\d(?:,\d)*)(-\d{3})\b', s)
>>> for m in it:
a, b, c = m.groups()
for x in b.split(','):
print a + x + c
...
030421-1-001
030421-2-001
030421-1-002
030421-1-002
030421-2-002
030421-3-002
030421-1-003
Or using a list comprehension
>>> [a+x+c for a, b, c in (m.groups() for m in it) for x in b.split(',')]
['030421-1-001', '030421-2-001', '030421-1-002', '030421-1-002', '030421-2-002', '030421-3-002', '030421-1-003']
Use '\d{6}-\d(,\d)*-\d{3}'.
* means "as many as you want (0 included)".
It is applied to the previous element, here '(,\d)'.
I wouldn't use a single regular expression to try and parse this. Since it is essentially a list of strings, you might find it easier to replace the "&" with a comma globally in the string and then use split() to put the elements into a list.
Doing a loop of the list will allow you to write a single function to parse and fix the string and then you can push it onto a new list and the display your string.
replace(string, '&', ',')
initialList = string.split(',')
for item in initialList:
newItem = myfunction(item)
newList.append(newItem)
newstring = newlist(join(','))
(\d{6}-)((?:\d,?)+)(-\d{3})
We take 3 capturing groups. We match the first part and last part the easy way. The center part is optionally repeated and optionally contains a ','. Regex will however only match the last one, so ?: won't store it at all. What where left with is the following result:
>>> p = re.compile('(\d{6}-)((?:\d,?)+)(-\d{3})')
>>> m = p.findall('030421-1,2-001 & 030421-1-002,030421-1,2,3-002, 030421-1-003')
>>> m
[('030421-', '1,2', '-001'), ('030421-', '1', '-002'), ('030421-', '1,2,3', '-002'), ('030421-', '1', '-003')]
You'll have to manually process the 2nd term to split them up and join them, but a list comprehension should be able to do that.

Split a string using a list of strings as a pattern

Consider an input string :
mystr = "just some stupid string to illustrate my question"
and a list of strings indicating where to split the input string:
splitters = ["some", "illustrate"]
The output should look like
result = ["just ", "some stupid string to ", "illustrate my question"]
I wrote some code which implements the following approach. For each of the strings in splitters, I find its occurrences in the input string, and insert something which I know for sure would not be a part of my input string (for example, this '!!'). Then I split the string using the substring that I just inserted.
for s in splitters:
mystr = re.sub(r'(%s)'%s,r'!!\1', mystr)
result = re.split('!!', mystr)
This solution seems ugly, is there a nicer way of doing it?
Splitting with re.split will always remove the matched string from the output (NB, this is not quite true, see the edit below). Therefore, you must use positive lookahead expressions ((?=...)) to match without removing the match. However, re.split ignores empty matches, so simply using a lookahead expression doesn't work. Instead, you will lose one character at each split at minimum (even trying to trick re with "boundary" matches (\b) does not work). If you don't care about losing one whitespace / non-word character at the end of each item (assuming you only split at non-word characters), you can use something like
re.split(r"\W(?=some|illustrate)")
which would give
["just", "some stupid string to", "illustrate my question"]
(note that the spaces after just and to are missing). You could then programmatically generate these regexes using str.join. Note that each of the split markers is escaped with re.escape so that special characters in the items of splitters do not affect the meaning of the regular expression in any undesired ways (imagine, e.g., a ) in one of the strings, which would otherwise lead to a regex syntax error).
the_regex = r"\W(?={})".format("|".join(re.escape(s) for s in splitters))
Edit (HT to #Arkadiy): Grouping the actual match, i.e. using (\W) instead of \W, returns the non-word characters inserted into the list as seperate items. Joining every two subsequent items would then produce the list as desired as well. Then, you can also drop the requirement of having a non-word character by using (.) instead of \W:
the_new_regex = r"(.)(?={})".format("|".join(re.escape(s) for s in splitters))
the_split = re.split(the_new_regex, mystr)
the_actual_split = ["".join(x) for x in itertools.izip_longest(the_split[::2], the_split[1::2], fillvalue='')]
Because normal text and auxiliary character alternate, the_split[::2] contains the normal split text and the_split[1::2] the auxiliary characters. Then, itertools.izip_longest is used to combine each text item with the corresponding removed character and the last item (which is unmatched in the removed characters)) with fillvalue, i.e. ''. Then, each of these tuples is joined using "".join(x). Note that this requires itertools to be imported (you could of course do this in a simple loop, but itertools provides very clean solutions to these things). Also note that itertools.izip_longest is called itertools.zip_longest in Python 3.
This leads to further simplification of the regular expression, because instead of using auxiliary characters, the lookahead can be replaced with a simple matching group ((some|interesting) instead of (.)(?=some|interesting)):
the_newest_regex = "({})".format("|".join(re.escape(s) for s in splitters))
the_raw_split = re.split(the_newest_regex, mystr)
the_actual_split = ["".join(x) for x in itertools.izip_longest([""] + the_raw_split[1::2], the_raw_split[::2], fillvalue='')]
Here, the slice indices on the_raw_split have swapped, because now the even-numbered items must be added to item afterwards instead of in front. Also note the [""] + part, which is necessary to pair the first item with "" to fix the order.
(end of edit)
Alternatively, you can (if you want) use string.replace instead of re.sub for each splitter (I think that is a matter of preference in your case, but in general it is probably more efficient)
for s in splitters:
mystr = mystr.replace(s, "!!" + s)
Also, if you use a fixed token to indicate where to split, you do not need re.split, but can use string.split instead:
result = mystr.split("!!")
What you could also do (instead of relying on the replacement token not to be in the string anywhere else or relying on every split position being preceded by a non-word character) is finding the split strings in the input using string.find and using string slicing to extract the pieces:
def split(string, splitters):
while True:
# Get the positions to split at for all splitters still in the string
# that are not at the very front of the string
split_positions = [i for i in (string.find(s) for s in splitters) if i > 0]
if len(split_positions) > 0:
# There is still somewhere to split
next_split = min(split_positions)
yield string[:next_split] # Yield everything before that position
string = string[next_split:] # Retain the rest of the string
else:
yield string # Yield the rest of the string
break # Done.
Here, [i for i in (string.find(s) for s in splitters) if i > 0] generates a list of positions where the splitters can be found, for all splitters that are in the string (for this, i < 0 is excluded) and not right at the beginning (where we (possibly) just split, so i == 0 is excluded as well). If there are any left in the string, we yield (this is a generator function) everything up to (excluding) the first splitter (at min(split_positions)) and replace the string with the remaining part. If there are none left, we yield the last part of the string and exit the function. Because this uses yield, it is a generator function, so you need to use list to turn it into an actual list.
Note that you could also replace yield whatever with a call to some_list.append (provided you defined some_list earlier) and return some_list at the very end, I do not consider that to be very good code style, though.
TL;DR
If you are OK with using regular expressions, use
the_newest_regex = "({})".format("|".join(re.escape(s) for s in splitters))
the_raw_split = re.split(the_newest_regex, mystr)
the_actual_split = ["".join(x) for x in itertools.izip_longest([""] + the_raw_split[1::2], the_raw_split[::2], fillvalue='')]
else, the same can also be achieved using string.find with the following split function:
def split(string, splitters):
while True:
# Get the positions to split at for all splitters still in the string
# that are not at the very front of the string
split_positions = [i for i in (string.find(s) for s in splitters) if i > 0]
if len(split_positions) > 0:
# There is still somewhere to split
next_split = min(split_positions)
yield string[:next_split] # Yield everything before that position
string = string[next_split:] # Retain the rest of the string
else:
yield string # Yield the rest of the string
break # Done.
Not especially elegant but avoiding regex:
mystr = "just some stupid string to illustrate my question"
splitters = ["some", "illustrate"]
indexes = [0] + [mystr.index(s) for s in splitters] + [len(mystr)]
indexes = sorted(list(set(indexes)))
print [mystr[i:j] for i, j in zip(indexes[:-1], indexes[1:])]
# ['just ', 'some stupid string to ', 'illustrate my question']
I should acknowledge here that a little more work is needed if a word in splitters occurs more than once because str.index finds only the location of the first occurrence of the word...

Categories

Resources