Doing it without iteration might seem like a good goal, but iteration done right is going to be extremely fast.
Benchmarks are important:
require 'benchmark'
DATA = ['a','b','c','a','b','c','a','b','c']
INDEXES = [2,5,8]
def ttm(data)
d2 = data.dup
INDEXES.sort.reverse.each{ |i| d2.delete_at(i) }
d2
end
def devon_parsons(data)
new_data = data.each_with_index.reject do |value,index|
INDEXES.include? index
end.map(&:first)
new_data
end
def arup_rakshit(data)
data.values_at(*(0...data.size).to_a - INDEXES)
end
def sawa(data)
data.values_at(*data.each_index.to_a - INDEXES)
end
Make sure it's an apples to apples test:
ttm(DATA) # => ["a", "b", "a", "b", "a", "b"]
devon_parsons(DATA) # => ["a", "b", "a", "b", "a", "b"]
arup_rakshit(DATA) # => ["a", "b", "a", "b", "a", "b"]
sawa(DATA) # => ["a", "b", "a", "b", "a", "b"]
Run the benchmarks:
n = 100_000
Benchmark.bm(13) do |b|
b.report('ttm:') { n.times { ttm(DATA) } }
b.report('devon_parsons') { n.times { devon_parsons(DATA) } }
b.report('arup_rakshit') { n.times { arup_rakshit(DATA) } }
b.report('sawa') { n.times { sawa(DATA) } }
end
Which results in:
# >> user system total real
# >> ttm: 0.130000 0.000000 0.130000 ( 0.127559)
# >> devon_parsons 0.530000 0.000000 0.530000 ( 0.535929)
# >> arup_rakshit 0.250000 0.000000 0.250000 ( 0.255295)
# >> sawa 0.300000 0.010000 0.310000 ( 0.305376)
If the data size grows:
DATA2 = DATA * 100
Benchmark.bm(13) do |b|
b.report('ttm:') { n.times { ttm(DATA2) } }
b.report('devon_parsons') { n.times { devon_parsons(DATA2) } }
b.report('arup_rakshit') { n.times { arup_rakshit(DATA2) } }
b.report('sawa') { n.times { sawa(DATA2) } }
end
The results really change:
# >> user system total real
# >> ttm: 0.320000 0.090000 0.410000 ( 0.420074)
# >> devon_parsons 39.170000 0.080000 39.250000 ( 39.265062)
# >> arup_rakshit 9.950000 0.010000 9.960000 ( 9.975699)
# >> sawa 9.940000 0.020000 9.960000 ( 9.959036)
It's really important to test what happens as the array size changes. What might run quickly on a small array can slow dramatically as the array grows. And, too often, what seems like a cool way to do something turns out to be very slow because there are hidden costs. Benchmarks help us figure these things out.
Note: Using sort.reverse is very important. Without those the array will be mangled.
sort can further be improved to sort_by(&:itself)
require 'benchmark'
array = (0..99).to_a.shuffle
n = 100_000
Benchmark.bm(7) do |b|
b.report('sort:') { n.times { array.sort } }
b.report('sort_by:') { n.times { array.sort_by(&:itself) } }
end
Resulting in:
user system total real
sort: 0.460000 0.010000 0.470000 ( 0.480236)
sort_by: 3.600000 0.030000 3.630000 ( 3.627871)
Increasing the array size:
array = (0..999).to_a.shuffle
Benchmark.bm(13) do |b|
b.report('sort:') { n.times { array.sort } }
b.report('sort_by:') { n.times { array.sort_by(&:itself) } }
end
Resulting in:
user system total real
sort: 9.520000 0.120000 9.640000 ( 9.659246)
sort_by: 53.530000 0.720000 54.250000 ( 54.321285)
c's in here?delete_at(i)todelete_at(*i)?