Skip to content

Commit 129e2f9

Browse files
committed
Overhaul the API
* Switch KWayMerger type to kway_merge function as API entrypoint * Use Base's Ordering API
1 parent 294e77f commit 129e2f9

File tree

4 files changed

+98
-61
lines changed

4 files changed

+98
-61
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,15 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html
77
## UNRELEASED
88
* Add content here that have been merged, but not made it to a release yet.
99

10+
## [0.2.0]
11+
### Breaking changes
12+
* The public constructor for the `KWayMerger` is now the new `kway_merge` function.
13+
`KWayMerger` is public, but unexported.
14+
* Instead of the `F` parameter (and argument to its constructor), `kway_merge`
15+
uses the same ordering API as Base's sorting functions.
16+
* `KWayMerger{T}` now iterates `@NamedTuple{from_iter::Int, value::T}`, to reduce
17+
the risk of users conflating the two elements of the tuple.
18+
19+
1020
## [0.1.0]
21+
* Initial release

src/KWayMerges.jl

Lines changed: 64 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,59 @@
11
module KWayMerges
22

3-
export KWayMerger
3+
using Base.Order: Ordering, Forward, ord, lt
4+
5+
export kway_merge
6+
public KWayMerger
47

58
include("heap.jl")
69

710
"""
8-
KWayMerger{T, I, F}(f::F, iterators)
9-
KWayMerger{T, I}(iterators)
10-
KWayMerger(f, iterators)
11-
KWayMerger(iterators)
11+
KWayMerger{T, I, O, S}
12+
13+
Stateful iterator of a k-way merge of multiple iterators of the same type.
14+
Constructed using [`kway_merge`](@ref).
15+
16+
The type parameters are:
17+
* `T`: Element type of iterators
18+
* `I`: Iterator type
19+
* `O`: Ordering, subtype of `Base.Ordering`
20+
* `S`: Type of state of iterators
21+
"""
22+
struct KWayMerger{T, I, O <: Base.Ordering, S}
23+
ordering::O
24+
iterators::Vector{I}
25+
states::Vector{S}
26+
heap::Vector{@NamedTuple{from_iter::Int, value::T}}
27+
end
28+
29+
"""
30+
kway_merge(
31+
iterators;
32+
lt=isless,
33+
by=identity,
34+
rev::Bool=false,
35+
order::Base.Order.Ordering=Base.Order.Forward
36+
)
37+
kway_merge(::Type{T}, ::Type{T}, iterators; kwargs...)
38+
kway_merge(::Type{T}, ::Type{T}, ordering::Ordering, iterators)
1239
1340
Create a stateful iterator which does a k-way merge between multiple
1441
iterators of the same type.
1542
1643
This iterator yields `@NamedTuple{from_iter::Int, value::T}` elements, where `value` is the
1744
next element from one of the iterators, and `from_iter` is the 1-based index of the iterator
1845
that yielded `value`.
19-
The element `value` are chosenis from among the iterators such that, among all elements which
20-
are the next element of the iterators, the element is chosen which is the smallest
21-
according to the predicate `f::F`, which defaults to `isless`.
22-
23-
This implies that if all iterators are sorted by `f`, the yielded will be in sorted
24-
order.
46+
The element `value` is chosen among the iterators such that, among all elements which
47+
are the next element of the iterators, the element is chosen which is the first
48+
according to the ordering.
49+
This implies that if all iterators are sorted by `f`, the yielded will be in sorted order.
2550
Hence, a `KWayMerger` is typically used to combined multiple sorted arrays
2651
into one sorted array.
2752
53+
The ordering is given by the keywords `by`, `lt`, `rev` and `order` - these are the
54+
same as for `Base.sort!`.
55+
56+
2857
# Examples
2958
```jldoctest
3059
julia> arrs = [[1,6], [2], [5,7], [3,4,8]];
@@ -41,31 +70,17 @@ julia> print(map(Tuple, it))
4170
```
4271
4372
# Extended help
44-
The type parameters are:
45-
* `F`: Type of function used to compare the elements. It defaults
46-
to `typeof(Base.isless)`
47-
* `T`: Element type of iterators
48-
* `I`: Iterator type
49-
* `S`: Type of state of iterators
50-
5173
All iterators must be of the same type. For the constructors which don't pass
5274
in `T` and `I` explicitly, `Base.eltype` is used
5375
to determine them; since its default implementation
5476
returns `Any`, explicitly passing them may be needed for good performance for some
5577
iterators.
5678
5779
`S` is derived automatically, but this must be a fixed type;
58-
iterators that use states of multiple different types may
59-
not be supported by `KWayMerger`.
80+
iterators that use states of multiple different types during iteration may
81+
not be supported.
6082
"""
61-
struct KWayMerger{T, I, F, S}
62-
f::F
63-
iterators::Vector{I}
64-
states::Vector{S}
65-
heap::Vector{@NamedTuple{from_iter::Int, value::T}}
66-
end
67-
68-
function KWayMerger{T, I, F}(f::F, iterators) where {T, I, F}
83+
function kway_merge(::Type{T}, ::Type{I}, ordering::O, iterators) where {T, I, O}
6984
iters = vec(collect(iterators))
7085
states = nothing
7186
things = @NamedTuple{from_iter::Int, value::T}[]
@@ -79,25 +94,32 @@ function KWayMerger{T, I, F}(f::F, iterators) where {T, I, F}
7994
push!(things, (; from_iter = i, value))
8095
states[i] = state
8196
end
82-
heapify!(f, things)
97+
heapify!(ordering, things)
8398
states = if isnothing(states)
8499
Vector{Union{}}(undef, length(iters))
85100
else
86101
states
87102
end
88-
return KWayMerger{T, I, F, eltype(states)}(f, iters, states, things)
103+
return KWayMerger{T, I, O, eltype(states)}(ordering, iters, states, things)
89104
end
90105

91-
function KWayMerger{T, I}(iterators) where {T, I}
92-
return KWayMerger{T, I, typeof(isless)}(isless, iterators)
106+
function kway_merge(
107+
::Type{T},
108+
::Type{I},
109+
iterators;
110+
lt = isless,
111+
by = identity,
112+
rev::Bool = false,
113+
order::Base.Ordering = Forward,
114+
) where {T, I}
115+
ordering = ord(lt, by, rev, order)
116+
return kway_merge(T, I, ordering, iterators)
93117
end
94118

95-
KWayMerger(iterators) = KWayMerger(isless, iterators)
96-
97-
function KWayMerger(f::F, iterators) where {F}
119+
function kway_merge(iterators; kwargs...)
98120
I = eltype(typeof(iterators))
99121
T = eltype(I)
100-
return KWayMerger{T, I, F}(f, iterators)
122+
return kway_merge(T, I, iterators; kwargs...)
101123
end
102124

103125
# We could technically know this, but KWayMerger is stateful, and
@@ -112,11 +134,15 @@ function Base.iterate(x::KWayMerger, ::Nothing = nothing)
112134
state = @inbounds x.states[top.from_iter]
113135
it = iterate(iterator, state)
114136
if it === nothing
115-
@inbounds heappop!(x.f, x.heap)
137+
@inbounds heappop!(x.ordering, x.heap)
116138
else
117139
(new_item, new_state) = it
118140
@inbounds x.states[top.from_iter] = new_state
119-
@inbounds heapreplace!(x.f, x.heap, (; from_iter = top.from_iter, value = new_item))
141+
@inbounds heapreplace!(
142+
x.ordering,
143+
x.heap,
144+
(; from_iter = top.from_iter, value = new_item)
145+
)
120146
end
121147
return (top, nothing)
122148
end

src/heap.jl

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
1-
@inline function heapify!(f, xs::Vector)
1+
@inline function heapify!(o::Ordering, xs::Vector)
22
for i in div(length(xs), 2):-1:1
3-
percolate_down!(f, xs, i, xs[i])
3+
percolate_down!(o, xs, i, xs[i])
44
end
55
return xs
66
end
77

88
@inline function percolate_down!(
9-
f::F,
9+
o::Ordering,
1010
xs::Vector,
1111
i::Integer,
1212
x,
13-
) where {F}
13+
)
1414
len = length(xs)
1515
@inbounds while (l = 2i) <= len
1616
r = 2i + 1
17-
j = if r > len || f(last(@inbounds(xs[l])), last(@inbounds(xs[r])))
17+
j = if r > len || lt(o, last(@inbounds(xs[l])), last(@inbounds(xs[r])))
1818
l
1919
else
2020
r
2121
end
22-
if f(last(@inbounds(xs[j])), last(x))
22+
if lt(o, last(@inbounds(xs[j])), last(x))
2323
@inbounds xs[i] = xs[j]
2424
i = j
2525
else
@@ -29,20 +29,20 @@ end
2929
return @inbounds xs[i] = x
3030
end
3131

32-
@noinline function heappop!(f, xs::Vector)
32+
@noinline function heappop!(o::Ordering, xs::Vector)
3333
isempty(xs) && throw(BoundsError(xs, 1))
3434
x = @inbounds xs[1]
3535
y = @inbounds pop!(xs)
3636
if !isempty(xs)
37-
percolate_down!(f, xs, 1, y)
37+
percolate_down!(o, xs, 1, y)
3838
end
3939
return x
4040
end
4141

42-
@inline function heapreplace!(f, xs::Vector, x)
42+
@inline function heapreplace!(o::Ordering, xs::Vector, x)
4343
@boundscheck isempty(xs) && throw(BoundsError(xs, 1))
4444
res = @inbounds xs[1]
4545
@inbounds xs[1] = x
46-
percolate_down!(f, xs, 1, x)
46+
percolate_down!(o, xs, 1, x)
4747
return res
4848
end

test/runtests.jl

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ imap(f) = xs -> Iterators.map(f, xs)
88
@testset "Construction" begin
99
v = [1:3, 4:6, 9:11]
1010

11-
T = KWayMerger{Int, UnitRange{Int}, typeof(isless), Int}
11+
T = KWayMerges.KWayMerger{Int, UnitRange{Int}, Base.Order.ForwardOrdering}
1212

13-
@test KWayMerger{Int, UnitRange{Int}, typeof(isless)}(isless, v) isa T
14-
@test KWayMerger{Int, UnitRange{Int}}(v) isa T
15-
@test KWayMerger(v) isa T
16-
@test KWayMerger(isless, v) isa T
17-
@test KWayMerger(<, v) isa KWayMerger{Int, UnitRange{Int}, typeof(<)}
13+
@test kway_merge(Int, UnitRange{Int}, v; lt = isless) isa T
14+
@test kway_merge(Int, UnitRange{Int}, v; rev = false) isa T
15+
@test kway_merge(v) isa T
16+
@test kway_merge(v; by = identity, lt = isless) isa T
17+
@test kway_merge(v; lt = <) isa KWayMerges.KWayMerger{Int, UnitRange{Int}}
1818
end
1919

2020
function manual_collect(it; lt = isless, by = identity)
@@ -29,15 +29,15 @@ end
2929
@testset "Forward sorting" begin
3030
v = [[3, 5, 8], [1, 1], [10, 11], [1, 2, 7], Int[]]
3131

32-
@test collect(KWayMerger(v)) == manual_collect(v)
32+
@test collect(kway_merge(v)) == manual_collect(v)
3333
end
3434

3535
@testset "Using a predicate" begin
3636
v = [["de", "abc"], [""], ["xysa", "dsakljdwe"]]
3737

3838
r = manual_collect(v; by = length)
3939

40-
@test collect(KWayMerger((i, j) -> isless(length(i), length(j)), v)) == r
40+
@test collect(kway_merge(v; rev = false, by = length)) == r
4141
end
4242

4343
@testset "Reverse sorting" begin
@@ -47,16 +47,16 @@ end
4747
end
4848
r = manual_collect(v; lt = >)
4949

50-
@test collect(KWayMerger((i, j) -> isless(j, i), v)) == r
50+
@test collect(kway_merge(v; order = Base.Order.Reverse)) == r
5151
end
5252

5353
@testset "Some edge cases" begin
54-
it = KWayMerger([])
55-
@test it isa (KWayMerger{T, I, typeof(isless)} where {T, I})
54+
it = kway_merge([])
55+
@test it isa (KWayMerges.KWayMerger{T, I, Base.Order.ForwardOrdering} where {T, I})
5656
@test collect(it) == Any[]
5757

58-
it = KWayMerger([1:0, 11:10])
59-
@test it isa KWayMerger{Int, UnitRange{Int}, typeof(isless)}
58+
it = kway_merge([1:0, 11:10])
59+
@test it isa KWayMerges.KWayMerger{Int, UnitRange{Int}, Base.Order.ForwardOrdering}
6060
@test collect(it) == Tuple{Int, Int}[]
6161
@test typeof(collect(it)) == Vector{@NamedTuple{from_iter::Int, value::Int}}
6262
end

0 commit comments

Comments
 (0)