String manipulation weirdness when incrementing trailing digit - python

I got this code:
myString = 'blabla123_01_version6688_01_01Long_stringWithNumbers'
versionSplit = re.findall(r'-?\d+|[a-zA-Z!##$%^&*()_+.,<>{}]+|\W+?', myString)
for i in reversed(versionSplit):
id = versionSplit.index(i)
if i.isdigit():
digit = '%0'+str(len(i))+'d'
i = int(i) + 1
i = digit % i
versionSplit[id]=str(i)
break
final = ''
myString = final.join(versionSplit)
print myString
Which suppose to increase ONLY the last digit from the string given. But if you run that code you will see that if there is the same digit in the string as the last one it will increase it one after the other if you keep running the script. Can anyone help me find out why?
Thank you in advance for any help

Is there a reason why you aren't doing something like this instead:
prefix, version = re.match(r"(.*[^\d]+)([\d]+)$", myString).groups()
newstring = prefix + str(int(version)+1).rjust(len(version), '0')
Notes:
This will actually "carry over" the version numbers properly: ("09" -> "10") and ("99" -> "100")
This regex assumes at least one non-numeric character before the final version substring at the end. If this is not matched, it will throw an AttributeError. You could restructure it to throw a more suitable or specific exception (e.g. if re.match(...) returns None; see comments below for more info).
Adjust accordingly.

The issue is the use of the list.index() function on line 5. This returns the index of the first occurrence of a value in a list, from left to right, but the code is iterating over the reversed list (right to left). There are lots of ways to straighten this out, but here's one that makes the fewest changes to your existing code: Iterate over indices in reverse (avoids reversing the list).
for idx in range(len(versionSplit)-1, -1, -1):
i = versionSplit[idx]
if chunk.isdigit():
digit = '%0'+str(len(i))+'d'
i = int(i) + 1
i = digit % i
versionSplit[idx]=str(i)
break

myString = 'blabla123_01_version6688_01_01veryLong_stringWithNumbers01'
versionSplit = re.findall(r'-?\d+|[^\-\d]+', myString)
for i in xrange(len(versionSplit) - 1, -1, -1):
s = versionSplit[i]
if s.isdigit():
n = int(s) + 1
versionSplit[i] = "%0*d" % (len(s), n)
break
myString = ''.join(versionSplit)
print myString
Notes:
It is silly to use the .index() method to try to find the string. Just use a decrementing index to try each part of versionSplit. This was where your problem was, as commented above by #David Robinson.
Don't use id as a variable name; you are covering up the built-in function id().
This code is using the * in a format template, which will accept an integer and set the width.
I simplified the pattern: either you are matching a digit (with optional leading minus sign) or else you are matching non-digits.
I tested this and it seems to work.

First, three notes:
id is a reserved python word;
For joining, a more pythonic idiom is ''.join(), using a literal empty string
reversed() returns an iterator, not a list. That's why I use list(reversed()), in order to do rev.index(i) later.
Corrected code:
import re
myString = 'blabla123_01_version6688_01_01veryLong_stringWithNumbers01'
print myString
versionSplit = re.findall(r'-?\d+|[a-zA-Z!##$%^&*()_+.,<>{}]+|\W+?', myString)
rev = list(reversed(versionSplit)) # create a reversed list to work with from now on
for i in rev:
idd = rev.index(i)
if i.isdigit():
digit = '%0'+str(len(i))+'d'
i = int(i) + 1
i = digit % i
rev[idd]=str(i)
break
myString = ''.join(reversed(rev)) # reverse again only just before joining
print myString

Related

python string replace using for loop with if else

I am new with python, trying to replace string using for loop with if else condition,
I have a string and want to replace some character of that string in a such way that it should take / pick first character of the string and search them in the old_list if the character match it should replace that character with the character of new_list and if the character does not match it should consider that character (previous) and the next character together of the string and search them combinely and again search in old_list and so on.
it should replace in this oder (picking the character from string) = 010,101,010,010,100,101,00,00,011,1101,011,00,101,010,00,011,1111,1110,00,00,00,010,101,010,
replacing value = 1001,0000,0000,1000,1111,1001,1111,1111,100,1010101011,100,1111,1001,0000,1111,100,10100101,101010,1111,1111,1111,0000,1001,
with the example of above string if we performed that operation the string will becomes
final or result string = 10010000000010001111100111111111100101010101110011111001000011111001010010110101011111111111100001001
string = 01010101001010010100000111101011001010100001111111110000000010101010
old_list = ['00','011','010','101','100','1010','1011','1101','1110','1111']
new_list = ['1111','100','0000','1001','1000'1111','0101','1010101011','101010','10100101']
i = 0
for i in range((old), 0):
if i == old:
my_str = my_str.replace(old[i],new[i], 0)
else:
i = i + 1
print(my_str)
as result, string become = 10010000000010001111100111111111100101010101110011111001000011111001010010110101011111111111100001001
new = ['a ','local ','is ']
my_str = 'anindianaregreat'
old = ['an','indian','are']
for i, string in enumerate(old):
my_str = my_str.replace(string, new[i], 1)
print(my_str)
Your usage of range is incorrect.
range goes from lower (inclusive) to higher (exclusive) or simply 0 to higher (exclusive)
Your i == old condition is incorrect as well. (i is an integer, while old is a list). Also what is it supposed to do?
You can simply do:
for old_str, new_str in zip(old, new):
my_str = my_str.replace(old_str, new_str, 1)
https://docs.python.org/3/library/stdtypes.html#str.replace
You can provide an argument to replace to specify how many occurrences to replace.
No conditional is required since if old_str is absent, nothing will be replaced anyway.

Remove punctuation items from end of string

I have a seemingly simple problem, which I cannot seem to solve. Given a string containing a DOI, I need to remove the last character if it is a punctuation mark until the last character is letter or number.
For example, if the string was:
sampleDoi = "10.1097/JHM-D-18-00044.',"
I want the following output:
"10.1097/JHM-D-18-00044"
ie. remove .',
I wrote the following script to do this:
invalidChars = set(string.punctuation.replace("_", ""))
a = "10.1097/JHM-D-18-00044.',"
i = -1
for each in reversed(a):
if any(char in invalidChars for char in each):
a = a[:i]
i = i - 1
else:
print (a)
break
However, this produces 10.1097/JHM-D-18-00 but I would like it to produce 10.1097/JHM-D-18-00044. Why is the 44 removed from the end?
The string function rstrip() is designed to do exactly this:
>>> sampleDoi = "10.1097/JHM-D-18-00044.',"
>>> sampleDoi.rstrip(",.'")
'10.1097/JHM-D-18-00044'
Corrected code:
import string
invalidChars = set(string.punctuation.replace("_", ""))
a = "10.1097/JHM-D-18-00044.',"
i = -1
for each in reversed(a):
if any(char in invalidChars for char in each):
a = a[:i]
i = i # Well Really this line can just be removed all together.
else:
print (a)
break
This gives the output you want, while keeping the original code mostly the same.
This is one way using next and str.isalnum with a generator expression utilizing enumerate / reversed.
sampleDoi = "10.1097/JHM-D-18-00044.',"
idx = next((i for i, j in enumerate(reversed(sampleDoi)) if j.isalnum()), 0)
res = sampleDoi[:-idx]
print(res)
'10.1097/JHM-D-18-00044'
The default parameter 0is used so that, if no alphanumeric character is found, an empty string is returned.
If you dont wanna use regex:
the_str = "10.1097/JHM-D-18-00044.',"
while the_str[-1] in string.punctuation:
the_str = the_str[:-1]
Removes the last character until it's no longer a punctuation character.

How to split string everywhere a letter appears?

I have a string containing letters and numbers like this -
12345A6789B12345C
How can I get a list that looks like this
[12345A, 6789B, 12345C]
>>> my_string = '12345A6789B12345C'
>>> import re
>>> re.findall('\d*\w', my_string)
['12345A', '6789B', '12345C']
For the sake of completeness, non-regex solution:
data = "12345A6789B12345C"
result = [""]
for char in data:
result[-1] += char
if char.isalpha():
result.append("")
if not result[-1]:
result.pop()
print(result)
# ['12345A', '6789B', '12345C']
Should be faster for smaller strings, but if you're working with huge data go with regex as once compiled and warmed up, the search separation happens on the 'fast' C side.
You could build this with a generator, too. The approach below keeps track of start and end indices of each slice, yielding a generator of strings. You'll have to cast it to list to use it as one, though (splitonalpha(some_string)[-1] will fail, since generators aren't indexable)
def splitonalpha(s):
start = 0
for end, ch in enumerate(s, start=1):
if ch.isalpha:
yield s[start:end]
start = end
list(splitonalpha("12345A6789B12345C"))
# ['12345A', '6789B', '12345C']

Realizing if there is a pattern in a string (Does not need to start at index 0, could be any length)

Coding a program to detect a n-length pattern in a string, even without knowing where the pattern starts, could be easily done by creating a list of n-length substrings and check if starting at one point there are same items or the rest of the list. Without any piece of information other than the string to check through, is the only way to recognize the pattern is to brute-force through all lengths and check or is there a more efficient algorithm?
(I'm just a beginner in Python, so this may be easy to code... )
Current code that only suits checking for starting at index 0:
def search(s):
match=s[0]+s[1]
while (match != s) and (match[0] != match[-1]):
for matchLen in range(len(match),len(s)-1):
letter = s[matchLen]
if letter == match[-1]:
match += s[len(match)]
break
if match == s:
return None
else:
return match[:-1]
You can use re.findall(r'(.{2,})\1+', string). The parentheses creates a capture group that is later backreferenced by \1. The . matches any character (except for line breaks). The {2,} requires the pattern to be at least two characters long (otherwise strings like ss would be considered a pattern). Finally the + requires that pattern to repeat 1 or more times (in addition to the first time that it occurred inside the capture group). You can see it working in action.
Pattern is a far too vague term, but assuming you mean some string repeating itself, the regexp (?P<pat>.+)(?P=pat) will work.
Given a string what you could do is -
You start with length = 1, and take two pointer variables i and j which you shall use to traverse the string.
Set i = 0 and j = i+length
if str[i]==str[j]:
i++,j++ // till j not equal to length of string
else:
length = length + 1
//increase length by 1 and start the algorithm over from i = 0
Take the example abcdeabcde :
In this we see
Initially i = 0, j = 1 ,
but str[0]!=str[1] i.e. a!=b,
Then we get length = 2 i.e., i = 0,j = 2
but str[0]!=str[2] i.e. a!=c,
Continuing in the same fashion,
We see when length = 5 and i = 0 and j = 5,
str[0]==str[5]
and thus you can see that i and j increment till j is equal to string length.
And you have your answer that is the pattern length. It may not seem obvious but i would suggest you dry-run this algorithm over some of your test cases and let me know the results.
You can use re.findall() to find all matches:
import re
s = "somethingabcdeabcdeabcdeabcdeabcdeelseabcdeabcdeabcde"
li = re.findall(r'abcde',s)
print(li)
Output:
['abcde', 'abcde', 'abcde', 'abcde', 'abcde', 'abcde', 'abcde', 'abcde']

Splitting a number pattern

I want to now how do i split a string like
44664212666666 into [44664212 , 666666] or
58834888888888 into [58834, 888888888]
without knowing where the first occurrence of the last recurring digit occurs.
so passing it to a function say seperate(str) --> [non_recurring_part, end_recurring digits]
print re.findall(r'^(.+?)((.)\3+)$', '446642126666')[0][:-1] # ('44664212', '6666')
As pointed out in the comments, the last group should be made optional to handle strings with no repeated symbols correctly:
print re.findall(r'^(.+?)((.)\3+)?$', '12333')[0][:-1] # ('12', '333')
print re.findall(r'^(.+?)((.)\3+)?$', '123')[0][:-1] # ('123', '')
Same answer as Justin:
>>> for i in range(len(s) - 1, 0, -1):
if s[i] != s[-1]:
break
>>> non_recurring_part, end_recurring_digits = s[:i], s[i + 1:]
>>> non_recurring_part, end_recurring_digits
('4466421', '666666')
Here is a non-regex answer that deals with cases when there are no repeating digits.
def separate(s):
last = s[-1]
t = s.rstrip(last)
if len(t) + 1 == len(s):
return (s, '')
else:
return t, last * (len(s) - len(t))
Examples:
>>> separate('123444')
('123', '444')
>>> separate('1234')
('1234', '')
>>> separate('11111')
('', '11111')
Can't you just scan from the last character to the first character and stop when the next char doesn't equal the previous. Then split at that index.
def separate(n):
s = str(n)
return re.match(r'^(.*?)((.)\3*)$', s).groups()
def seperate(s):
return re.findall('^(.+?)('+s[-1]+'+)$',s)
>>> import re
>>> m = re.match(r'(.*?)((.)\3+)$', '1233333')
>>> print list(m.groups())[:2]
['12', '33333']
Here you use regular expressions. The last part of the re ((.)\3+)$ says that the same number must be repeated till the end of the string. And all the rest is the first part of the string. The function m.groups() return the list of the string that correspond to the () parts of the re. The 0 element contains the first part; the 1 element contains the second part. The third part is not needed, we can just ignore it.
Another important point is ? in .*?. Using the symbol you say that you need non-greedy search. That means that you need to switch to the second part of re as soon as possible.
start iterating from the end,towards the initial digit, just get the position where the character occurring changes, that should be the limit for sub string splitting, Let that limit index is--> i, Then Your Result will be-->{sub-string [0,i) , sub-string [i,size)},, That will solve your problem..
int pos=0;
String str="ABCDEF";
for (int i = str.length()-1; i > 0; i--)
{
if(str.charAt(i) != str.charAt(i-1))
{
pos=i;
break;
}
}
String sub1=str.substring(0, pos);
String sub2=str.substring(pos);

Categories