按降序对字符串进行排序的方法(在复杂键中)
Method to sort strings in descending order (in complex keys)
为了对字符串数组 a
进行降序排序,可以使用 reverse
。
a.sort.reverse
但是当你想在多个排序键中使用一个字符串时,这是无法做到的。假设 items
是具有属性 attr1
(String
)、attr2
(String
)、attr3
(Integer
).排序可以这样完成:
items.sort_by{|item| [item.attr1, item.attr2, item.attr3]}
可以通过将 Integer
与 -1
相乘来独立完成从升序到降序的切换:
items.sort_by{|item| [item.attr1, item.attr2, -item.attr3]}
但是这样的方法对于String
来说并不简单。可以定义这样的方法吗?当你想对 attr2
进行降序排序时,应该这样写:
items.sort_by{|item| [item.attr1, item.attr2.some_method, item.attr3]}
虽然我不知道一般的学术实施,但在现实生活中我会选择:
class String
def hash_for_sort precision = 5
(@h_f_p ||= {})[precision] ||= self[0...precision].codepoints.map do |cp|
[cp, 99999].min.to_s.ljust 5, '0'
join.to_i
end
end
现在可以随意按 -item.attr2.hash_for_sort
排序。
上面的方法有一些问题:
- 字符串排序无效,有
> precision
个字母不同;
- 函数的初始调用是
O(self.length)
;
- 高于 99999 的代码点将被视为相等(排序不准确)。
但考虑到实际情况,我想不出什么时候这还不行。
P.S. 如果我要精确地解决这个任务,我会寻找一种算法,以一对一的方式将字符串转换为浮点数。
以下支持所有响应<=>
的对象。
def generalized_array_sort(arr, inc_or_dec)
arr.sort do |a,b|
comp = 0
a.zip(b).each_with_index do |(ae,be),i|
next if (ae<=>be).zero?
comp = (ae<=>be) * (inc_or_dec[i]==:inc ? 1 : -1)
break
end
comp
end
end
例子
arr = [[3, "dog"], [4, "cat"], [3, "cat"], [4, "dog"]]
inc_or_dec = [:inc, :dec]
generalized_array_sort(arr, inc_or_dec)
#=> [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
另一个例子
class A; end
class B<A; end
class C<B; end
[A,B,C].sort #=> [C, B, A]
arr = [[3, A], [4, B], [3, B], [4, A], [3, C], [4,C]]
inc_or_dec = [:inc, :dec]
generalized_array_sort(arr, inc_or_dec)
#=> [[3, A], [3, B], [3, C], [4, A], [4, B], [4, C]]
我不确定这两个是否通过了您的直截了当测试,但我认为它们都可以正常工作。使用@CarySwoveland 的测试数据:
arr = [[3, "dog"], [4, "cat"], [3, "cat"], [4, "dog"]]
arr.sort_by {|a, b| [ a, *b.codepoints.map(&:-@) ] }
# => [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
或者,这里有一个无论类型如何都有效的解决方案(即它不必是字符串):
arr.sort do |a, b|
c0 = a[0] <=> b[0]
next c0 unless c0.zero?
-(a[1] <=> b[1])
end
# => [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
后者可以概括为如下方法:
def arr_cmp(a, b, *dirs)
return 0 if a.empty? && b.empty?
return a <=> b if dirs.empty?
a0, *a = a
b0, *b = b
dir, *dirs = dirs
c0 = a0 <=> b0
return arr_cmp(a, b, *dirs) if c0.zero?
dir * c0
end
这就像 <=>
一样工作,但它的最终参数采用 1
或 -1
的列表,指示每个相应数组元素的排序方向,例如:
a = [3, "dog"]
b = [3, "cat"]
arr_cmp(a, b, 1, 1) # => 1
arr_cmp(a, b, 1, -1) # => -1
与 <=>
一样,它在 sort
块中最有用:
arr.sort {|a, b| arr_cmp(a, b, 1, -1) }
# => [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
虽然我没有对其进行太多测试,因此可能存在它失败的边缘情况。
我认为您始终可以将字符串转换为整数数组 (ord
)。像这样:
strings = [["Hello", "world"], ["Hello", "kitty"], ["Hello", "darling"]]
strings.sort_by do |s1, s2|
[
s1,
s2.chars.map(&:ord).map{ |n| -n }
]
end
PS:
因为@CarySwoveland 在这里发现了一个空字符串的极端情况,可以用这个不优雅的解决方案来解决:
strings.sort_by do |s1, s2|
[
s1,
s2.chars.
map(&:ord).
tap{|chars| chars << -Float::INFINITY if chars.empty? }.
map{ |n| -n }
]
end
@Jordan 好心地提到 sort_by uses Schwartzian Transform 所以你根本不需要预处理。
为了对字符串数组 a
进行降序排序,可以使用 reverse
。
a.sort.reverse
但是当你想在多个排序键中使用一个字符串时,这是无法做到的。假设 items
是具有属性 attr1
(String
)、attr2
(String
)、attr3
(Integer
).排序可以这样完成:
items.sort_by{|item| [item.attr1, item.attr2, item.attr3]}
可以通过将 Integer
与 -1
相乘来独立完成从升序到降序的切换:
items.sort_by{|item| [item.attr1, item.attr2, -item.attr3]}
但是这样的方法对于String
来说并不简单。可以定义这样的方法吗?当你想对 attr2
进行降序排序时,应该这样写:
items.sort_by{|item| [item.attr1, item.attr2.some_method, item.attr3]}
虽然我不知道一般的学术实施,但在现实生活中我会选择:
class String
def hash_for_sort precision = 5
(@h_f_p ||= {})[precision] ||= self[0...precision].codepoints.map do |cp|
[cp, 99999].min.to_s.ljust 5, '0'
join.to_i
end
end
现在可以随意按 -item.attr2.hash_for_sort
排序。
上面的方法有一些问题:
- 字符串排序无效,有
> precision
个字母不同; - 函数的初始调用是
O(self.length)
; - 高于 99999 的代码点将被视为相等(排序不准确)。
但考虑到实际情况,我想不出什么时候这还不行。
P.S. 如果我要精确地解决这个任务,我会寻找一种算法,以一对一的方式将字符串转换为浮点数。
以下支持所有响应<=>
的对象。
def generalized_array_sort(arr, inc_or_dec)
arr.sort do |a,b|
comp = 0
a.zip(b).each_with_index do |(ae,be),i|
next if (ae<=>be).zero?
comp = (ae<=>be) * (inc_or_dec[i]==:inc ? 1 : -1)
break
end
comp
end
end
例子
arr = [[3, "dog"], [4, "cat"], [3, "cat"], [4, "dog"]]
inc_or_dec = [:inc, :dec]
generalized_array_sort(arr, inc_or_dec)
#=> [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
另一个例子
class A; end
class B<A; end
class C<B; end
[A,B,C].sort #=> [C, B, A]
arr = [[3, A], [4, B], [3, B], [4, A], [3, C], [4,C]]
inc_or_dec = [:inc, :dec]
generalized_array_sort(arr, inc_or_dec)
#=> [[3, A], [3, B], [3, C], [4, A], [4, B], [4, C]]
我不确定这两个是否通过了您的直截了当测试,但我认为它们都可以正常工作。使用@CarySwoveland 的测试数据:
arr = [[3, "dog"], [4, "cat"], [3, "cat"], [4, "dog"]]
arr.sort_by {|a, b| [ a, *b.codepoints.map(&:-@) ] }
# => [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
或者,这里有一个无论类型如何都有效的解决方案(即它不必是字符串):
arr.sort do |a, b|
c0 = a[0] <=> b[0]
next c0 unless c0.zero?
-(a[1] <=> b[1])
end
# => [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
后者可以概括为如下方法:
def arr_cmp(a, b, *dirs)
return 0 if a.empty? && b.empty?
return a <=> b if dirs.empty?
a0, *a = a
b0, *b = b
dir, *dirs = dirs
c0 = a0 <=> b0
return arr_cmp(a, b, *dirs) if c0.zero?
dir * c0
end
这就像 <=>
一样工作,但它的最终参数采用 1
或 -1
的列表,指示每个相应数组元素的排序方向,例如:
a = [3, "dog"]
b = [3, "cat"]
arr_cmp(a, b, 1, 1) # => 1
arr_cmp(a, b, 1, -1) # => -1
与 <=>
一样,它在 sort
块中最有用:
arr.sort {|a, b| arr_cmp(a, b, 1, -1) }
# => [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
虽然我没有对其进行太多测试,因此可能存在它失败的边缘情况。
我认为您始终可以将字符串转换为整数数组 (ord
)。像这样:
strings = [["Hello", "world"], ["Hello", "kitty"], ["Hello", "darling"]]
strings.sort_by do |s1, s2|
[
s1,
s2.chars.map(&:ord).map{ |n| -n }
]
end
PS:
因为@CarySwoveland 在这里发现了一个空字符串的极端情况,可以用这个不优雅的解决方案来解决:
strings.sort_by do |s1, s2|
[
s1,
s2.chars.
map(&:ord).
tap{|chars| chars << -Float::INFINITY if chars.empty? }.
map{ |n| -n }
]
end
@Jordan 好心地提到 sort_by uses Schwartzian Transform 所以你根本不需要预处理。