Looking for a elegant way to convert a list of substrings and the text between them to key, value pairs in a dict. Example:
s = 'k1:some text k2:more text k3:and still more'
key_list = ['k1','k2','k3']
(missing code)
# s_dict = {'k1':'some text', 'k2':'more text', 'k3':'and still more'}
This is solvable using str.find()
, etc, but I know there's a better solution than what I've hacked together.
Method 1: Splitting a string to generate key:value pair of the dictionary In this approach, the given string will be analysed and with the use of split() method, the string will be split in such a way that it generates the key:value pair for the creation of a dictionary.
To convert a string to dictionary, we have to ensure that the string contains a valid representation of dictionary. This can be done by eval() function. Abstract Syntax Tree (ast) module of Python has literal_eval() method which safely evaluates valid Python literal structure.
Using zip and dictThe dict() can be used to take input parameters and convert them to a dictionary. We also use the zip function to group the keys and values together which finally become the key value pair in the dictionary.
To convert a list to dictionary, we can use list comprehension and make a key:value pair of consecutive elements. Finally, typecase the list to dict type.
Option 1
If the keys don't have spaces or colons, you can simplify your solution with dict
+ re.findall
(import re
, first):
>>> dict(re.findall('(\S+):(.*?)(?=\s\S+:|$)', s))
{'k1': 'some text', 'k2': 'more text', 'k3': 'and still more'}
Only the placement of the colon (:
) determines how keys/values are matched.
Details
(\S+) # match the key (anything that is not a space)
: # colon (not matched)
(.*?) # non-greedy match - one or more characters - this matches the value
(?= # use lookahead to determine when to stop matching the value
\s # space
\S+: # anything that is not a space followed by a colon
| # regex OR
$) # EOL
Note that this code assumes the structure as presented in the question. It will fail on strings with invalid structures.
Option 2
Look ma, no regex...
This operates on the same assumption as the one above.
:
)v = s.split(':')
v[1:-1] = [j for i in v[1:-1] for j in i.rsplit(None, 1)]
dict(zip(v[::2], v[1::2]))
{'k1': 'some text', 'k2': 'more text', 'k3': 'and still more'}
If the keys don't have spaces or colons in it, you could:
like this:
import re,itertools
s = 'k1:some text k2:more text k3:and still more'
toks = [x for x in re.split("(\w+):",s) if x] # we need to filter off empty tokens
# toks => ['k1', 'some text ', 'k2', 'more text ', 'k3', 'and still more']
d = {k:v for k,v in zip(itertools.islice(toks,None,None,2),itertools.islice(toks,1,None,2))}
print(d)
result:
{'k2': 'more text ', 'k1': 'some text ', 'k3': 'and still more'}
using itertools.islice
avoids to create sub-lists like toks[::2]
would do
Another regex magic with splitting the input string on key/value pairs:
import re
s = 'k1:some text k2:more text k3:and still more'
pat = re.compile(r'\s+(?=\w+:)')
result = dict(i.split(':') for i in pat.split(s))
print(result)
The output:
{'k1': 'some text', 'k2': 'more text', 'k3': 'and still more'}
re.compile()
and saving the resulting regular expression object for reuse is more efficient when the expression will be used several times in a single program\s+(?=\w+:)
- the crucial pattern to split the input string by whitespace character(s) \s+
if it's followed by a "key"(a word \w+
with colon :
).(?=...)
- stands for lookahead positive assertionIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With