How to transform string of space-separated key,value pairs of unique words into a dict
I've got a string with words that are separated by spaces (all words are unique, no duplicates). I turn this string into list:
s = "#one cat #two dogs #three birds"
out = s.split()
And count how many values are created:
print len(out) # Says 192
Then I try to delete everything from the list:
for x in out:
out.remove(x)
And then count again:
print len(out) # Says 96
Can someone explain please why it says 96 instead of 0?
MORE INFO
Each line starts with '#' and is in fact a space-separated pair of words: the first in the pair is the key and second is the value.
So, what I am doing is:
for x in out:
if '#' in x:
ind = out.index(x) # Get current index
nextValue = out[ind+1] # Get next value
myDictionary[x] = nextValue
out.remove(nextValue)
out.remove(x)
The problem is I cannot move all key,value-pairs into a dictionary since I only iterate through 96 items.
Solution 1:
As for what actually happened in the for loop:
From the Python for statement documentation:
The expression list is evaluated once; it should yield an iterable object. An iterator is created for the result of the
expression_list
. The suite is then executed once for each item provided by the iterator, in the order of ascending indices. Each item in turn is assigned to the target list using the standard rules for assignments, and then the suite is executed. When the items are exhausted (which is immediately when the sequence is empty), the suite in theelse
clause, if present, is executed, and theloop
terminates.
I think it is best shown with the aid of an illustration.
Now, suppose you have an iterable object
(such as list
) like this:
out = [a, b, c, d, e, f]
What happen when you do for x in out
is that it creates internal indexer which goes like this (I illustrate it with the symbol ^
):
[a, b, c, d, e, f]
^ <-- here is the indexer
What normally happen is that: as you finish one cycle of your loop, the indexer moves forward like this:
[a, b, c, d, e, f] #cycle 1
^ <-- here is the indexer
[a, b, c, d, e, f] #cycle 2
^ <-- here is the indexer
[a, b, c, d, e, f] #cycle 3
^ <-- here is the indexer
[a, b, c, d, e, f] #cycle 4
^ <-- here is the indexer
[a, b, c, d, e, f] #cycle 5
^ <-- here is the indexer
[a, b, c, d, e, f] #cycle 6
^ <-- here is the indexer
#finish, no element is found anymore!
As you can see, the indexer keeps moving forward till the end of your list, regardless of what happened to the list!
Thus when you do remove
, this is what happened internally:
[a, b, c, d, e, f] #cycle 1
^ <-- here is the indexer
[b, c, d, e, f] #cycle 1 - a is removed!
^ <-- here is the indexer
[b, c, d, e, f] #cycle 2
^ <-- here is the indexer
[c, d, e, f] #cycle 2 - c is removed
^ <-- here is the indexer
[c, d, e, f] #cycle 3
^ <-- here is the indexer
[c, d, f] #cycle 3 - e is removed
^ <-- here is the indexer
#the for loop ends
Notice that there are only 3 cycles there instead of 6 cycles(!!) (which is the number of the elements in the original list). And that's why you left with half len
of your original len
, because that is the number of cycles it takes to complete the loop when you remove one element from it for each cycle.
If you want to clear the list, simply do:
if (out != []):
out.clear()
Or, alternatively, to remove the element one by one, you need to do it the other way around - from the end to the beginning. Use reversed
:
for x in reversed(out):
out.remove(x)
Now, why would the reversed
work? If the indexer keeps moving forward, wouldn't reversed
also should not work because the number of element is reduced by one per cycle anyway?
No, it is not like that,
Because
reversed
method changes the way to the internal indexer works! What happened when you usereversed
method is to make the internal indexer moves backward (from the end) instead of forward.
To illustrate, this is what normally happens:
[a, b, c, d, e, f] #cycle 1
^ <-- here is the indexer
[a, b, c, d, e, f] #cycle 2
^ <-- here is the indexer
[a, b, c, d, e, f] #cycle 3
^ <-- here is the indexer
[a, b, c, d, e, f] #cycle 4
^ <-- here is the indexer
[a, b, c, d, e, f] #cycle 5
^ <-- here is the indexer
[a, b, c, d, e, f] #cycle 6
^ <-- here is the indexer
#finish, no element is found anymore!
And thus when you do one removal per cycle, it doesn't affect how the indexer works:
[a, b, c, d, e, f] #cycle 1
^ <-- here is the indexer
[a, b, c, d, e] #cycle 1 - f is removed
^ <-- here is the indexer
[a, b, c, d, e] #cycle 2
^ <-- here is the indexer
[a, b, c, d] #cycle 2 - e is removed
^ <-- here is the indexer
[a, b, c, d] #cycle 3
^ <-- here is the indexer
[a, b, c] #cycle 3 - d is removed
^ <-- here is the indexer
[a, b, c] #cycle 4
^ <-- here is the indexer
[a, b] #cycle 4 - c is removed
^ <-- here is the indexer
[a, b] #cycle 5
^ <-- here is the indexer
[a] #cycle 5 - b is removed
^ <-- here is the indexer
[a] #cycle 6
^ <-- here is the indexer
[] #cycle 6 - a is removed
^ <-- here is the indexer
Hope the illustration helps you to understand what's going on internally...
Solution 2:
I think you actually want something like this:
s = '#one cat #two dogs #three birds'
out = s.split()
entries = dict([(x, y) for x, y in zip(out[::2], out[1::2])])
What is this code doing? Let's break it down. First, we split s
by whitespace into out
as you had.
Next we iterate over the pairs in out
, calling them "x, y
". Those pairs become a list
of tuple/pairs. dict()
accepts a list of size two tuples and treats them as key, val
.
Here's what I get when I tried it:
$ cat tryme.py
s = '#one cat #two dogs #three birds'
out = s.split()
entries = dict([(x, y) for x, y in zip(out[::2], out[1::2])])
from pprint import pprint
pprint(entries)
$ python tryme.py
{'#one': 'cat', '#three': 'birds', '#two': 'dogs'}