Skip to content

Commit 90967c9

Browse files
drewdzzzTotktonada
authored andcommitted
fix drop_while work with stateful iterators
Currently, method `drop_while` rewinds the iterator once it yields values not satisfying the condition. Such approach doesn't work with stateful iterators since they cannot be rewound by simply copying the state. The commit brings a new implementation that works correctly with all types of iterators. Performance comparison. In the first scenario, functor from `drop_while` returns `true` on the first million elements, then it returns `false` on the other million elements. With LuaJIT enabled, the new implementation is about 7% faster. Without LuaJIT, results are identical. In the second scenario, we leave only the first half of elements so that functor always returns `true`, which results in an empty iterator since all the elements are dropped. In this case, the new implementation is about 10-15% faster with JIT enabled. In the third scenario, we leave the second half of elements so that the functor returns `false` right from the start. In this case, the new implementation is about 4% slower than the old one with JIT enabled. Part of #83
1 parent a52e66e commit 90967c9

File tree

2 files changed

+97
-9
lines changed

2 files changed

+97
-9
lines changed

fun.lua

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -411,24 +411,41 @@ end
411411
methods.drop_n = method1(drop_n)
412412
exports.drop_n = export1(drop_n)
413413

414+
-- Unpack values from param[3] on the first iteration, then return
415+
-- values from the provided iterator.
416+
--
417+
-- A generator function for drop_while().
418+
local drop_while_gen = function(param, state)
419+
local results = param[3]
420+
if not results then
421+
return param[1](param[2], state)
422+
else
423+
param[3] = nil
424+
return state, unpack(results, 1, table.maxn(results))
425+
end
426+
end
427+
428+
-- Checks if drop_while should continue skipping. If iterator is not exhausted
429+
-- and skipping is over, elements returned by iterator are wrapped into a table
430+
-- and returned as the second return value. Note that a table is created only
431+
-- once, on the last iteration, for the sake of performance.
414432
local drop_while_x = function(fun, state_x, ...)
415-
if state_x == nil or not fun(...) then
416-
return state_x, false
433+
if state_x ~= nil and not fun(...) then
434+
return state_x, {...}
417435
end
418-
return state_x, true, ...
436+
return state_x
419437
end
420438

421439
local drop_while = function(fun, gen_x, param_x, state_x)
422440
assert(type(fun) == "function", "invalid first argument to drop_while")
423-
local cont, state_x_prev
424-
repeat
425-
state_x_prev = deepcopy(state_x)
426-
state_x, cont = drop_while_x(fun, gen_x(param_x, state_x))
427-
until not cont
441+
local pivot = nil
442+
while state_x ~= nil and pivot == nil do
443+
state_x, pivot = drop_while_x(fun, gen_x(param_x, state_x))
444+
end
428445
if state_x == nil then
429446
return wrap(nil_gen, nil, nil)
430447
end
431-
return wrap(gen_x, param_x, state_x_prev)
448+
return wrap(drop_while_gen, {gen_x, param_x, pivot}, state_x)
432449
end
433450
methods.drop_while = method1(drop_while)
434451
exports.drop_while = export1(drop_while)

tests/stateful.lua

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
-- compatibility with Lua 5.1/5.2
2+
local unpack = rawget(table, "unpack") or unpack
3+
4+
function gen_stateful_iter(values)
5+
local i = 1
6+
local gen = function(_param, values)
7+
if i > #values then
8+
return nil
9+
end
10+
local t = values[i]
11+
i = i + 1
12+
if type(t) == 'table' then
13+
return values, unpack(t, 1, table.maxn(t))
14+
else
15+
return values, t
16+
end
17+
end
18+
return iter(gen, nil, values)
19+
end
20+
21+
--------------------------------------------------------------------------------
22+
-- drop_while
23+
--------------------------------------------------------------------------------
24+
25+
-- Simple test
26+
dump(gen_stateful_iter({1, 2, 3, 4, 5, 6, 5, 4, 3}):drop_while(function(x) return x < 5 end))
27+
--[[test
28+
5
29+
6
30+
5
31+
4
32+
3
33+
--test]]
34+
35+
-- Multireturn
36+
dump(gen_stateful_iter({{1, 10}, {3, 30}, {5, 50}, {6, 60}, {3, 20}}):drop_while(function(x) return x < 5 end))
37+
--[[test
38+
5 50
39+
6 60
40+
3 20
41+
--test]]
42+
43+
-- Multireturn with nil
44+
dump(gen_stateful_iter({{1, nil, 10}, {3, nil, 30}, {5, nil, 50}, {6, nil, 60}, {3, nil, 20}}):drop_while(function(x) return x < 5 end))
45+
--[[test
46+
5 nil 50
47+
6 nil 60
48+
3 nil 20
49+
--test]]
50+
51+
-- Multireturn with condition on second returned value
52+
dump(gen_stateful_iter({{0, 1}, {0, 3}, {0, 5}, {0, 4}}):drop_while(function(x, y) return y < 5 end))
53+
--[[test
54+
0 5
55+
0 4
56+
--test]]
57+
58+
-- Empty iterator
59+
dump(gen_stateful_iter({}):drop_while(function(x) return x < 5 end))
60+
--[[test
61+
--test]]
62+
63+
-- Always false
64+
dump(gen_stateful_iter({1, 2, 3, 4, 5}):drop_while(function(x) return x > 100 end))
65+
--[[test
66+
1
67+
2
68+
3
69+
4
70+
5
71+
--test]]

0 commit comments

Comments
 (0)