【问题标题】:Python: Rename duplicates in list with progressive numbers without sorting listPython:用渐进式数字重命名列表中的重复项而不对列表进行排序
【发布时间】:2015-08-19 10:57:05
【问题描述】:

给定这样的列表:

mylist = ["name", "state", "name", "city", "name", "zip", "zip"]

我想通过附加一个数字来重命名重复项以获得以下结果:

mylist = ["name1", "state", "name2", "city", "name3", "zip1", "zip2"]

我不想更改原始列表的顺序。为此related Stack Overflow question 建议的解决方案对列表进行排序,我不想这样做。

【问题讨论】:

    标签: python list duplicates rename


    【解决方案1】:

    我使用maplambda 的解决方案:

    print map(lambda x: x[1] + str(mylist[:x[0]].count(x[1]) + 1) if mylist.count(x[1]) > 1 else x[1], enumerate(mylist))
    

    更传统的形式

    newlist = []
    for i, v in enumerate(mylist):
        totalcount = mylist.count(v)
        count = mylist[:i].count(v)
        newlist.append(v + str(count + 1) if totalcount > 1 else v)
    

    最后一个

    [v + str(mylist[:i].count(v) + 1) if mylist.count(v) > 1 else v for i, v in enumerate(mylist)]
    

    【讨论】:

    • lambda 1 衬里有点复杂,但你的第二个建议很好。谢谢!
    • 添加了一个类似于 Rick Teachey 解决方案但保留顺序的衬垫
    • 这个解决方案实际上很昂贵,因为它需要 O(n^2)
    • 谢谢!我真的很佩服根据列表切片的计数使列表项唯一是多么聪明,直到 [:i] (在最后一个列表理解解决方案中),我真诚的赞美!!!
    【解决方案2】:

    我会这样做。编辑:我把它写成一个更通用的实用函数,因为人们似乎喜欢这个答案。

    mylist = ["name", "state", "name", "city", "name", "zip", "zip"]
    check = ["name1", "state", "name2", "city", "name3", "zip1", "zip2"]
    copy = mylist[:]  # so we will only mutate the copy in case of failure
    
    from collections import Counter # Counter counts the number of occurrences of each item
    from itertools import tee, count
    
    def uniquify(seq, suffs = count(1)):
        """Make all the items unique by adding a suffix (1, 2, etc).
    
        `seq` is mutable sequence of strings.
        `suffs` is an optional alternative suffix iterable.
        """
        not_unique = [k for k,v in Counter(seq).items() if v>1] # so we have: ['name', 'zip']
        # suffix generator dict - e.g., {'name': <my_gen>, 'zip': <my_gen>}
        suff_gens = dict(zip(not_unique, tee(suffs, len(not_unique))))  
        for idx,s in enumerate(seq):
            try:
                suffix = str(next(suff_gens[s]))
            except KeyError:
                # s was unique
                continue
            else:
                seq[idx] += suffix
    
    uniquify(copy)
    assert copy==check  # raise an error if we failed
    mylist = copy  # success
    

    如果您想在每个计数前添加下划线,您可以执行以下操作:

    >>> mylist = ["name", "state", "name", "city", "name", "zip", "zip"]
    >>> uniquify(mylist, (f'_{x!s}' for x in range(1, 100)))
    >>> mylist
    ['name_1', 'state', 'name_2', 'city', 'name_3', 'zip_1', 'zip_2']
    

    ...或者如果您想改用字母:

    >>> mylist = ["name", "state", "name", "city", "name", "zip", "zip"]
    >>> import string
    >>> uniquify(mylist, (f'_{x!s}' for x in string.ascii_lowercase))
    >>> mylist
    ['name_a', 'state', 'name_b', 'city', 'name_c', 'zip_a', 'zip_b']
    

    注意:这不是最快的算法;为此,请参阅the answer by ronakg。上述函数的优点是易于理解和阅读,除非您有一个非常大的列表,否则您不会看到太大的性能差异。

    编辑:这是我在单行中的原始答案,但是订单没有保留,它使用了.index 方法,这是非常不理想的(如the answer by DTing 中所述)。请参阅the answer by queezz 了解保持秩序的漂亮“双线”。

    [s + str(suffix) if num>1 else s for s,num in Counter(mylist).items() for suffix in range(1, num+1)]
    # Produces: ['zip1', 'zip2', 'city', 'state', 'name1', 'name2', 'name3']
    

    【讨论】:

    • 这是我最喜欢的建议,因为它紧凑且易于阅读。谢谢!
    【解决方案3】:

    在每个元素上调用count 的任何方法都将导致O(n^2),因为countO(n)。你可以这样做:

    # not modifying original list
    from collections import Counter
    
    mylist = ["name", "state", "name", "city", "name", "zip", "zip"]
    counts = {k:v for k,v in Counter(mylist).items() if v > 1}
    newlist = mylist[:]
    
    for i in reversed(range(len(mylist))):
        item = mylist[i]
        if item in counts and counts[item]:
            newlist[i] += str(counts[item])
            counts[item]-=1
    print(newlist)
    
    # ['name1', 'state', 'name2', 'city', 'name3', 'zip1', 'zip2']
    

    # modifying original list
    from collections import Counter
    
    mylist = ["name", "state", "name", "city", "name", "zip", "zip"]
    counts = {k:v for k,v in Counter(mylist).items() if v > 1}      
    
    for i in reversed(range(len(mylist))):
        item = mylist[i]
        if item in counts and counts[item]:
            mylist[i] += str(counts[item])
            counts[item]-=1
    print(mylist)
    
    # ['name1', 'state', 'name2', 'city', 'name3', 'zip1', 'zip2']
    

    这应该是O(n)

    其他提供的答案:

    mylist.index(s) 每个元素导致O(n^2)

    mylist = ["name", "state", "name", "city", "name", "zip", "zip"]
    
    from collections import Counter
    counts = Counter(mylist)
    for s,num in counts.items():
        if num > 1:
            for suffix in range(1, num + 1):
                mylist[mylist.index(s)] = s + str(suffix) 
    

    count(x[1]) 每个元素导致O(n^2)
    它还与列表切片一起用于每个元素多次。

    print map(lambda x: x[1] + str(mylist[:x[0]].count(x[1]) + 1) if mylist.count(x[1]) > 1 else x[1], enumerate(mylist))
    

    基准测试:

    http://nbviewer.ipython.org/gist/dting/c28fb161de7b6287491b

    【讨论】:

    • 根据您的建议,我更改了答案以取消 .index。好多了。
    【解决方案4】:

    这是一个非常简单的O(n) 解决方案。只需遍历存储列表中元素索引的列表。如果我们以前见过这个元素,请使用之前存储的数据来附加出现值。

    这种方法解决了这个问题,只需再创建一个字典进行回顾。避免进行前瞻,这样我们就不会创建临时列表切片。

    mylist = ["name", "state", "name", "city", "city", "name", "zip", "zip", "name"]
    
    dups = {}
    
    for i, val in enumerate(mylist):
        if val not in dups:
            # Store index of first occurrence and occurrence value
            dups[val] = [i, 1]
        else:
            # Special case for first occurrence
            if dups[val][1] == 1:
                mylist[dups[val][0]] += str(dups[val][1])
    
            # Increment occurrence value, index value doesn't matter anymore
            dups[val][1] += 1
    
            # Use stored occurrence value
            mylist[i] += str(dups[val][1])
    
    print mylist
    
    # ['name1', 'state', 'name2', 'city1', 'city2', 'name3', 'zip1', 'zip2', 'name4']
    

    【讨论】:

    • 这很好,但你见过collections.Counter吗?使用它,您不必实现自己的计数算法。
    • 是的,我知道collections.Counter :)。我只是想发布一个更有效的解决方案。此解决方案只执行一次列表。
    【解决方案5】:

    Rick Teachey answer, "two-liner" 的列表理解版本:

    from collections import Counter
    
    m = ["name", "state", "name", "city", "name", "zip", "zip"]
    
    d = {a:list(range(1, b+1)) if b>1 else '' for a,b in Counter(m).items()}
    [i+str(d[i].pop(0)) if len(d[i]) else i for i in m]
    #['name1', 'state', 'name2', 'city', 'name3', 'zip1', 'zip2']
    

    【讨论】:

    • 非常好的答案,但您需要执行 range(1,b+1) 以获得所需的后缀。
    • 当然,范围(1,b+1)。在笔记本中使用一些旧变量时一定错过了索引。谢谢。
    【解决方案6】:

    您可以使用哈希表来解决这个问题。定义字典 ​​D.键是字符串,值是 (first_time_index_in_the_list, times_of_appearance)。每次看到一个词,就查字典,如果值为2,则使用first_time_index_in_the_list将'1'附加到第一个元素,并将times_of_appearance附加到当前元素。如果大于 2,只需将 times_of_appearance 附加到当前元素。

    【讨论】:

    • 如果有示例代码就好了,眼睛能更快地实现逻辑。
    【解决方案7】:

    不那么花哨的东西。

    from collections import defaultdict
    mylist = ["name", "state", "name", "city", "name", "zip", "zip"]
    finalList = []
    dictCount = defaultdict(int)
    anotherDict = defaultdict(int)
    for t in mylist:
       anotherDict[t] += 1
    for m in mylist:
       dictCount[m] += 1
       if anotherDict[m] > 1:
           finalList.append(str(m)+str(dictCount[m]))
       else:
           finalList.append(m)
    print finalList
    

    【讨论】:

      【解决方案8】:

      注意原始列表中已存在的更新值

      如果起始列表已经包含一个项目"name2" ...

      mylist = ["name", "state", "name", "city", "name", "zip", "zip", "name2"]
      

      ...那么函数运行时mylist[2]不应该更新为"name2",否则会创建一个新的副本;相反,该函数应该跳转到下一个可用的项目名称"name3"

      mylist_updated = ['name1', 'state', 'name3', 'city', 'name4', 'zip1', 'zip2', 'name2']
      

      这是一个替代解决方案(可能会被缩短和优化),其中包括一个递归函数,用于检查原始列表中的这些现有项目。

      mylist = ["name", "state", "name", "city", "name", "zip", "zip", "name2"]
      
      def fix_dups(mylist, sep='', start=1, update_first=True):
          mylist_dups = {}
          #build dictionary containing val: [occurrences, suffix]
          for val in mylist:
              if val not in mylist_dups:
                  mylist_dups[val] = [1, start - 1]
              else:
                  mylist_dups[val][0] += 1
                  
          #define function to update duplicate values with suffix, check if updated value already exists
          def update_val(val, num):
              temp_val = sep.join([str(x) for x in [val, num]])
              if temp_val not in mylist_dups:
                  return temp_val, num
              else:
                  num += 1
                  return update_val(val, num)        
          
          #update list
          for i, val in enumerate(mylist):
              if mylist_dups[val][0] > 1:
                  mylist_dups[val][1] += 1  
                  if update_first or mylist_dups[val][1] > start:
                      new_val, mylist_dups[val][1] = update_val(val, mylist_dups[val][1])
                      mylist[i] = new_val
      
          return mylist
                      
      mylist_updated = fix_dups(mylist, sep='', start=1, update_first=True)
      print(mylist_updated)
      #['name1', 'state', 'name3', 'city', 'name4', 'zip1', 'zip2', 'name2']
      

      如果您不想更改第一个匹配项。

      mylist = ["name", "state", "name", "city", "name", "zip", "zip", "name_2"]
                   
      mylist_updated = fix_dups(mylist, sep='_', start=0, update_first=False)
      print(mylist_updated)
      #['name', 'state', 'name_1', 'city', 'name_3', 'zip', 'zip_1', 'name_2']
      

      【讨论】:

        猜你喜欢
        • 2014-07-23
        • 1970-01-01
        • 1970-01-01
        • 2015-11-14
        • 2013-08-11
        • 1970-01-01
        • 1970-01-01
        • 2012-07-22
        • 2019-07-01
        相关资源
        最近更新 更多