【问题标题】:Sort array with repeated values对具有重复值的数组进行排序
【发布时间】:2021-12-13 16:30:00
【问题描述】:

我必须订购一个重复值从 0 到 9 的数组并获得向量初始索引。输入数组是:

[3, 1, 2, 0, 4, 5, 6, 7, 1, 0, 9, 5, 3, 9, 2, 7, 6, 4]

我想获得以下订单:

array([0, 1, 2, 3, 4, 5, 6, 7, 9, 0, 1, 2, 3, 4, 5, 6, 7, 9], dtype=uint8)

代替:

array([0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 9, 9])

由以下给出:

import numpy as np
a = [3, 1, 2, 0, 4, 5, 6, 7, 1, 0, 9, 5, 3, 9, 2, 7, 6, 4]
np.argsort(a)

有没有办法操作这个函数?

【问题讨论】:

  • 所有数字总是具有相同的计数吗? 0-9 每个在输入中出现两次?
  • 为什么你删除了旧版本的问题,重新提问,仍然没有解决我问你的任何澄清问题?
  • 在这种情况下,您对订单的定义没有很好地定义。如果每个元素的重复次数不同,您希望得到什么?
  • 让我再试一次:如果a 改为[1,1,4,4,2,3],结果应该是什么,为什么?
  • 如果 a 是 [1,1,4,4,2,3] 我需要对应于 [1,2,3,4,1,4] 的 a 的索引

标签: python numpy


【解决方案1】:
l = [1,2,3,4,5,6,7,8,9,4,3,5]
l_oredered = []
while len(l) != 0:
    unique_nums = list(set(l))
    unique_nums.sort()
    l_oredered.extend(unique_nums)
    for num in unique_nums:
        l.remove(num)

print(l_oredered)

这将导致:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 3, 4, 5]

你可以用 NumPy 应用这个想法,或者将最终结果转换成一个 NumPy 数组。

【讨论】:

    【解决方案2】:

    对于元素数量较少的数组,@Mr.O 的答案更快。如果 arr 中有超过 100 个整数,下面的代码会更快。

    import numpy as np
    
    def sort_groups( arr ):
        ct = np.ones( len(arr), dtype = np.int64 )
        for i in set( arr ):
            ct[arr == i] = ct[arr == i ].cumsum()
        # ct calculates a rank for each int in arr
        tosort = ( arr.max() + 1 ) * ct + arr 
        # tosort ranks by ct first then a if ct's are equal
        return arr[ np.argsort( tosort ) ]
         
    a = np.array([3, 1, 2, 0, 4, 5, 6, 7, 1, 0, 9, 5, 3, 9, 2, 7, 6, 4])
    sort_groups( a )
    # array([0, 1, 2, 3, 4, 5, 6, 7, 9, 0, 1, 2, 3, 4, 5, 6, 7, 9])
    

    分解函数以查看发生了什么:

    arr = a
    
    ct = np.ones( len(arr), dtype = np.int64 )
    for i in set( arr ):
        ct[arr == i] = ct[arr == i ].cumsum()
    
    arr, ct
    # (array([3, 1, 2, 0, 4, 5, 6, 7, 1, 0, 9, 5, 3, 9, 2, 7, 6, 4]),
    #  array([1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2]))
    
    tosort = ( arr.max() + 1 ) * ct + arr  # Assumes arr is > 0
    
    tosort
    # array([13, 11, 12, 10, 14, 15, 16, 17, 21, 20, 19, 25, 23, 29, 22, 27, 26, 24])
    
    arr[ np.argsort( tosort ) ]
    array([0, 1, 2, 3, 4, 5, 6, 7, 9, 0, 1, 2, 3, 4, 5, 6, 7, 9])
    

    【讨论】:

      【解决方案3】:

      非常有趣的任务!这是我解决问题的尝试

      import numpy as np
      
      
      def groupsort(a: np.ndarray):
          uniques, counts = np.unique(a, return_counts=True)
          min_count = np.min(counts)
          counts -= min_count
          n_easy = min_count * len(uniques)
      
          # Pre allocate array
          values = np.empty(n_easy + counts.sum(), dtype=a.dtype)
      
          # Set easy values
          temp = values[:n_easy].reshape(min_count, len(uniques))
          temp[:] = uniques
      
          # Set hard values
          i = n_easy
          while np.any(mask := counts > 0): # Python 3.8 syntax
              masksum = mask.sum()
              values[i : i + masksum] = uniques[mask]
              counts -= mask
              i += masksum
          return values
      
      
      a = np.array(list(range(4)) * 2 + [0, 1, 2, 0, 1, 2, 0, 1, 1, 1])
      np.random.shuffle(a)
      print(a)
      # [3 0 1 0 1 0 2 1 2 1 0 2 1 1 2 0 3 1]
      print(groupsort(a))
      # [0 1 2 3 0 1 2 3 0 1 2 0 1 2 0 1 1 1]
      
      # Your input
      a = np.array([3, 1, 2, 0, 4, 5, 6, 7, 1, 0, 9, 5, 3, 9, 2, 7, 6, 4])
      print(groupsort(a))
      # [0 1 2 3 4 5 6 7 9 0 1 2 3 4 5 6 7 9]
      

      这个想法是将问题分为两种情况。一个简单的案例和一个困难的案例。最简单的情况是处理这样的输入:a = [0,1,2,3,0,1,2,3],其中每个唯一值的计数相等。然后你可以简单地计算一个特定值(例如0)的数字n,然后就做list(range(max(a))) * n

      最难的情况是处理诸如a = [1,1,1,1,1,0,0,0,2,2] 之类的输入。然后想法是获取每个值的计数,在本例中为counts = [3,5,2,0]。然后做:

      values = np.empty(counts.sum())
      i = 0
      while np.any(mask := counts > 0): # Python 3.8 syntax
          masksum = mask.sum()
          values[i : i + masksum] = uniques[mask]
          counts -= mask
          i += masksum
      

      在我的解决方案中,您会看到我结合了这两种解决方案来优化速度。假设np.unique具有线性平均时间复杂度,那么该算法也具有线性平均运行时间复杂度。

      【讨论】:

        【解决方案4】:

        IIUC,你想要一个重复的、排序的数组。

        使用numpy.unique、sort 和tile 将重复值删除到预期大小:

        a = np.array([3, 1, 2, 0, 4, 5, 6, 7, 1, 0, 9, 5, 3, 9, 2, 7, 6, 4])
        b = np.unique(a)
        b = np.tile(b, len(a)//len(b))
        

        输出:

        array([0, 1, 2, 3, 4, 5, 6, 7, 9, 0, 1, 2, 3, 4, 5, 6, 7, 9])
        

        【讨论】:

        • 这很优雅!我想补充一点,np.unique 实际上对唯一值进行了排序(它在文档中指定)。使sorted 变得多余。
        • 根据原帖下的 cmets / answers 这不是解决问题的方法。那里的 OP 回答 [1,1,4,4,2,3] 应该等于 [1,2,3,4,1,4]。您的代码似乎并非如此。
        • @HampusLarsson 我没看过这个
        • 谢谢解答,我需要[3, 1, 2, 0, 4, 5, 6, 7, 1, 0, 9, 5, 3、9、2、7、6、4]。
        【解决方案5】:

        这是一个 2-liner:

        unique, counts = np.unique(a, return_counts=True)
        b = [x for y in [[u for i, u in enumerate(unique) if counts[i] > n] for n in range(counts.max())] for x in y]
        

        输出:

        >>> b
        [0, 1, 2, 3, 4, 5, 6, 7, 9, 0, 1, 2, 3, 4, 5, 6, 7, 9, 1, 5, 9]
        #^ reset                    ^ reset                    ^ reset
        

        【讨论】:

          【解决方案6】:

          我更喜欢使用np.bincount 而不是np.uniquenp.sortnp.argsort,因为在最大数据项很小的情况下它会更快。

          def count_out(arr, N):
              bins = np.bincount(arr, minlength=N) 
              threshold_idx = np.unique(bins[bins!=0]) 
              counts = np.diff(threshold_idx, prepend=0)
              mask = (bins >= threshold_idx[:, None])
              full_mask = np.repeat(mask, counts, axis=0)
              blocks = np.repeat([np.arange(N)], np.sum(counts), axis=0)
              return blocks[full_mask]
          
          N = 10
          X = np.array([3, 5, 3, 9, 9, 9, 9, 0, 0, 6, 8, 8, 7, 0, 5, 9, 7, 8, 1, 5, 8, 8, 1, 0, 7, 1, 9])
          print(X)
          print(count_out(X, N))
          >>> [3 5 1 3 9 7 5 9 0 9 9 0 0 6 8 8 8 9 7 0 5 9 7 8 3 1 5 8 8 1 0 7 1 9 9 8]
          >>> [0 1 3 5 6 7 8 9 0 1 3 5 7 8 9 0 1 3 5 7 8 9 0 1 5 7 8 9 0 8 9 8 9 8 9 9]
          

          关键思想是找出每个块重复多少次的counts。然后为每个块创建唯一的掩码:

          方块:

          [[0 1 2 3 4 5 6 7 8 9]
           [0 1 2 3 4 5 6 7 8 9]
           [0 1 2 3 4 5 6 7 8 9]
           [0 1 2 3 4 5 6 7 8 9]
           [0 1 2 3 4 5 6 7 8 9]
           [0 1 2 3 4 5 6 7 8 9]]
          

          独特的面具:

          [[1 1 0 1 0 1 1 1 1 1]
           [1 1 0 1 0 1 0 1 1 1]
           [1 1 0 0 0 1 0 1 1 1]
           [1 0 0 0 0 0 0 0 1 1]
           [0 0 0 0 0 0 0 0 1 1]
           [0 0 0 0 0 0 0 0 0 1]]
          

          最后,用我们得到的counts重构所有的掩码。

          计数:[1 2 1 1 2 1]

          完整的面具:

          [[1 1 0 1 0 1 1 1 1 1]
           [1 1 0 1 0 1 0 1 1 1]
           [1 1 0 1 0 1 0 1 1 1]
           [1 1 0 0 0 1 0 1 1 1]
           [1 0 0 0 0 0 0 0 1 1]
           [0 0 0 0 0 0 0 0 1 1]
           [0 0 0 0 0 0 0 0 1 1]
           [0 0 0 0 0 0 0 0 0 1]]
          

          顺便说一句,这似乎可以进一步优化。首先,重复块的创建是多余的,因为应该有一种方法可以创建指向单个块的指针。其次,如果全掩码稀疏,它会很慢。在这种情况下,您应该考虑实现 your own way to repeat blocks 而不使用屏蔽。

          希望目前对你有所帮助。

          【讨论】:

            猜你喜欢
            • 2017-04-12
            • 2021-08-22
            • 2021-02-15
            • 2012-09-26
            • 2021-06-25
            • 2019-09-20
            • 2014-06-22
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多