在Ruby中,按多个条件排序的最常用方法是使用sort_by
和排序函数返回与每个排序标准对应的值的数组,按重要性递减的顺序排列,例如:
Dir["*"].sort_by { |f| [test(?s, f) || 0, test(?M, f), f] }
将按大小排序目录条目,然后按mtime排序,最后按文件名排序。这是有效的,因为它使用Schwartzian transform仅计算每个文件的大小和mtime一次,而不是每次比较一次。然而,它并不是真正的懒惰,因为它计算每个文件的mtime,但是如果(比方说)目录中的每个文件都有不同的大小,则不需要计算任何 mtimes。 / p>
在这种情况下,这不是一个大问题,因为在查找大小后立即查找mtime应该是高效的,因为内核级别的缓存(例如Linux上的IIRC,它们都来自stat(2)
系统调用如果Ruby也有自己的优化,我也不会感到惊讶。但是想象一下,如果第二个标准不是mtime,而是(比方说)文件中字符串的出现次数,并且所讨论的文件很大。在这种情况下,您 需要进行延迟评估,以避免在按大小排序时读取所有这些大文件。
在撰写本文时,Algorithm Implementation/Sorting/Schwartzian transform的Wikibooks条目提出了这个解决方案:
sorted_files =
Dir["*"]. # Get all files
# compute tuples of name, size, modtime
collect{|f| [f, test(?s, f), test(?M, f)]}.
sort {|a, b| # sort
a[1] <=> b[1] or # -- by increasing size
b[2] <=> a[2] or # -- by age descending
a[0] <=> b[0] # -- by name
}.collect{|a| a[0]} # extract original name
这种方法是从Perl复制的,其中
sort {
$a->[1] <=> $b->[1] # sort first numerically by size (smallest first)
or $b->[2] <=> $a->[2] # then numerically descending by modtime age (oldest first)
or $a->[0] cmp $b->[0] # then stringwise by original name
}
效果很好,因为Perl有一个0 or $foo
评估为$foo
的怪癖。但是在Ruby中,由于0 or foo
评估为0
,因此它已被破坏。实际上,Wikibooks实现完全忽略了mtimes和文件名,并且只按大小排序。我已经删除了我的Wikibooks帐户,以便我可以解决这个问题,但我想知道:在Ruby中组合多个<=>
太空飞船运算符比较结果的最简洁方法是什么?
我将举一个具体的例子来澄清这个问题。假设我们有两种类型的评估,可能需要在排序期间作为标准。第一个相对便宜:
def size(a)
# get the size of file `a`, and if we're feeling keen,
# memoize the results
...
end
第二个是昂贵的:
def matches(a)
# count the number of occurrences of a string
# in file `a`, which could be a large file, and
# memoize the results
...
end
我们希望首先按大小升序排序,然后按匹配数递减。我们无法使用Schwartzian变换,因为这会在每个项目上非懒惰地调用matches()
。
我们可以定义一个像
这样的帮手def nil_if_equal(result)
result == 0 ? nil : result
end
然后执行:
sort {|a, b|
nil_if_equal(size(a) <=> size(b)) or
matches(b) <=> matches(a)
}
如果有n
条件要排序,那么您需要n-1
nil_if_equal
这里的nil_if_equal
次调用,因为只有最后的排序标准不需要它。
那么是否有比这更习惯的方式可以避免"engines": {
"node": ">6.9.4",
"npm": ">4.2.0"
},
的需要?
答案 0 :(得分:1)
不知道它是多么惯用,但这是一种再次使用sort_by
的方法。代替
例如
['bab', 'foo', 'so', 'bar'].sort_by { |s| [s.size, count_a(s), count_b(s)] }
执行此操作以使count_a(s)
和count_b(s)
延迟并记住:
['bab', 'foo', 'so', 'bar'].sort_by { |s| [s.size, lazy{count_a(s)}, lazy{count_b(s)}] }
我的lazy
使该块的行为类似于它所产生的值的懒惰和记忆版本。
演示输出,显示我们只计算必要的内容(即,不要计入'so'
,因为它具有唯一的大小,并且在'b'
中不计算'foo'
,因为它{ {1}} - count在size-3字符串中是唯一的):
'a'
演示代码:
Counting 'a' in 'bab'.
Counting 'a' in 'foo'.
Counting 'a' in 'bar'.
Counting 'b' in 'bab'.
Counting 'b' in 'bar'.
["so", "foo", "bar", "bab"]
另一种使def lazy(&block)
def block.value
(@value ||= [self.yield])[0]
end
def block.<=>(other)
value <=> other.value
end
block
end
def count_a(s)
puts "Counting 'a' in '#{s}'."
s.count('a')
end
def count_b(s)
puts "Counting 'b' in '#{s}'."
s.count('b')
end
p ['bab', 'foo', 'so', 'bar'].sort_by { |s| [s.size, lazy{count_a(s)}, lazy{count_b(s)}] }
进行记忆的方法:如果它被调用,它立即用一个只返回存储值的方法替换自己:
value