Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Julia v1.5 Release Notes

New language features
---------------------
* Functions `searchsorted*` have a new keyword argument `arrayby`, which is applied
to the array, but not to the comparison argument (in constrast to `by`) ([#9429])
* Macro calls `@foo {...}` can now also be written `@foo{...}` (without the space) ([#34498]).
* `⨟` is now parsed as a binary operator with times precedence. It can be entered in the REPL
with `\bbsemi` followed by <kbd>TAB</kbd> ([#34722]).
Expand Down
83 changes: 49 additions & 34 deletions base/sort.jl
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,13 @@ midpoint(lo::Integer, hi::Integer) = midpoint(promote(lo, hi)...)

# index of the first value of vector a that is greater than or equal to x;
# returns length(v)+1 if x is greater than all values in v.
function searchsortedfirst(v::AbstractVector, x, lo::T, hi::T, o::Ordering)::keytype(v) where T<:Integer
function searchsortedfirst(v::AbstractVector, x, lo::T, hi::T, o::Ordering, aby=identity)::keytype(v) where T<:Integer
u = T(1)
lo = lo - u
hi = hi + u
@inbounds while lo < hi - u
m = midpoint(lo, hi)
if lt(o, v[m], x)
if lt(o, aby(v[m]), x)
lo = m
else
hi = m
Expand All @@ -191,13 +191,13 @@ end

# index of the last value of vector a that is less than or equal to x;
# returns 0 if x is less than all values of v.
function searchsortedlast(v::AbstractVector, x, lo::T, hi::T, o::Ordering)::keytype(v) where T<:Integer
function searchsortedlast(v::AbstractVector, x, lo::T, hi::T, o::Ordering, aby=identity)::keytype(v) where T<:Integer
u = T(1)
lo = lo - u
hi = hi + u
@inbounds while lo < hi - u
m = midpoint(lo, hi)
if lt(o, x, v[m])
if lt(o, x, aby(v[m]))
hi = m
else
lo = m
Expand All @@ -209,26 +209,27 @@ end
# returns the range of indices of v equal to x
# if v does not contain x, returns a 0-length range
# indicating the insertion point of x
function searchsorted(v::AbstractVector, x, ilo::T, ihi::T, o::Ordering)::UnitRange{keytype(v)} where T<:Integer
function searchsorted(v::AbstractVector, x, ilo::T, ihi::T, o::Ordering, aby=identity)::UnitRange{keytype(v)} where T<:Integer
u = T(1)
lo = ilo - u
hi = ihi + u
@inbounds while lo < hi - u
m = midpoint(lo, hi)
if lt(o, v[m], x)
vm = aby(v[m])
if lt(o, vm, x)
lo = m
elseif lt(o, x, v[m])
elseif lt(o, x, vm)
hi = m
else
a = searchsortedfirst(v, x, max(lo,ilo), m, o)
b = searchsortedlast(v, x, m, min(hi,ihi), o)
a = searchsortedfirst(v, x, max(lo,ilo), m, o, aby)
b = searchsortedlast(v, x, m, min(hi,ihi), o, aby)
return a : b
end
end
return (lo + 1) : (hi - 1)
end

function searchsortedlast(a::AbstractRange{<:Real}, x::Real, o::DirectOrdering)::keytype(a)
function searchsortedlast(a::AbstractRange{<:Real}, x::Real, o::DirectOrdering, ::typeof(identity))::keytype(a)
require_one_based_indexing(a)
if step(a) == 0
lt(o, x, first(a)) ? 0 : length(a)
Expand All @@ -238,7 +239,7 @@ function searchsortedlast(a::AbstractRange{<:Real}, x::Real, o::DirectOrdering):
end
end

function searchsortedfirst(a::AbstractRange{<:Real}, x::Real, o::DirectOrdering)::keytype(a)
function searchsortedfirst(a::AbstractRange{<:Real}, x::Real, o::DirectOrdering, ::typeof(identity))::keytype(a)
require_one_based_indexing(a)
if step(a) == 0
lt(o, first(a), x) ? length(a) + 1 : 1
Expand All @@ -248,7 +249,7 @@ function searchsortedfirst(a::AbstractRange{<:Real}, x::Real, o::DirectOrdering)
end
end

function searchsortedlast(a::AbstractRange{<:Integer}, x::Real, o::DirectOrdering)::keytype(a)
function searchsortedlast(a::AbstractRange{<:Integer}, x::Real, o::DirectOrdering, ::typeof(identity))::keytype(a)
require_one_based_indexing(a)
h = step(a)
if h == 0
Expand All @@ -270,7 +271,7 @@ function searchsortedlast(a::AbstractRange{<:Integer}, x::Real, o::DirectOrderin
end
end

function searchsortedfirst(a::AbstractRange{<:Integer}, x::Real, o::DirectOrdering)::keytype(a)
function searchsortedfirst(a::AbstractRange{<:Integer}, x::Real, o::DirectOrdering, ::typeof(identity))::keytype(a)
require_one_based_indexing(a)
h = step(a)
if h == 0
Expand All @@ -292,7 +293,7 @@ function searchsortedfirst(a::AbstractRange{<:Integer}, x::Real, o::DirectOrderi
end
end

function searchsortedfirst(a::AbstractRange{<:Integer}, x::Unsigned, o::DirectOrdering)::keytype(a)
function searchsortedfirst(a::AbstractRange{<:Integer}, x::Unsigned, o::DirectOrdering, ::typeof(identity))::keytype(a)
require_one_based_indexing(a)
if lt(o, first(a), x)
if step(a) == 0
Expand All @@ -305,7 +306,7 @@ function searchsortedfirst(a::AbstractRange{<:Integer}, x::Unsigned, o::DirectOr
end
end

function searchsortedlast(a::AbstractRange{<:Integer}, x::Unsigned, o::DirectOrdering)::keytype(a)
function searchsortedlast(a::AbstractRange{<:Integer}, x::Unsigned, o::DirectOrdering, ::typeof(identity))::keytype(a)
require_one_based_indexing(a)
if lt(o, x, first(a))
0
Expand All @@ -316,25 +317,33 @@ function searchsortedlast(a::AbstractRange{<:Integer}, x::Unsigned, o::DirectOrd
end
end

searchsorted(a::AbstractRange{<:Real}, x::Real, o::DirectOrdering) =
searchsortedfirst(a, x, o) : searchsortedlast(a, x, o)
searchsorted(a::AbstractRange{<:Real}, x::Real, o::DirectOrdering, aby) =
searchsortedfirst(a, x, o, aby) : searchsortedlast(a, x, o, aby)

for s in [:searchsortedfirst, :searchsortedlast, :searchsorted]
@eval begin
$s(v::AbstractVector, x, o::Ordering) = (inds = axes(v, 1); $s(v,x,first(inds),last(inds),o))
$s(v::AbstractVector, x;
lt=isless, by=identity, rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward) =
$s(v,x,ord(lt,by,rev,order))
$s(v::AbstractVector, x, o::Ordering, xform=identity) = (inds = axes(v, 1); $s(v,x,first(inds),last(inds),o, xform))
function $s(v::AbstractVector, x;
lt=isless, by=identity, arrayby=identity, rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward)
if by !== identity && arrayby !== identity
throw(ArgumentError("only one of keyword arguments 'by' and 'arrayby' allowed"))
end
$s(v,x,ord(lt,by,rev,order), arrayby)
end
end
end

"""
searchsorted(a, x; by=<transform>, lt=<comparison>, rev=false)
searchsorted(a, x; {by|arrayby}=<transform>, lt=<comparison>, rev=false)

Return the range of indices of `a` which yield `x <= arrayby(a[i]) <= x`
(using binary search)
according to the order specified by the `by`, `lt` and `rev` keywords,
assuming that `arrayby.(a)` is already sorted in that order.
Only one of `by` and `arrayby` may be different from the default value `identity`.

Return the range of indices of `a` which compare as equal to `x` (using binary search)
according to the order specified by the `by`, `lt` and `rev` keywords, assuming that `a`
is already sorted in that order. Return an empty range located at the insertion point
if `a` does not contain values equal to `x`.
Return an empty range located at the insertion point
if `arrayby.(by.(a))` does not contain values equal to `by(x)`.

# Examples
```jldoctest
Expand All @@ -356,11 +365,14 @@ julia> searchsorted([1, 2, 4, 5, 5, 7], 0) # no match, insert at start
""" searchsorted

"""
searchsortedfirst(a, x; by=<transform>, lt=<comparison>, rev=false)
searchsortedfirst(a, x; {by|arrayby}=<transform>, lt=<comparison>, rev=false)

Return the index of the first value in `a` greater than or equal to `x`, according to the
specified order. Return `length(a) + 1` if `x` is greater than all values in `a`.
`a` is assumed to be sorted.
Return the index of the first value in `a` greater than or equal to `x`,
according to the order specified by the `by`, `lt` and `rev` keywords,
assuming that `arrayby.(a)` is already sorted in that order.
Only one of `by` and `arrayby` may be different from the default value `identity`.

Return `length(a) + 1` if `x` is greater than all values in `arrayby.(a)`.

# Examples
```jldoctest
Expand All @@ -382,11 +394,14 @@ julia> searchsortedfirst([1, 2, 4, 5, 5, 7], 0) # no match, insert at start
""" searchsortedfirst

"""
searchsortedlast(a, x; by=<transform>, lt=<comparison>, rev=false)
searchsortedlast(a, x; {by|arrayby}=<transform>, lt=<comparison>, rev=false)

Return the index of the last value in `a` less than or equal to `x`,
according to the order specified by the `by`, `lt` and `rev` keywords,
assuming that `arrayby.(a)` is already sorted in that order.
Only one of `by` and `arrayby` may be different from the default value `identity`.

Return the index of the last value in `a` less than or equal to `x`, according to the
specified order. Return `0` if `x` is less than all values in `a`. `a` is assumed to
be sorted.
Return `0` if `x` is less than all values in `arrayby.(a)`.

# Examples
```jldoctest
Expand Down
10 changes: 10 additions & 0 deletions test/sorting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,16 @@ end
@test searchsorted(v, 0.1, rev=true) === 4:3
end
end

@testset "issue #9429" begin
@test_throws ArgumentError searchsortedfirst(1:10, 24, by=i->i^2, arrayby=i->i)
@test searchsortedfirst(1:10, 24, by=i->i^2) == 11
@test searchsortedfirst(1:10, 24, arrayby=i->i^2) == 5
@test searchsortedlast(1:10, 24, by=i->i^2) == 10
@test searchsortedlast(1:10, 24, arrayby=i->i^2) == 4
@test searchsorted(1:10, 25, by=i->i^2) == 11:10
@test searchsorted(1:10, 25, arrayby=i->i^2) == 5:5
end
end
# exercise the codepath in searchsorted* methods for ranges that check for zero step range
struct ConstantRange{T} <: AbstractRange{T}
Expand Down