【问题标题】:Sort string with integers and words without any change in their positions用整数和单词对字符串进行排序而不改变它们的位置
【发布时间】:2017-11-15 15:48:55
【问题描述】:

假设我有一个字符串 a。

a = "12 I have car 8 200 a"

我需要对这个字符串进行排序,使得输出应该是

8 a car have 12 200 I

即,对字符串进行排序,使所有单词按字母顺序排列,所有整数按数字顺序排列。此外,如果字符串中的第 n 个元素是整数,则它必须保持为整数,如果是单词,则必须保持为单词。

这是我尝试过的。

a = "12 I have car 8 200 a"


def is_digit(element_):
    """
    Function to check the item is a number. We can make using of default isdigit function
    but it will not work with negative numbers.
    :param element_:
    :return: is_digit_
    """
    try:
        int(element_)
        is_digit_ = True
    except ValueError:
        is_digit_ = False

    return is_digit_



space_separated = a.split()

integers = [int(i) for i in space_separated if is_digit(i)]
strings = [i for i in space_separated if i.isalpha()]

# sort list in place
integers.sort()
strings.sort(key=str.lower)

# This conversion to iter is to make use of next method.
int_iter = iter(integers)
st_iter = iter(strings)

final = [next(int_iter) if is_digit(element) else next(st_iter) if element.isalpha() else element for element in
         space_separated]

print " ".join(map(str, final))
# 8 a car have 12 200 I

我得到了正确的输出。但是我使用两个单独的排序函数对整数和单词进行排序(我认为这很昂贵)。

是否可以使用单个排序函数完成整个排序?

【问题讨论】:

    标签: python python-2.7 performance sorting iterator


    【解决方案1】:

    numpy 允许更简洁地编写它,但不会消除对两个单独排序的需要:

    $ python3
    Python 3.5.2 (default, Nov 23 2017, 16:37:01) 
    [GCC 5.4.0 20160609] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import numpy as np
    >>> from numpy.core.defchararray import isdecimal, lower
    >>> 
    >>> s = "12 I have car 8 200 a"
    >>> 
    >>> a = np.array(s.split())
    >>> 
    >>> integer_mask = isdecimal(a)
    >>> string_mask = ~integer_mask
    >>> strings = a[string_mask]
    >>> 
    >>> a[integer_mask] = np.sort(np.int_(a[integer_mask]))
    >>> a[string_mask]  = strings[np.argsort(lower(strings))]
    >>> 
    >>> ' '.join(a)
    '8 a car have 12 200 I'
    

    【讨论】:

      【解决方案2】:

      是否可以使用单个排序函数完成整个排序?

      不,不是。

      为什么不呢?原来答案已经在你的代码中了。

      integers.sort()
      strings.sort(key=str.lower)
      

      请注意这里您需要如何按两个不同的函数进行排序。第一个是整数排序,第二个是小写字符串排序。我们可以试试这样的:

      def get_sort_order(element):
          try:
              value = int(element)
          except ValueError:
              value = element.lower()
          return value
      
      a.sort(key=get_sort_order)
      

      但这也不行;它只是给了我们结果

      ['8', '12', '200', 'a', 'car', 'have', 'I']
      

      您可能会强制将此作为解决方案,但它不会很漂亮。

      不过,还有一点我想说明:

      但我使用两个单独的排序函数来排序整数和单词(我认为这很昂贵)。

      无论如何,对两个不同的列表进行排序基本上总是会更快。要找出原因,只需看看这两个任务的时间复杂度:

      假设一个长度为1000的列表,正好是半个整数和半个字符串,排序算法为O(nlog(n)):

      单次排序:1000 * log(1000) = 3000

      两种不同的排序:2 * (500 * log(500) = ~2699

      因此在一次运行中对列表进行排序既更难实现,也更慢!

      【讨论】:

      • 您必须考虑将列表分成两部分的复杂性。那么总复杂度是 = o(n)[这是为了将列表分成两个] + 2 (n/2log(n/2)) 这将大于单一排序。
      • @Knight71 通常在考虑时间复杂度时会丢弃低阶项,因此 O(n + nlog(n)) = O(nlog(n))。这不是一个完美的系统,但它基于这样的想法,即 n->infinity 只有大项才重要。无论哪种方式,如果您选择一次对整个列表进行排序,您最终会在每次比较中添加至少一个额外的操作,这将增加大量时间。
      【解决方案3】:

      通过在“排序”方法中应用自定义函数作为上述用户,这是一种可能的。我已经尝试过相同的简化版本。默认的“排序”方法只需稍加调整即可实现奇迹。希望它能解决您的问题。

      import re
      
      input = "12 I have car 8 200 a"
      splitted = input.split()
      s_lst=sorted(splitted, key=lambda a:int(a) if a.isdigit() else a.lower())
      
      count_nos = re.findall(r'\d+',' '.join(s_lst))
      str_index = len(count_nos)
      no_index = 0
      result=[]
      for i in range(0,len(splitted)):
          if splitted[i].isdigit():
              result.append(s_lst[no_index])
              no_index+=1
          else:
              result.append(s_lst[str_index])
              str_index+=1
      print ' '.join(result)
      

      【讨论】:

      • 请注意,这不适用于 Python 3:TypeError: unorderable types: str() < int()
      • @Leon 是的仪式。在 python3 中,除非类型转换正确,否则不允许比较 str 和 int。
      【解决方案4】:

      只要您编写一个自定义函数进行比较,您就可以这样做。 这个想法是在同一个列表中按升序对单词进行排序,并按降序对整数进行排序。比较单词和整数的大小写,然后将单词视为比单词更小。

      然后为了打印最终结果,如果找到单词,则增加 word 的索引,如果找到 digit,则减少整数的索引。

      以下代码在python2中工作:

      a = "12 I have car 8 200 a"
      
      def custom_compare(x,y):
          if x.isdigit() and y.isdigit():
              return int(y) - int(x) #do a descending order
          if x.isdigit() and y.isdigit() == False:
              return 1
          if x.isdigit() == False and y.isdigit():
              return -1
          if x.isdigit() == False and y.isdigit() == False:
              # do ascending order
              if x.lower() == y.lower():
                  return 0
              elif x.lower() < y.lower():
                  return -1
              else:
                  return 1
      
      original_list = a.split(" ")
      sorted_list = sorted(original_list, cmp=custom_compare)
      
      result = []
      integer_index = -1
      string_index = 0
      for word in original_list:
          if word.isdigit():
              result.append(sorted_list[integer_index])
              integer_index = integer_index - 1
          else:
              result.append(sorted_list[string_index])
              string_index = string_index + 1
      
      result
      ['8', 'a', 'car', 'have', '12', '200', 'I']
      

      Python 3: 导入函数工具

      a = "12 I have car 8 200 a"
      
      def custom_compare(x,y):
          if x.isdigit() and y.isdigit():
              return int(y) - int(x) #do a descending order
          if x.isdigit() and y.isdigit() == False:
              return 1
          if x.isdigit() == False and y.isdigit():
              return -1
          if x.isdigit() == False and y.isdigit() == False:
              # do ascending order
              if x.lower() == y.lower():
                  return 0
              elif x.lower() < y.lower():
                  return -1
              else:
                  return 1
      
      original_list = a.split(" ")
      sorted_list = sorted(original_list, key=functools.cmp_to_key(custom_compare))
      
      result = []
      integer_index = -1
      string_index = 0
      for word in original_list:
          if word.isdigit():
              result.append(sorted_list[integer_index])
              integer_index = integer_index - 1
          else:
              result.append(sorted_list[string_index])
              string_index = string_index + 1
      
      result
      

      PS:单词比较可以高效编写。 我来自 C 背景,我不确定 Python 的比较方式。

      【讨论】:

      • 我相信你的意思是key=custom_compare,而不是cmp=custom_compare,因为它目前崩溃了。此外,custom_compare 不能通过名称传递,因为它需要两个参数。 xy。我建议您为输入中的每个元素使用带有自定义 __lt__ 的类包装器,因为 sorted 无法让您访问正在比较的两个元素。
      • 它只适用于python2。 Python3 用 key 函数去掉了这个
      • 是的,它与Key而不是Cmp的参数相同
      【解决方案5】:
      s = "2 is a A -3 car 11 I 0 a"
      
      def magick(s):
        s = s.split()
      
        def reverse(tuples):
          return [(a, b) for (b, a) in tuples]
      
        def do_sort(tuples):
          firsts  = [a for a, _ in tuples]
          seconds = [a for _, a in tuples]
          return list(zip(sorted(firsts), seconds))
      
        def str_is_int(x):
          try:
            int(x)
            return True
          except:
            return False
      
        indexed = list(enumerate(s))
      
        ints = do_sort([(int(x), ix) for (ix, x) in indexed if     str_is_int(x)])
        strs = do_sort([(    x , ix) for (ix, x) in indexed if not str_is_int(x)])
      
        return ' '.join([str(b) for _, b in sorted(reverse(ints+strs))])
      
      print(magick(s))
      

      【讨论】:

      • 所以回答你的问题:这可能是可能的,但对我来说不是最直观的解决方案。
      【解决方案6】:

      在将原始输入分组为整数和字符串之后,此解决方案使用了一个自定义排序算法:

      def gt(a, b):
        return a > b if isinstance(a, int) and isinstance(b, int) else a[0].lower() > b[0].lower()
      
      def type_sort(d):
         '''similar to bubble sort, but does not swap elements of different types. 
            For instance, type_sort([5, 3, 'b', 'a']) => [3, 5, 'a', 'b']
         '''
         for _ in d:
           for i in range(len(d)-1):
             _c = d[i]
             _t = d[i+1]
             if isinstance(_c, type(_t)):
               if gt(_c, _t):
                 d[i+1] = _c
                 d[i] = _t
         return d
      
      def get_type(x):
        return int(x) if x.isdigit() else x
      
      def sort_in_place(s:str):
        _s = list(map(get_type, s.split()))
        new_s = type_sort([i for i in _s if isinstance(i, int)]+[i for i in _s if isinstance(i, str)])
        ints = iter(i for i in new_s if isinstance(i, int))
        strings = iter(i for i in new_s if isinstance(i, str))
        return ' '.join(map(str, [next(ints) if isinstance(i, int) else next(strings) for i in _s]))
      
      print(sort_in_place(a))
      

      输出:

      '8 a car have 12 200 I'
      

      【讨论】:

      • 这种排序的复杂度不是 o(n2) 吗?
      猜你喜欢
      • 2017-07-28
      • 1970-01-01
      • 2015-11-21
      • 2021-10-09
      • 2018-07-19
      • 1970-01-01
      • 2014-07-30
      • 1970-01-01
      • 2022-10-23
      相关资源
      最近更新 更多