Skip to content

Commit 60fe54b

Browse files
committed
[GR-19220] Improvements to Array#sample (#2148)
PullRequest: truffleruby/2155
2 parents b89ca3f + e3533ea commit 60fe54b

File tree

6 files changed

+91
-58
lines changed

6 files changed

+91
-58
lines changed

.rubocop.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,8 @@ Lint/InheritException:
179179

180180
Lint/LiteralAsCondition:
181181
Description: Checks of literals used in conditions.
182-
Details: >-
183-
Pattern `while true` is allowed, disable the cop with
184-
`while true # rubocop:disable Lint/LiteralAsCondition` in this case.
185-
Enabled: true
182+
# disabled, it prevents `while true`, which is useful
183+
Enabled: false
186184

187185
# Supports --auto-correct
188186
Lint/LiteralInInterpolation:

src/main/ruby/truffleruby/core/array.rb

Lines changed: 86 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -975,7 +975,7 @@ def sample(count=undefined, options=undefined)
975975
end
976976

977977
if count and count < 0
978-
raise ArgumentError, 'count must be greater than 0'
978+
raise ArgumentError, 'count must be >= 0'
979979
end
980980

981981
rng = options[:random] if options
@@ -996,80 +996,119 @@ def sample(count=undefined, options=undefined)
996996
return [at(rng.rand(size))]
997997
when 2
998998
i = rng.rand(size)
999-
j = rng.rand(size)
1000-
if i == j
1001-
j = i == 0 ? i + 1 : i - 1
999+
j = rng.rand(size - 1)
1000+
if j >= i
1001+
j += 1
10021002
end
10031003
return [at(i), at(j)]
10041004
else
10051005
sample_many(count, rng)
10061006
end
10071007
end
10081008

1009-
def sample_many(count, rng)
1010-
if size / count > 3
1011-
abandon = false
1009+
private def sample_many(count, rng)
1010+
if count <= 70 # three implementations; choice determined experimentally
1011+
if 2.0 * size / count <= count + 13
1012+
sample_many_swap(count, rng)
1013+
else
1014+
sample_many_quad(count, rng)
1015+
end
1016+
else
1017+
if size <= -1100.0 + 59.5 * count
1018+
sample_many_swap(count, rng)
1019+
else
1020+
sample_many_hash(count,rng)
1021+
end
1022+
end
1023+
end
10121024

1013-
result = Array.new count
1014-
i = 1
1025+
private def sample_many_swap(count, rng)
1026+
# linear dependence on array size, therefore very slow for small count / size
1027+
result = Array.new(self)
10151028

1016-
result[0] = rng.rand(size)
1017-
while i < count
1018-
k = rng.rand(size)
1029+
count.times do |c|
1030+
result.__send__ :swap, c, rng.rand(size)
1031+
end
10191032

1020-
spin = false
1021-
spin_count = 0
1033+
count == size ? result : result[0, count]
1034+
end
10221035

1023-
while true # rubocop:disable Lint/LiteralAsCondition
1024-
j = 0
1025-
while j < i
1026-
if k == result[j]
1027-
spin = true
1028-
break
1029-
end
1036+
private def sample_many_quad(count, rng)
1037+
# quadratic time due to linear time collision check but low overhead
1038+
result = Array.new count
1039+
i = 1
10301040

1031-
j += 1
1032-
end
1041+
result[0] = rng.rand(size)
10331042

1034-
if spin
1035-
if (spin_count += 1) > 100
1036-
abandon = true
1037-
break
1038-
end
1043+
while i < count
1044+
k = rng.rand(size)
1045+
spin = false
10391046

1040-
k = rng.rand(size)
1041-
else
1047+
while true
1048+
j = 0
1049+
while j < i
1050+
if k == result[j]
1051+
spin = true
10421052
break
10431053
end
1054+
1055+
j += 1
10441056
end
10451057

1046-
break if abandon
1058+
if spin
1059+
k = rng.rand(size)
1060+
spin = false
1061+
else
1062+
break
1063+
end
1064+
end
10471065

1048-
result[i] = k
1066+
result[i] = k
1067+
i += 1
1068+
end
10491069

1050-
i += 1
1051-
end
1070+
i = 0
1071+
while i < count
1072+
result[i] = at result[i]
1073+
i += 1
1074+
end
10521075

1053-
unless abandon
1054-
i = 0
1055-
while i < count
1056-
result[i] = at result[i]
1057-
i += 1
1058-
end
1076+
result
1077+
end
1078+
1079+
private def sample_many_hash(count, rng)
1080+
# use hash for constant time collision check but higher overhead
1081+
result = Array.new count
1082+
i = 1
10591083

1060-
return result
1084+
result[0] = rng.rand(size)
1085+
result_set = { result[0] => 0 }
1086+
1087+
while i < count
1088+
k = rng.rand(size)
1089+
1090+
while true
1091+
if result_set.include?(k)
1092+
k = rng.rand(size)
1093+
else
1094+
break
1095+
end
10611096
end
1062-
end
10631097

1064-
result = Array.new(self)
1098+
result[i] = k
1099+
result_set[i] = k
10651100

1066-
count.times do |c|
1067-
result.__send__ :swap, c, rng.rand(size)
1101+
i += 1
10681102
end
10691103

1070-
count == size ? result : result[0, count]
1104+
i = 0
1105+
while i < count
1106+
result[i] = at result[i]
1107+
i += 1
1108+
end
1109+
1110+
result
10711111
end
1072-
private :sample_many
10731112

10741113
def select!(&block)
10751114
return to_enum(:select!) { size } unless block_given?
@@ -1079,7 +1118,6 @@ def select!(&block)
10791118
ary = select(&block)
10801119
Primitive.steal_array_storage(self, ary) unless size == ary.size
10811120
end
1082-
10831121
alias_method :filter!, :select!
10841122

10851123
def shuffle(options = undefined)
@@ -1188,7 +1226,6 @@ def unshift(*values)
11881226

11891227
self
11901228
end
1191-
11921229
alias_method :prepend, :unshift
11931230

11941231
def values_at(*args)

src/main/ruby/truffleruby/core/enumerable.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ def cycle(many=nil)
553553
i += 1
554554
end
555555
else
556-
while true # rubocop:disable Lint/LiteralAsCondition
556+
while true
557557
cache.each { |o| yield o }
558558
end
559559
end

src/main/ruby/truffleruby/core/kernel.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ def loop
434434
return to_enum(:loop) { Float::INFINITY } unless block_given?
435435

436436
begin
437-
while true # rubocop:disable Lint/LiteralAsCondition
437+
while true
438438
yield
439439
end
440440
rescue StopIteration => si

src/main/ruby/truffleruby/core/posix.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ def self.with_array_of_strings_pointer(strings)
346346
# gets as soon as it gets something.
347347

348348
def self.read_string_at_least_one_byte(io, count)
349-
while true # rubocop:disable Lint/LiteralAsCondition
349+
while true
350350
# must call #read_string in order to properly support polyglot STDIO
351351
string, errno = read_string(io, count)
352352
return string if errno == 0

src/main/ruby/truffleruby/core/range.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2727
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2828

29-
# rubocop:disable Lint/LiteralAsCondition
30-
3129
class Range
3230

3331
include Enumerable

0 commit comments

Comments
 (0)