0

Given

original = 'Has two optional arguments which must be specified.' 

and

strings = [{'index': 3, 'string': 'foo'}, {'index': 7, 'string': 
'bar'}, {'index': 12, 'string': 'abc'}]

what would be an efficient way (ideally by only iterating once over original) to insert all of the strings in strings to original at their specified index? The function, in this case would return 'Hasfoo twobar optiabconal arguments which must be specified.'.

For example, here is an inefficient implementation I just wrote:

def add_strings(original, strings):
    added_length = 0
    for i in strings:
        insertion_index = i['index'] + added_length
        original = original[:insertion_index] + i['string'] + 
        original[insertion_index:]
        added_length += len(i['string'])
return original
4
  • 2
    why you consider it inefficient? Commented Dec 23, 2017 at 23:59
  • I don't know, I felt like iterating over original to insertion_index twice on every iteration was kind of inefficient. It is O(n^2) (I think), which isn't that efficient. Commented Dec 24, 2017 at 0:35
  • Are the indices guaranteed to be in increasing order? Or even strictly increasing? Commented Dec 24, 2017 at 0:41
  • Yes, they are sorted. However they are not "strictly increasing," so if adding two elements who both have index == 3, the first would be added then the second. Commented Dec 24, 2017 at 1:40

4 Answers 4

2

You could first split original into a list of strings, then prepend the new strings in the positions found in strings:

original = 'Has two optional arguments which must be specified.'

strings = [{'index': 3, 'string': 'foo'}, {'index': 7, 'string': 
            'bar'}, {'index': 12, 'string': 'abc'}]

hashed = list(original)
for str_pos in strings:
    pos = str_pos['index']
    temp = hashed[pos]
    hashed[pos] = str_pos['string'] + temp

result = ''.join(hashed)

print(result)

Which outputs:

Hasfoo twobar optiabconal arguments which must be specified.
Sign up to request clarification or add additional context in comments.

5 Comments

Quite clever. Just gets the order wrong when there are duplicate insertion indices.
Oh and it doesn't allow insertion at the end of the string, resulting in an IndexError. You could solve both issues by prepending an empty string to hashed and then appending instead of prepending.
@StefanPochmann Cheers. If you have [{'index': 3, 'string': 'foo'}, {'index': 3, 'string': 'blah'}, {'index': 7, 'string': 'bar'}, {'index': 12, 'string': 'abc'}], where 3 is the duplicate insertion index, Would you not insert first 'foo' then 'blah' second?
Also wouldn't inserting at the end of the string be fine here, as long as its -1 or len(original) - 1 for the index? I'm probably missing something here, but at the moment I can't see why this wouldn't work. Unless you mean that if the index specified would go beyond the length of original, then it would insert at the last position, which leads to the Index Error.
Yes, I'd insert 'foo' first and then 'blah'. Though it's unclear what that means :-). The OP also said it just like that in the comments, so that's no help. But you produce the reverse order of what their code produces. And I think what they said sounds a little more like what their code does than what yours does. With inserting at the end, I mean after the last character. At index len(original).
1

Collecting the parts in a list, always the next part of original followed by the next string to be inserted. Then join.

def add_strings(original, strings):
    parts = []
    start = 0
    for s in strings:
        parts += original[start:s['index']], s['string']
        start = s['index']
    parts += original[start:],
    return ''.join(parts)

Comments

1

You can try this:

original = 'Has two optional arguments which must be specified.' 
strings = [{'index': 3, 'string': 'foo'}, {'index': 7, 'string': 'bar'}, {'index': 12, 'string': 'abc'}]
s = [0]+[i['index'] for i in strings]+[len(original)]
split_s = '{}'.join([original[s[i]:s[i+1]] for i in range(len(s)-1)]).format(*[i['string'] for i in strings])

Output:

'Hasfoo twobar optiabconal arguments which must be specified.'

2 Comments

Well, at least not as obviously wrong. It for example crashes if you append {} to the original string. It also iterates the string at least three times (once for extracting the parts, once for joining, and once to replace the {} placeholders (which kinda is two times, one for reading and one for writing)). And it's hard to read, partly because its so wide that I can't see it all at once and have to scroll.
@StefanPochmann you should not downvote it instant, Instead just tell the mistake, we are here for providing the solution and we do mistakes, but if we do mistakes good programmers point out our mistake and we learn from them, instead of showing their ego by downvote, no one is going to take your vote so be helpful and ready to help to your fellows .*Encourage*, Don’t Criticize; Help Instead of downvote.
0

Why making it so much complex? Just a single loop can solve your issue :

original = 'Has two optional arguments which must be specified.'
strings = [{'index': 3, 'string': 'foo'}, {'index': 7, 'string':
'bar'}, {'index': 12, 'string': 'abc'}]


meta_data=sorted([(i['index'],i['string']) for i in strings])[::-1]

splitted=list(original)

for k in meta_data:
    splitted.insert(*k)



print("".join(splitted))

output:

Hasfoo twobar optiabconal arguments which must be specified.

Let's solve your problem in three simple steps :

Data is :

original = 'Has two optional arguments which must be specified.'
strings = [{'index': 3, 'string': 'foo'}, {'index': 7, 'string':
'bar'}, {'index': 12, 'string': 'abc'}]

First step :

Just create a tuple with index_no and string which have to insert :

meta_data=sorted([(i['index'],i['string']) for i in strings])[::-1]

print(meta_data)

output:

[(12, 'abc'), (7, 'bar'), (3, 'foo')]

Second step:

turn your string into list because we want to modify it :

splitted=list(original)
print(splitted)

output:

['H', 'a', 's', ' ', 't', 'w', 'o', ' ', 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l', ' ', 'a', 'r', 'g', 'u', 'm', 'e', 'n', 't', 's', ' ', 'w', 'h', 'i', 'c', 'h', ' ', 'm', 'u', 's', 't', ' ', 'b', 'e', ' ', 's', 'p', 'e', 'c', 'i', 'f', 'i', 'e', 'd', '.']

Third step :

for k in meta_data:
    splitted.insert(k[0],k[1])

print("".join(splitted))

output:

Hasfoo twobar optiabconal arguments which must be specified.

3 Comments

Ok it's better now, just still inefficient. And while I might still prefer for i, s in, you could take advantage of your for k in by using insert(*k) instead of insert(k[0],k[1]).
@StefanPochmann indeed i tried dict approach too but yeah i agree that dict is dumb approach :D gist.github.com/exepaul/19acd0b6f6605d2fe25d98e2160bb7c7
Ugh... why do you keep using for k in in that dict approach where you're then writing k[0] or k[1] six times? Very bad.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.