如何将由空格分隔的独特单词键值对字符串转换为字典

9

我有一个由空格分隔的单词字符串(所有单词都是唯一的,没有重复)。我将这个字符串转换成了列表:

s = "#one cat #two dogs #three birds"
out = s.split()

并计算创建了多少个值:

print len(out) # Says 192 

那么我尝试从列表中删除所有内容:
for x in out:
     out.remove(x)

然后再重新计数:
print len(out) # Says 96 

请问为什么它显示的是96而不是0?

更多信息:

每行都以“#”开头,实际上是一对用空格分隔的单词:第一对是键,第二个是值。

所以,我正在做的是:

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) 

问题在于我无法将所有的键值对都移动到字典中,因为我只能迭代96个项目。

2
那我试着从列表中删除所有内容 -- 让我们深入探讨一下。你为什么想要这样做? - Brian Cain
因为我正在将列表中的值成对地移动到字典中,例如第一对中的第一个值是键,第二个值是值。问题在于我的字典不完整,因为我无法完全遍历列表 - 它显示只有96个索引。 - magic_turtle
如果您能够附上您正在使用的字符串和字典的代码,那么我们提供解决方案会更容易些。问题可能存在于您迄今为止提供给我们的内容之外。 - MKreegs
请查看此链接:https://dev59.com/p3M_5IYBdhLWcg3w1G6N - g-217
2
您不应该从正在迭代的列表中删除项目。这是未定义的行为。最好使用列表推导式。 - g-217
9个回答

13

关于for循环中实际发生的事情:

根据Python for语句文档

表达式列表被执行一次,它应该返回一个可迭代对象。为expression_list的结果创建一个迭代器。然后按照索引递增的顺序每次为迭代器提供的每个项目执行一次套件。每个项目依次使用分配的列表目标进行分配,然后执行套件。当项用尽时(即序列为空时)执行else子句中的套件(如果存在),并且loop终止了。

我认为最好通过一个图示来展示。

现在,假设您有一个像这样的iterable对象(例如list):

out = [a, b, c, d, e, f]
当您执行for x in out时,会创建一个内部索引器,其运行方式如下(我用符号^来说明):
[a, b, c, d, e, f]
 ^  <-- here is the indexer

通常情况下发生的是:当您完成循环的一个周期时,索引器向前移动,如下所示:

[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!
正如您所看到的,索引器会一直向前移动到列表末尾,无论列表发生了什么!因此,当您执行remove时,内部会发生以下情况:
[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

注意,这里只有3个循环,而不是原始列表中的6个循环(!!)。这就是为什么在每个循环中删除一个元素后,留下一半len,因为这是完成循环所需的周期数。


如果你想清空列表,只需要执行:

if (out != []):
    out.clear()

或者,另一种方法是逐个删除元素,你需要 反过来 - 从尾部到头部 进行操作。使用 reversed

for x in reversed(out):
    out.remove(x)

现在,为什么会有reversed方法?如果索引器一直往前移,那么每个周期元素的数量都会减少一个,reversed也不应该起作用,对吗?

不,不是这样的,

因为reversed方法改变了内部索引器的工作方式!使用reversed方法时发生的情况是,将内部索引器向后移动(从末尾开始),而不是向前移动

为了举例说明,这是通常发生的情况:

[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!

因此,当您每个周期执行一次删除操作时,它不会影响索引器的工作:

[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

希望这个插图能帮助你理解内部发生了什么...


1
这是我见过的关于迭代期间修改的最好解释之一。我希望我能多次点赞! - Chinmay Kanchi
1
你对我的回答进行了明确的解释!非常感谢你! - magic_turtle
1
刚想发布一个小例子,感谢您回答实际问题! - pyInTheSky

8
我认为你实际上想要的是这样的内容:

我认为您实际上需要的是这样的内容:

s = '#one cat #two dogs #three birds'
out = s.split()
entries = dict([(x, y) for x, y in zip(out[::2], out[1::2])])

这段代码在做什么?我们来分解一下。首先,我们按空格将s分割成out,就像你所做的那样。
接下来,我们循环遍历out中的对,称它们为“x,y”。这些对会变成一个元组/对的列表dict()接受一个大小为二的元组列表,并把它们视为key,val
当我试着运行它时,我得到了以下结果:
$ 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'}

没错,可以用!非常感谢。我能否在这个过程中加入一个条件呢? 因为我需要在将某些值传递到字典之前修改它们。 我需要删除哈希标志,通过 entries = dict([(x [1:],y) for x,y in zip(out [:: 2],out [1:: 2])])已经实现了此操作,但在某些情况下我需要修改某些字符,例如,如果单词包含“keyword”,则需要将其替换为另一个单词。 - magic_turtle
将“列表推导式”重写为传统的for循环。第一行类似于“for key, val in zip(out[::2], out[1::2]):”,然后您可以对keyval进行任何操作。然后像这样将它们添加到字典中:entries[key] = val。不要忘记在开始时创建字典,entries = {},例如。 - Brian Cain
谢谢!非常感谢你的帮助! - magic_turtle

3

你没有具体说明。为什么要尝试删除 out-list 中的所有内容?如果您只需要清除 out-list,为什么不直接这样做:

out = []

2
你遇到的问题是在遍历列表时进行了修改。当一个项目被删除后,其后的所有项目都会向前移动一个索引位置,但迭代器不会考虑这个变化,并继续通过增加它最后访问的索引来进行迭代。因此,迭代器跳过列表中每隔一个元素,这就是为什么你最终只剩下一半元素的原因。
直接解决该问题的最简单方法是使用切片符号遍历 out复制品
for x in out[:]:
    # ...
    out.remove(x)

然而,这里还有一个更深层次的问题:为什么你需要从列表中删除项目?使用你的算法,你保证最终会得到一个空列表,这对你没有任何用处。更简单和更有效的方法是在不删除项目的情况下遍历整个列表。
当你完成列表的操作后(在for循环块之后),你可以显式地删除它(使用del关键字)或者让Python的垃圾回收系统处理。
进一步的问题是:你将直接迭代列表与基于索引的引用相结合。使用"for x in out"通常应限制在想要独立访问每个元素的情况下。如果你想使用索引进行操作,请使用"for i in range(len(out))"并使用"out[i]"访问元素。
此外,你可以使用"字典推导式"在一行Python表达式中完成整个任务。
my_dictionary = {out[i]: out[i + 1] for i in range(len(out)) if "#" in out[i]}

另一个符合Python习惯的方法是利用每个偶数元素都是键,每个奇数元素都是值的事实(您需要假设str.split()的列表结果始终遵循此模式),并在偶数和奇数子列表上使用zip
my_dictionary = dict(zip(out[::2], out[1::2]))

2
我相信您想要以下内容。
>>> a = '#one cat #two dogs #three birds'
>>> b = { x.strip().split(' ')[0] : x.strip().split(' ')[-1] for x in a.strip().split('#') if len(x) > 0 }
>>> b
{'three': 'birds', 'two': 'dogs', 'one': 'cat'}

甚至更好的是
>>> b = [ y   for x in a.strip().split('#') for y in x.strip().split(' ') if len(x) > 0 ]
>>> c = { x: y for x,y  in zip(b[0::2],b[1::2]) }
>>> c
{'three': 'birds', 'two': 'dogs', 'one': 'cat'}
>>> 

1
如果你只需要清空列表,使用 out = [] 或者 out.clear()。无论如何,你说的是因为列表的 remove 函数会影响列表本身。
out = ['a', 'b', 'c', 'd', 'e', 'f']
for x in out:
    out.remove(x)
    print(x)

然后结果如下所示:

a c e

这正好是完整列表的一半。因此,在您的情况下,您从192中得到了96(192的一半)。


1
问题在于当您从列表中删除一个值时,该特定列表会动态地恢复其值。 也就是说,当您执行 out.remove(ind)out.remove(ind+1) 时,这些索引中的值被删除, 但它们被替换为先前值的前任新值。
因此,为了避免这种情况,请按以下方式实现代码:
out = []
out = '#one cat #two dogs #three birds'.split()

print "The list is : {0} \n".format(out)
myDictionary = dict()

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 = []  # #emptying the list
print("The dictionary is : {0} \n".format(myDictionary))

因此,在将列表中的值转移到字典后,我们可以使用out = []安全地清空out


0
问题在于您在迭代时使用了 remove(x)。'out' 变量同时被 remove 函数和 for 循环引用。
只需使用
for i in range(len(out)):
     out.remove(out[i]);

0

首先,您需要在“#”上进行拆分以获取每个记录(键值对字符串)。然后,您需要在空格上拆分每个o,以便得到一个[key,value]列表。 dict()允许您直接从键值对列表构建字典。所以:

>>> dict( k_v.split() for k_v in s.split('#')[1:] )
{'one': 'cat', 'two': 'dogs', 'three': 'birds'}

(注:我们不得不使用s.split('#')[1:]跳过第一个(空白)记录)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接