101

How can I insert some text into an existing string?

For example, suppose I have a string "Name Age Group Class Profession". How can I insert the third word three more times before the fourth, to get "Name Age Group Group Group Group Class Profession"?

I know how to split the string into words using .split(), but then what?

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
james
  • 1,087
  • 2
  • 10
  • 13
  • The example given here doesn't really illustrate the problem. A string has character indices, not word indices. The problem in the example could be tackled by splitting the string into words, inserting into the list, and joining the list back into a string; but the question that was asked specifically describes figuring out a position in a string and directly inserting into the string. It also made use of the word "fields", which does not make any sense in context. – Karl Knechtel Jan 10 '23 at 02:02

9 Answers9

157

For the sake of future 'newbies' tackling this problem, I think a quick answer would be fitting to this thread.

Like bgporter said: Python strings are immutable, and so, in order to modify a string you have to make use of the pieces you already have.

In the following example I insert 'Fu' in to 'Kong Panda', to create 'Kong Fu Panda'

>>> line = 'Kong Panda'
>>> index = line.find('Panda')
>>> output_line = line[:index] + 'Fu ' + line[index:]
>>> output_line
'Kong Fu Panda'

In the example above, I used the index value to 'slice' the string in to 2 substrings: 1 containing the substring before the insertion index, and the other containing the rest. Then I simply add the desired string between the two and voilà, we have inserted a string inside another.

Python's slice notation has a great answer explaining the subject of string slicing.

Community
  • 1
  • 1
Ben Avnon
  • 1,681
  • 2
  • 13
  • 13
  • 62
    Great answer, however I believe the film title is spelt "*Kung* Fu Panda" – James Vickery Nov 15 '16 at 00:31
  • 1
    Be advised that `find()` will return the start index of the first occurrence and it will return `-1` if the searched-for string is not found. So this will not generalize to situations where the string of interest, in this case `Panda`, may occur multiple times or not at all. – tony_tiger Jan 09 '20 at 03:47
155

An important point that often bites new Python programmers but the other posters haven't made explicit is that strings in Python are immutable -- you can't ever modify them in place.

You need to retrain yourself when working with strings in Python so that instead of thinking, "How can I modify this string?" instead you're thinking "how can I create a new string that has some pieces from this one I've already gotten?"

bgporter
  • 35,114
  • 8
  • 59
  • 65
  • 63
    This doesn't really excuse Python from not having an indexed insert or replace! The output could just be a new string that contains the desired result. – Codie CodeMonkey Jun 28 '13 at 09:55
  • 9
    @CodieCodeMonkey the Python Zen mentions that 'Explicit is better than implicit'. You want the developer to know that he will be working on a copy. Otherwise he most certainly will run into issues with object identity which will be frustrating to debug. Thinking string - think functional. – Zakum Jul 24 '13 at 10:56
  • 13
    @Zakum, I get your point, but there are precedents for this, e.g. str.strip(). A developer who didn't read the documentation carefully might think strip() operates on the original. – Codie CodeMonkey Jul 24 '13 at 11:04
  • @CodieCodeMonkey thats actually quite a good point. Searching the string doc for 'copy' gives 9 results. I yield. ;) – Zakum Jul 24 '13 at 11:12
  • 2
    This answered the question without a single line of code. The power of a correct mindset. – addlistener Feb 12 '15 at 06:37
  • 2
    It doesn't answer the question _at all_ because OP's problem is solved. – MERose Dec 19 '19 at 10:23
  • 10
    `"how can I create a new string that has some pieces from this one I've already gotten?"` Ok, but _how_? – Det Feb 17 '20 at 13:46
  • 2
    @jchnxu the question was poorly worded but the answer completely misses the obvious point of the question, he wants to generate a string with an inserted string. The pedantics are only fine as long as he also answers the obvious spirit of the question. – Austin Salgat Dec 20 '21 at 04:04
  • .NET strings are immutable (C#, F#, PowerShell), they still have .Insert() and it's [documented](https://learn.microsoft.com/en-us/dotnet/api/system.string.insert?view=net-6.0) as "returns a copy of the string...". I downvoted because it's not helpful, just preachy. – TessellatingHeckler Dec 27 '21 at 23:26
26

I know it's malapropos (since it uses string concatenation, which isn't very efficient), but IMHO the easy way is:

def insert (source_str, insert_str, pos):
    return source_str[:pos] + insert_str + source_str[pos:]
wovano
  • 4,543
  • 5
  • 22
  • 49
Sim Mak
  • 398
  • 3
  • 6
  • @RobSmallshire What would be a more efficient method? – Jacob Jul 31 '20 at 22:45
  • 2
    @JacobJones Using the join method of str would likely be more efficient, potentially avoiding large intermediate results. return ''.join((source_str[:pos], insert_str, source_str[pos:])) – Rob Smallshire Aug 25 '20 at 07:30
  • Given that there are exactly three strings to concatenate here, the performance difference with `+` is unlikely to be noticeable. As a programmer, it's important to be wary of cargo-cult "performance" rules. If using `+` to concatenate strings were always wrong, Python wouldn't provide the functionality. – Karl Knechtel Jan 10 '23 at 01:58
11
line='Name Age Group Class Profession'
arr = line.split()
for i in range(3):
    arr.insert(2, arr[2])
print(' '.join(arr))
Michał Niklas
  • 53,067
  • 18
  • 70
  • 114
  • 1
    str.join() has the benefit of reducing memory usage since it does not create intermediate strings (although IIRC this has been optimized in recent versions of CPython/PyPy). On the other hand, it is slower than concatenation when combining only a handful of strings, and so is most useful for dealing with large strings or when you would otherwise have to perform a large number of concatenations. Under Python 3.6 you can use f-strings in lieu of concatenation to save a few additional CPU cycles when the number of substitutions is fixed (e.g., f'{source_str[:pos]}{insert_str}{source_str[pos:]}'). – kgriffs Jun 05 '19 at 22:05
4

I had a similar problem for my DNA assignment and I used bgporter's advice to answer it. Here is my function which creates a new string...

def insert_sequence(str1, str2, int):
    """ (str1, str2, int) -> str

    Return the DNA sequence obtained by inserting the 
    second DNA sequence into the first DNA sequence 
    at the given index.

    >>> insert_sequence('CCGG', 'AT', 2)
    CCATGG
    >>> insert_sequence('CCGG', 'AT', 3)
    CCGATG
    >>> insert_sequence('CCGG', 'AT', 4)
    CCGGAT
    >>> insert_sequence('CCGG', 'AT', 0)
    ATCCGG
    >>> insert_sequence('CCGGAATTGG', 'AT', 6)
    CCGGAAATTTGG

    """

    str1_split1 = str1[:int]
    str1_split2 = str1[int:]
    new_string = str1_split1 + str2 + str1_split2
    return new_string
Hom Bahrani
  • 3,022
  • 27
  • 25
  • 4
    I would not suggest naming a variable `int` since it shadows an in-built method. This can cause issues in larger programs when you try to use something like `int("5")` later on. – Kartik Soneji Mar 03 '21 at 09:26
4

There are several ways to do this:

One way is to use slicing:

>>> a="line=Name Age Group Class Profession"
>>> b=a.split()
>>> b[2:2]=[b[2]]*3
>>> b
['line=Name', 'Age', 'Group', 'Group', 'Group', 'Group', 'Class', 'Profession']
>>> a=" ".join(b)
>>> a
'line=Name Age Group Group Group Group Class Profession'

Another would be to use regular expressions:

>>> import re
>>> a=re.sub(r"(\S+\s+\S+\s+)(\S+\s+)(.*)", r"\1\2\2\2\2\3", a)
>>> a
'line=Name Age Group Group Group Group Class Profession'
Tim Pietzcker
  • 328,213
  • 58
  • 503
  • 561
0

Implementation

The functions below will allow one to insert one string into another string:

def str_insert(from_me, into_me, at):
    """
    Inserts the string <from_me> into <into_me>

    Input <at> must be an integer index of <into_me> or a substring of <into_me>

    Inserts <from_me> AFTER <at>, not before <at>

    Inputs <from_me> and <into_me> must have working __str__ methods defined.
    This is satisfied if they already are strings.

    If not already strings, <from_me>, <into_me> are converted into strings.

    If you try to insert an empty string, that's fine, and the result
    is no different from the original.

    In order to insert 'from_me' after nothing (insert at the beginning of the string) use:
        at = ''  or  at = 0
    """
    try:
        return str_insert_or_raise(from_me, into_me, at)
    except ValueError as err:
        serr = str(err)
        if (str_insert_or_raise.__name__ in serr) and 'not found' in serr and '<at>' in serr:
            # if can't find where to insert stuff, don't bother to insert it
            # use str_insert_or_raise if you want an exception instead
            return into_me
        else:
            raise err

##############################################################

def str_insert_or_raise(from_me, into_me, at):
    """
    Inserts the string <from_me> into <into_me>

    Inserts <from_me> AFTER <at>, not before <at>

    Input <at> must be an integer index of <into_me> or a substring of <into_me>

    If <at> is the string '15', that substring will be searched for,
    '15' will not be interpreted as an index/subscript.        

    Inputs <from_me> and <into_me> must have working __str__ methods defined.
    If not already strings, <from_me>, <into_me> are converted into strings. 

    If you try to insert something, but we cannot find the position where
    you said to insert it, then an exception is thrown guaranteed to at least
    contain the following three substrings:
        str_insert_or_raise.__name__
        'not found'
        '<at>'
    """
    try:
        if isinstance(at, int):
            return str_insert_by_int(from_me, into_me, at)
        # Below, the calls to str() work fine if <at> and <from_me> are already strings
        # it makes them strings if they are not already
        return str_insert_by_str(str(from_me), str(into_me), str(at))
    except ValueError as err:
        serr = str(err)
        if 'empty string' in serr:
            return into_me # We allow insertion of the empty string
        elif ("<at>" in serr) and 'not found' in serr:
            msg_start = "In " + str_insert_or_raise.__name__ + ":  "
            msg = [msg_start, "\ninput ", "<at> string", " not found in ", "<into_me>",
                              "\ninput <",   str(at)  , "> not found in <", str(into_me), ">"]
            msg = ''.join(msg)
            raise ValueError(msg) from None
        else:
           raise err
#############################################################
def str_insert_by_str(from_me, into_me, at):
    """
    Inserts the string <from_me> into <into_me>

    puts 'from_me' AFTER 'at', not before 'at'
    For example,
        str_insert_or_raise(at = '2',  from_me = '0', into_me = '123')
    puts the zero after the 2, not before the 2
    The call returns '1203' not '1023'

    Throws exceptions if input arguments are not strings.

    Also, if <from_me> is empty or <at> is not a substring of <into_me> then
    an exception is raised.

    For fewer exceptions, use <str_insert_or_raise> instead.
    """
    try:
        s = into_me.replace(at, at + from_me, 1)
    except TypeError as terr: # inputs to replace are not strings
        msg_list = ['Inputs to function ', str_insert_by_str.__name__, '() must be strings']
        raise TypeError(''.join(msg_list)) from None
    # At the end of call to replace(), the '1'  indicates we will replace
    # the leftmost occurrence of <at>, instead of every occurrence of <at>
    if (s == into_me): # <at> string not found and/or <from_me> is the empty string
        msg_start = "In " + str_insert_by_str.__name__ + ":  "
        if from_me == '':
            msg = ''.join([msg_start, "attempted to insert an empty string"])
            raise ValueError(msg) from None
        raise ValueError(msg_start, "Input <at> string not found in <into_me>.",
                                    "\nUnable to determine where you want the substring inserted.") from None
    return s
##################################################
def str_insert_by_int(from_me, into_me, at):
    """
    * Inserts the string <from_me> into <into_me> at integer index <at>    
    * throws exceptions if input arguments are not strings.    
    * Also, throws an  exception if you try to insert the empty string    
    * If <at> is less than zero, <from_me> gets placed at the
      beginning of <into_me>    
    * If <at> is greater than the largest index of <into_me>,
      <from_me> gets placed after the end of <into_me>

    For fewer exceptions, use <str_insert_or_raise> instead.
    """
    at = into_me[:(at if at > 0 else 0)]
    return str_insert_by_str(from_me, into_me, at)

Usage

The code below demonstrates how to call the str_insert function given earlier

def foo(*args):
    return args

F = 'F. '

s = 'Using the string \'John \' to specify where to make the insertion'
result = str_insert(from_me = F, into_me ='John Kennedy', at ='John ')
print(foo('\n\n', s, '\n', result))

s = 'Using an int returned by find(\'Ken\') to specify where to make the insertion'
index = 'John Kennedy'.find('Ken') # returns the position of the first letter of 'Ken', not the last letter
result = str_insert(from_me = F, into_me ='John Kennedy', at = index)
print(foo('\n\n', s, '\n', result))

s = 'Using an int (5) to specify where to make the insertion.'
result = str_insert(from_me = F, into_me ='John Kennedy', at = 5)
print(foo('\n\n', s, '\n', result))

s = "Looking for an 'at' string which does not exist"
result = str_insert(from_me = F, into_me ='John Kennedy', at ='x')
print(foo('\n\n', s, '\n', result))

s = ''.join(["Looking for the empty string.",
             "\nFind one immediately at the beginning of the string"])
result = str_insert(from_me = F, into_me ='John Kennedy', at = '')
print(foo('\n\n', s, '\n', result))

s = "Insert an empty string at index 3. No visible change"
result = str_insert(from_me = '', into_me = 'John Kennedy', at = 3)
print(foo('\n\n', s, '\n', result))    

for index in [-5, -1, 0, 1, 997, 999]:
    s = "index " + str(index)
    result = str_insert(from_me = F, into_me = 'John Kennedy', at = index)
    print(foo('\n\n', s, '\n', result))

Warning About Lack of Ability to Modify In-Place

None of the functions above will modify a string "in-place." The functions each return a modified copy of the string, but the original string remains intact.

For example,

s = ''.join(["Below is what we get when we forget ",
             "to overwrite the string with the value",
             " returned by str_insert_or_raise:"])

examp_str = 'John Kennedy'
str_insert('John ', F, examp_str)
print(foo('\n\n', s, '\n', examp_str))

# examp_str is still 'John Kennedy' without the F
Toothpick Anemone
  • 4,290
  • 2
  • 20
  • 42
0

Answer for Insert characters of string in other string al located positions

str1 = "ibuprofen"
str2 = "MEDICAL"
final_string=""
Value = 2
list2=[]
result=[str1[i:i+Value] for i in range(0, len(str1), Value)]
count = 0

for letter in result:
    if count < len(result)-1:
        final_string = letter + str2[count]
        list2.append(final_string)
    elif ((len(result)-1)==count):
        list2.append(letter + str2[count:len(str2)])
        break
    count += 1

print(''.join(list2))
pavan kumar
  • 102
  • 1
  • 2
  • 11
  • note: have answered https://stackoverflow.com/questions/53168288/insert-characters-of-string-in-other-string-al-located-positions since answering was disabled there and was duped to this – pavan kumar Nov 06 '18 at 11:26
0

Here's a simple function that extends on the above to allow inserting at an index or at any character within a string.

def insert(src, ins, at, occurrence=1, before=False):
        '''Insert character(s) into a string at a given location.
        if the character doesn't exist, the original string will be returned.

        :parameters:
            src (str) = The source string.
            ins (str) = The character(s) to insert.
            at (str)(int) = The index or char(s) to insert at.
            occurrence (int) = Specify which occurrence to insert at.
                            Valid only when 'at' is given as a string.
                            default: The first occurrence.
                            A value of -1 will insert at the last occurrence.
            before (bool) = Specify inserting before or after. default: after
                            Valid only when 'at' is given as a string.
        :return:
            (str)
        '''
        try:
            return ''.join((src[:at], str(ins), src[at:]))

        except TypeError:
            if occurrence<0: #if 'occurrance' is a negative value, search from the right.
                i = src.replace(at, ' '*len(at), occurrence-1).rfind(at)
            else:
                i = src.replace(at, ' '*len(at), occurrence-1).find(at)
            return insert(src, str(ins), i if before else i+len(at)) if i!=-1 else src

#insert '88' before the second occurrence of 'bar'
print (insert('foo bar bar bar', 88, 'bar', 2, before=True))
#result:  "foo bar 88bar bar"
m3trik
  • 333
  • 2
  • 13