From 8872f90b1c61b7af8384be566c7335ffbd9f07a8 Mon Sep 17 00:00:00 2001 From: Milan Bouchet-Valat Date: Thu, 21 Dec 2017 20:54:03 +0100 Subject: [PATCH] Make arrays and ranges hash and compare equal (#16401) * Make vectors and ranges hash and compare equal When hashing AbstractVectors, first check whether their first elements are equal to a range, and hash them as a such if that's the case. This allows for O(1) hashing of (some) ranges consistent with AbstractArrays, which means they can now compare equal. Types which have a regular range step have to use the new TypeRangeStep trait to enable O(1) hashing rather than the O(N) AbstractArray fallback. Apply the new trait to date ranges which have a regular step. Add tests for the new behaviors. --- NEWS.md | 7 +++ base/abstractarray.jl | 96 +++++++++++++++++++++++++++++++----- base/char.jl | 1 + base/hashing.jl | 13 ++--- base/hashing2.jl | 2 +- base/irrationals.jl | 2 + base/operators.jl | 10 ++-- base/range.jl | 3 ++ base/traits.jl | 37 ++++++++++++++ stdlib/Dates/src/periods.jl | 1 + stdlib/Dates/src/ranges.jl | 4 ++ stdlib/Dates/test/periods.jl | 3 ++ stdlib/Dates/test/ranges.jl | 16 ++++++ test/abstractarray.jl | 4 +- test/arrayops.jl | 6 +-- test/broadcast.jl | 2 +- test/char.jl | 2 + test/functional.jl | 6 +-- test/hashing.jl | 93 +++++++++++++++++++++++++++++++--- test/iterators.jl | 2 +- test/math.jl | 1 + test/numbers.jl | 3 +- test/random.jl | 2 +- test/ranges.jl | 79 +++++++++++++++++++---------- test/reduce.jl | 4 +- test/sparse/sparse.jl | 4 +- test/sparse/sparsevector.jl | 2 +- test/tuple.jl | 6 +-- 28 files changed, 329 insertions(+), 82 deletions(-) diff --git a/NEWS.md b/NEWS.md index afcf8bd858a3d..87d09e8956f39 100644 --- a/NEWS.md +++ b/NEWS.md @@ -344,6 +344,13 @@ This section lists changes that do not have deprecation warnings. * `eachindex(A, B...)` now requires that all inputs have the same number of elements. When the chosen indexing is Cartesian, they must have the same axes. + * `AbstractRange` objects are now considered as equal to other `AbstractArray` objects + by `==` and `isequal` if all of their elements are equal ([#16401]). + This has required changing the hashing algorithm: ranges now use an O(N) fallback + instead of a O(1) specialized method unless they define the `Base.TypeRangeStep` + trait; see its documentation for details. Types which support subtraction (operator + `-`) must now implement `widen` for hashing to work inside heterogeneous arrays. + Library improvements -------------------- diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 00414ecacf6d7..1cef6185c6ace 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -1547,9 +1547,6 @@ function isequal(A::AbstractArray, B::AbstractArray) if axes(A) != axes(B) return false end - if isa(A,AbstractRange) != isa(B,AbstractRange) - return false - end for (a, b) in zip(A, B) if !isequal(a, b) return false @@ -1570,9 +1567,6 @@ function (==)(A::AbstractArray, B::AbstractArray) if axes(A) != axes(B) return false end - if isa(A,AbstractRange) != isa(B,AbstractRange) - return false - end anymissing = false for (a, b) in zip(A, B) eq = (a == b) @@ -1944,19 +1938,92 @@ unshift!(A, a, b, c...) = unshift!(unshift!(A, c...), a, b) const hashaa_seed = UInt === UInt64 ? 0x7f53e68ceb575e76 : 0xeb575e76 const hashrle_seed = UInt === UInt64 ? 0x2aab8909bfea414c : 0xbfea414c -function hash(a::AbstractArray, h::UInt) +const hashr_seed = UInt === UInt64 ? 0x80707b6821b70087 : 0x21b70087 + +# Efficient O(1) method equivalent to the O(N) AbstractArray fallback, +# which works only for ranges with regular step (RangeStepRegular) +function hash_range(r::AbstractRange, h::UInt) + h += hashaa_seed + h += hash(size(r)) + + length(r) == 0 && return h + h = hash(first(r), h) + length(r) == 1 && return h + length(r) == 2 && return hash(last(r), h) + + h += hashr_seed + h = hash(length(r), h) + h = hash(last(r), h) +end + +function hash(a::AbstractArray{T}, h::UInt) where T + # O(1) hashing for types with regular step + if isa(a, AbstractRange) && isa(TypeRangeStep(a), RangeStepRegular) + return hash_range(a, h) + end + h += hashaa_seed h += hash(size(a)) state = start(a) done(a, state) && return h + x1, state = next(a, state) + done(a, state) && return hash(x1, h) x2, state = next(a, state) - done(a, state) && return hash(x2, h) + done(a, state) && return hash(x2, hash(x1, h)) + + # Check whether the array is equal to a range, and hash the elements + # at the beginning of the array as such as long as they match this assumption + # This needs to be done even for non-RangeStepRegular types since they may still be equal + # to RangeStepRegular values (e.g. 1.0:3.0 == 1:3) + if isa(a, AbstractVector) && applicable(-, x2, x1) + n = 1 + local step, laststep, laststate + while true + # If overflow happens with entries of the same type, a cannot be equal + # to a range with more than two elements because more extreme values + # cannot be represented. We must still hash the two first values as a + # range since they can always be considered as such (in a wider type) + if isconcrete(T) + try + step = x2 - x1 + catch err + isa(err, OverflowError) || rethrow(err) + break + end + # If true, wraparound overflow happened + sign(step) == cmp(x2, x1) || break + else + # widen() is here to ensure no overflow can happen + step = widen(x2) - widen(x1) + end + n > 1 && !isequal(step, laststep) && break + n += 1 + x1 = x2 + laststep = step + laststate = state + done(a, state) && break + x2, state = next(a, state) + end - x1 = x2 - while !done(a, state) - x1 = x2 - x2, state = next(a, state) + h = hash(first(a), h) + h += hashr_seed + # Always hash at least the two first elements as a range (even in case of overflow) + if n < 2 + h = hash(2, h) + h = hash(x2, h) + done(a, state) && return h + x1 = x2 + x2, state = next(a, state) + else + h = hash(n, h) + h = hash(x1, h) + done(a, laststate) && return h + end + end + + # Hash elements which do not correspond to a range (if any) + while true if isequal(x2, x1) # For repeated elements, use run length encoding # This allows efficient hashing of sparse arrays @@ -1970,7 +2037,10 @@ function hash(a::AbstractArray, h::UInt) h = hash(runlength, h) end h = hash(x1, h) + done(a, state) && break + x1 = x2 + x2, state = next(a, state) end !isequal(x2, x1) && (h = hash(x2, h)) return h -end +end \ No newline at end of file diff --git a/base/char.jl b/base/char.jl index 7c7595309aa7a..717881c37d05c 100644 --- a/base/char.jl +++ b/base/char.jl @@ -88,6 +88,7 @@ in(x::Char, y::Char) = x == y isless(x::Char, y::Char) = reinterpret(UInt32, x) < reinterpret(UInt32, y) hash(x::Char, h::UInt) = hash_uint64(((reinterpret(UInt32, x) + UInt64(0xd4d64234)) << 32) ⊻ UInt64(h)) +widen(::Type{Char}) = Char -(x::Char, y::Char) = Int(x) - Int(y) -(x::Char, y::Integer) = Char(Int32(x) - Int32(y)) diff --git a/base/hashing.jl b/base/hashing.jl index e2847342a8c57..caa1be81036aa 100644 --- a/base/hashing.jl +++ b/base/hashing.jl @@ -11,7 +11,9 @@ optional second argument `h` is a hash code to be mixed with the result. New types should implement the 2-argument form, typically by calling the 2-argument `hash` method recursively in order to mix hashes of the contents with each other (and with `h`). Typically, any type that implements `hash` should also implement its own `==` (hence -`isequal`) to guarantee the property mentioned above. +`isequal`) to guarantee the property mentioned above. Types supporting subtraction +(operator `-`) should also implement [`widen`](@ref), which is required to hash +values inside heterogeneous arrays. """ hash(x::Any) = hash(x, zero(UInt)) hash(w::WeakRef, h::UInt) = hash(w.value, h) @@ -73,12 +75,3 @@ else end hash(x::QuoteNode, h::UInt) = hash(x.value, hash(QuoteNode, h)) - -# hashing ranges by component at worst leads to collisions for very similar ranges -const hashr_seed = UInt === UInt64 ? 0x80707b6821b70087 : 0x21b70087 -function hash(r::AbstractRange, h::UInt) - h += hashr_seed - h = hash(first(r), h) - h = hash(step(r), h) - h = hash(last(r), h) -end diff --git a/base/hashing2.jl b/base/hashing2.jl index 55d67ca9caef9..83ef9ea09683f 100644 --- a/base/hashing2.jl +++ b/base/hashing2.jl @@ -178,4 +178,4 @@ function hash(s::Union{String,SubString{String}}, h::UInt) # note: use pointer(s) here (see #6058). ccall(memhash, UInt, (Ptr{UInt8}, Csize_t, UInt32), pointer(s), sizeof(s), h % UInt32) + h end -hash(s::AbstractString, h::UInt) = hash(String(s), h) +hash(s::AbstractString, h::UInt) = hash(String(s), h) \ No newline at end of file diff --git a/base/irrationals.jl b/base/irrationals.jl index 4f55783514fe1..5b863b8467cb1 100644 --- a/base/irrationals.jl +++ b/base/irrationals.jl @@ -116,6 +116,8 @@ isone(::AbstractIrrational) = false hash(x::Irrational, h::UInt) = 3*object_id(x) - h +widen(::Type{T}) where {T<:Irrational} = T + -(x::AbstractIrrational) = -Float64(x) for op in Symbol[:+, :-, :*, :/, :^] @eval $op(x::AbstractIrrational, y::AbstractIrrational) = $op(Float64(x),Float64(y)) diff --git a/base/operators.jl b/base/operators.jl index 99a7bea638a19..a3b383c7c18b8 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -771,9 +771,11 @@ conj(x) = x """ widen(x) -If `x` is a type, return a "larger" type (for numeric types, this will be -a type with at least as much range and precision as the argument, and usually more). -Otherwise `x` is converted to `widen(typeof(x))`. +If `x` is a type, return a "larger" type, defined so that arithmetic operations +`+` and `-` are guaranteed not to overflow nor lose precision for any combination +of values that type `x` can hold. + +If `x` is a value, it is converted to `widen(typeof(x))`. # Examples ```jldoctest @@ -784,7 +786,7 @@ julia> widen(1.5f0) 1.5 ``` """ -widen(x::T) where {T<:Number} = convert(widen(T), x) +widen(x::T) where {T} = convert(widen(T), x) # function pipelining diff --git a/base/range.jl b/base/range.jl index 39698d436ba15..b46bad0ac07ee 100644 --- a/base/range.jl +++ b/base/range.jl @@ -79,6 +79,9 @@ range(a::AbstractFloat, st::Real, len::Integer) = range(a, float(st), len) abstract type AbstractRange{T} <: AbstractArray{T,1} end +TypeRangeStep(::Type{<:AbstractRange}) = RangeStepIrregular() +TypeRangeStep(::Type{<:AbstractRange{<:Integer}}) = RangeStepRegular() + ## ordinal ranges abstract type OrdinalRange{T,S} <: AbstractRange{T} end diff --git a/base/traits.jl b/base/traits.jl index 3efad150e1ff8..7bd470a1bdea8 100644 --- a/base/traits.jl +++ b/base/traits.jl @@ -20,3 +20,40 @@ TypeArithmetic(instance) = TypeArithmetic(typeof(instance)) TypeArithmetic(::Type{<:AbstractFloat}) = ArithmeticRounds() TypeArithmetic(::Type{<:Integer}) = ArithmeticOverflows() TypeArithmetic(::Type{<:Any}) = ArithmeticUnknown() + +# trait for objects that support ranges with regular step +""" + TypeRangeStep(instance) + TypeRangeStep(T::Type) + +Indicate whether an instance or a type supports constructing a range with +a perfectly regular step or not. A regular step means that +[`step`](@ref) will always be exactly equal to the difference between two +subsequent elements in a range, i.e. for a range `r::AbstractRange{T}`: +```julia +all(diff(r) .== step(r)) +``` + +When a type `T` always leads to ranges with regular steps, it should +define the following method: +```julia +Base.TypeRangeStep(::Type{<:AbstractRange{<:T}}) = Base.RangeStepRegular() +``` +This will allow [`hash`](@ref) to use an O(1) algorithm for `AbstractRange{T}` +objects instead of the default O(N) algorithm (with N the length of the range). + +In some cases, whether the step will be regular depends not only on the +element type `T`, but also on the type of the step `S`. In that case, more +specific methods should be defined: +```julia +Base.TypeRangeStep(::Type{<:OrdinalRange{<:T, <:S}}) = Base.RangeStepRegular() +``` + +By default, all range types are assumed to be `RangeStepIrregular`, except +ranges with an element type which is a subtype of `Integer`. +""" +abstract type TypeRangeStep end +struct RangeStepRegular <: TypeRangeStep end # range with regular step +struct RangeStepIrregular <: TypeRangeStep end # range with rounding error + +TypeRangeStep(instance) = TypeRangeStep(typeof(instance)) diff --git a/stdlib/Dates/src/periods.jl b/stdlib/Dates/src/periods.jl index 37ea6504ab887..1a203ad3fbcde 100644 --- a/stdlib/Dates/src/periods.jl +++ b/stdlib/Dates/src/periods.jl @@ -101,6 +101,7 @@ end # intfuncs Base.gcdx(a::T, b::T) where {T<:Period} = ((g, x, y) = gcdx(value(a), value(b)); return T(g), x, y) Base.abs(a::T) where {T<:Period} = T(abs(value(a))) +Base.sign(x::Period) = sign(value(x)) periodisless(::Period,::Year) = true periodisless(::Period,::Month) = true diff --git a/stdlib/Dates/src/ranges.jl b/stdlib/Dates/src/ranges.jl index ba9cfc88d3b41..a6235c7755205 100644 --- a/stdlib/Dates/src/ranges.jl +++ b/stdlib/Dates/src/ranges.jl @@ -38,3 +38,7 @@ Base.done(r::StepRange{<:TimeType,<:Period}, i::Integer) = length(r) <= i +(x::Period, r::AbstractRange{<:TimeType}) = (x + first(r)):step(r):(x + last(r)) +(r::AbstractRange{<:TimeType}, x::Period) = x + r -(r::AbstractRange{<:TimeType}, x::Period) = (first(r)-x):step(r):(last(r)-x) + +# Combinations of types and periods for which the range step is regular +Base.TypeRangeStep(::Type{<:OrdinalRange{<:TimeType, <:FixedPeriod}}) = + Base.RangeStepRegular() diff --git a/stdlib/Dates/test/periods.jl b/stdlib/Dates/test/periods.jl index 455d5a8116cd7..27f3c1bd8e553 100644 --- a/stdlib/Dates/test/periods.jl +++ b/stdlib/Dates/test/periods.jl @@ -26,6 +26,9 @@ using Test @test mod.([t, t, t, t, t], Dates.Year(2)) == ([t, t, t, t, t]) @test [t, t, t] / t2 == [0.5, 0.5, 0.5] @test abs(-t) == t + @test sign(t) == sign(t2) == 1 + @test sign(-t) == sign(-t2) == -1 + @test sign(Dates.Year(0)) == 0 end @testset "div/mod/gcd/lcm/rem" begin @test Dates.Year(10) % Dates.Year(4) == Dates.Year(2) diff --git a/stdlib/Dates/test/ranges.jl b/stdlib/Dates/test/ranges.jl index 185a31363f5a8..7e9c51cdc0722 100644 --- a/stdlib/Dates/test/ranges.jl +++ b/stdlib/Dates/test/ranges.jl @@ -38,6 +38,8 @@ let @test !(l1 in dr) @test !(f1 - pos_step in dr) @test !(l1 + pos_step in dr) + @test dr == [] + @test hash(dr) == hash([]) for (f, l) in ((f2, l2), (f3, l3), (f4, l4)) dr = f:pos_step:l @@ -58,6 +60,8 @@ let @test length(dr1) == len @test findin(dr, dr) == [1:len;] @test length([dr;]) == len + @test dr == dr1 + @test hash(dr) == hash(dr1) end @test !isempty(reverse(dr)) @test length(reverse(dr)) == len @@ -92,6 +96,8 @@ let @test !(l1 in dr) @test !(l1 - neg_step in dr) @test !(l1 + neg_step in dr) + @test dr == [] + @test hash(dr) == hash([]) for (f, l) in ((f2, l2), (f3, l3), (f4, l4)) dr = l:neg_step:f @@ -112,6 +118,8 @@ let @test length(dr1) == len @test findin(dr, dr) == [1:len;] @test length([dr;]) == len + @test dr == dr1 + @test hash(dr) == hash(dr1) end @test !isempty(reverse(dr)) @test length(reverse(dr)) == len @@ -148,6 +156,8 @@ let @test !(l1 in dr) @test !(f1 - pos_step in dr) @test !(l1 + pos_step in dr) + @test dr == [] + @test hash(dr) == hash([]) for (f, l) in ((f2, l2), (f3, l3), (f4, l4)) dr = f:pos_step:l @@ -168,6 +178,8 @@ let @test length(dr1) == len @test findin(dr, dr) == [1:len;] @test length([dr;]) == len + @test dr == dr1 + @test hash(dr) == hash(dr1) end @test !isempty(reverse(dr)) @test length(reverse(dr)) == len @@ -202,6 +214,8 @@ let @test !(l1 in dr) @test !(l1 - neg_step in dr) @test !(l1 + neg_step in dr) + @test dr == [] + @test hash(dr) == hash([]) for (f, l) in ((f2, l2), (f3, l3), (f4, l4)) dr = l:neg_step:f @@ -222,6 +236,8 @@ let @test length(dr1) == len @test findin(dr, dr) == [1:len;] @test length([dr;]) == len + @test dr == dr1 + @test hash(dr) == hash(dr1) end @test !isempty(reverse(dr)) @test length(reverse(dr)) == len diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 5b1ec4bd6cf89..a3149a9bb0223 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -388,8 +388,8 @@ function test_vector_indexing(::Type{T}, shape, ::Type{TestAbstractArray}) where idxs = rand(1:N, 3, 3, 3) @test B[idxs] == A[idxs] == idxs @test B[vec(idxs)] == A[vec(idxs)] == vec(idxs) - @test B[:] == A[:] == collect(1:N) - @test B[1:end] == A[1:end] == collect(1:N) + @test B[:] == A[:] == 1:N + @test B[1:end] == A[1:end] == 1:N @test B[:,:,trailing2] == A[:,:,trailing2] == B[:,:,1,trailing3] == A[:,:,1,trailing3] B[1:end,1:end,trailing2] == A[1:end,1:end,trailing2] == B[1:end,1:end,1,trailing3] == A[1:end,1:end,1,trailing3] diff --git a/test/arrayops.jl b/test/arrayops.jl index 6a8f47d22712b..b3200069ceeb1 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -1125,7 +1125,7 @@ end # base case w/ Vector a = collect(1:10) filter!(x -> x > 5, a) - @test a == collect(6:10) + @test a == 6:10 # different subtype of AbstractVector ba = rand(10) .> 0.5 @@ -1761,8 +1761,8 @@ end for A in (reshape(collect(1:20), 4, 5), reshape(1:20, 4, 5)) local A - @test slicedim(A, 1, 2) == collect(2:4:20) - @test slicedim(A, 2, 2) == collect(5:8) + @test slicedim(A, 1, 2) == 2:4:20 + @test slicedim(A, 2, 2) == 5:8 @test_throws ArgumentError slicedim(A,0,1) @test slicedim(A, 3, 1) == A @test_throws BoundsError slicedim(A, 3, 2) diff --git a/test/broadcast.jl b/test/broadcast.jl index 849d44be7f694..1aef1408031e5 100644 --- a/test/broadcast.jl +++ b/test/broadcast.jl @@ -211,7 +211,7 @@ let a = sin.([1, 2]) end # PR #17300: loop fusion -@test (x->x+1).((x->x+2).((x->x+3).(1:10))) == collect(7:16) +@test (x->x+1).((x->x+2).((x->x+3).(1:10))) == 7:16 let A = [sqrt(i)+j for i = 1:3, j=1:4] @test atan2.(log.(A), sum(A,1)) == broadcast(atan2, broadcast(log, A), sum(A, 1)) end diff --git a/test/char.jl b/test/char.jl index 17b2c8eabf04b..9163122c95659 100644 --- a/test/char.jl +++ b/test/char.jl @@ -17,6 +17,8 @@ @test 'b' - 1 == 'a' @test typeof('b' - 1) == Char +@test widen('a') === 'a' + let numberchars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] lowerchars = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] diff --git a/test/functional.jl b/test/functional.jl index 7b15585cbc59d..d7b9026ea61cb 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -113,17 +113,17 @@ end let gen = (x * y for x in 1:10, y in 1:10) @test collect(gen) == collect(1:10) .* collect(1:10)' @test first(gen) == 1 - @test collect(gen)[1:10] == collect(1:10) + @test collect(gen)[1:10] == 1:10 end let gen = Base.Generator(+, 1:10, 1:10, 1:10) @test first(gen) == 3 - @test collect(gen) == collect(3:3:30) + @test collect(gen) == 3:3:30 end let gen = (x for x in 1:10 if x % 2 == 0), gen2 = Iterators.filter(x->x % 2 == 0, x for x in 1:10) @test collect(gen) == collect(gen2) - @test collect(gen) == collect(2:2:10) + @test collect(gen) == 2:2:10 end let gen = ((x,y) for x in 1:10, y in 1:10 if x % 2 == 0 && y % 2 == 0), diff --git a/test/hashing.jl b/test/hashing.jl index 67947d74bfeb6..0da3f859a3a85 100644 --- a/test/hashing.jl +++ b/test/hashing.jl @@ -62,7 +62,14 @@ end # hashing collections (e.g. issue #6870) vals = Any[ [1,2,3,4], [1 3;2 4], Any[1,2,3,4], [1,3,2,4], + [1.0, 2.0, 3.0, 4.0], BigInt[1, 2, 3, 4], [1,0], [true,false], BitArray([true,false]), + # Irrationals + Any[1, pi], [1, pi], [pi, pi], Any[pi, pi], + # Overflow with Int8 + Any[Int8(127), Int8(-128), -383], 127:-255:-383, + # Loss of precision with Float64 + Any[-2^53-1, 0.0, 2^53+1], [-2^53-1, 0, 2^53+1], (-2^53-1):2^53+1:(2^53+1), Set([1,2,3,4]), Set([1:10;]), # these lead to different key orders Set([7,9,4,10,2,3,5,8,6,1]), # @@ -75,11 +82,89 @@ vals = Any[ [], [1], [2], [1, 1], [1, 2], [1, 3], [2, 2], [1, 2, 2], [1, 3, 3], zeros(2, 2), spzeros(2, 2), Matrix(1.0I, 2, 2), sparse(1.0I, 2, 2), sparse(ones(2, 2)), ones(2, 2), sparse([0 0; 1 0]), [0 0; 1 0], - [-0. 0; -0. 0.], SparseMatrixCSC(2, 2, [1, 3, 3], [1, 2], [-0., -0.]) + [-0. 0; -0. 0.], SparseMatrixCSC(2, 2, [1, 3, 3], [1, 2], [-0., -0.]), + # issue #16364 + 1:4, 1:1:4, 1:-1:0, 1.0:4.0, 1.0:1.0:4.0, linspace(1, 4, 4), + 'a':'e', ['a', 'b', 'c', 'd', 'e'], + # check that hash is still consistent with heterogeneous arrays for which - is defined + # for some pairs and not others + ["a", "b", 1, 2], ["a", 1, 2], ["a", "b", 2, 2], ["a", "a", 1, 2], ["a", "b", 2, 3] ] for a in vals, b in vals @test isequal(a,b) == (hash(a)==hash(b)) + if a isa AbstractArray + @test hash(a) == hash(collect(a)) == hash(collect(Any, a)) + end +end + +vals = Any[ + Int[], Float64[], + [0], [1], [2], + # test vectors starting with ranges + [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6], + [2, 1], [3, 2, 1], [4, 3, 2, 1], [5, 4, 3, 2, 1], [5, 4, 3, 2, 1, 0, -1], + # test vectors starting with ranges which trigger overflow with Int8 + [124, 125, 126, 127], [124, 125, 126, 127, -128], [-128, 127, -128], + # test vectors including ranges + [2, 1, 2, 3], [2, 3, 2, 1], [2, 1, 2, 3, 2], [2, 3, 2, 1, 2], + # test various sparsity patterns + [0, 0], [0, 0, 0], [0, 1], [1, 0], + [0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 2], + [0 0; 0 0], [1 0; 0 0], [0 1; 0 0], [0 0; 1 0], [0 0; 0 1], + [5 1; 0 0], [1 1; 0 1], [0 2; 3 0], [0 2; 4 6], [4 0; 0 1], + [0 0 0; 0 0 0], [1 0 0; 0 0 1], [0 0 2; 3 0 0], [0 0 7; 6 1 2], + [4 0 0; 3 0 1], [0 2 4; 6 0 0], + # various stored zeros patterns + sparse([1], [1], [0]), sparse([1], [1], [-0.0]), + sparse([1, 2], [1, 1], [-0.0, 0.0]), sparse([1, 2], [1, 1], [0.0, -0.0]), + sparse([1, 2], [1, 1], [-0.0, 0.0], 3, 1), sparse([1, 2], [1, 1], [0.0, -0.0], 3, 1), + sparse([1, 3], [1, 1], [-0.0, 0.0], 3, 1), sparse([1, 3], [1, 1], [0.0, -0.0], 3, 1), + sparse([1, 2, 3], [1, 1, 1], [-1, 0, 1], 3, 1), sparse([1, 2, 3], [1, 1, 1], [-1.0, -0.0, 1.0], 3, 1), + sparse([1, 3], [1, 1], [-1, 0], 3, 1), sparse([1, 2], [1, 1], [-1, 0], 3, 1) +] + +for a in vals + b = Array(a) + @test hash(convert(Array{Any}, a)) == hash(b) + @test hash(convert(Array{supertype(eltype(a))}, a)) == hash(b) + @test hash(convert(Array{Float64}, a)) == hash(b) + @test hash(sparse(a)) == hash(b) + if !any(x -> isequal(x, -0.0), a) + @test hash(convert(Array{Int}, a)) == hash(b) + if all(x -> typemin(Int8) <= x <= typemax(Int8), a) + @test hash(convert(Array{Int8}, a)) == hash(b) + end + end +end + +# Test that overflow does not give inconsistent hashes with heterogeneous arrays +@test hash(Any[Int8(1), Int8(2), 255]) == hash([1, 2, 255]) +@test hash(Any[Int8(127), Int8(-128), 129, 130]) == + hash([127, -128, 129, 130]) != hash([127, 128, 129, 130]) + +# Test hashing sparse matrix with type which does not support - +struct CustomHashReal + x::Float64 +end +Base.hash(x::CustomHashReal, h::UInt) = hash(x.x, h) +Base.:(==)(x::CustomHashReal, y::Number) = x.x == y +Base.:(==)(x::Number, y::CustomHashReal) = x == y.x +Base.zero(::Type{CustomHashReal}) = CustomHashReal(0.0) + +let a = sparse([CustomHashReal(0), CustomHashReal(3), CustomHashReal(3)]) + @test hash(a) == hash(Array(a)) +end + +vals = Any[ + 0.0:0.1:0.3, 0.3:-0.1:0.0, + 0:-1:1, 0.0:-1.0:1.0, 0.0:1.1:10.0, -4:10, + 'a':'e', 'b':'a', + linspace(1, 1, 1), linspace(0.3, 1.0, 3), linspace(1, 1.1, 20) +] + +for a in vals + @test hash(collect(a)) == hash(a) end @test hash(SubString("--hello--",3,7)) == hash("hello") @@ -88,12 +173,6 @@ end @test hash([1,2]) == hash(view([1,2,3,4],1:2)) -# test explicit zeros in SparseMatrixCSC -x = sprand(10, 10, 0.5) -x[1] = 1 -x.nzval[1] = 0 -@test hash(x) == hash(Array(x)) - let a = QuoteNode(1), b = QuoteNode(1.0) @test (hash(a)==hash(b)) == (a==b) end diff --git a/test/iterators.jl b/test/iterators.jl index 27033efe1efd9..e5d8b998884ae 100644 --- a/test/iterators.jl +++ b/test/iterators.jl @@ -413,7 +413,7 @@ for T in (UInt8, UInt16, UInt32, UInt64, UInt128, Int8, Int16, Int128, BigInt) @test length(take(1:6, T(3))) == 3 @test length(drop(1:6, T(3))) == 3 @test length(repeated(1, T(5))) == 5 - @test collect(partition(1:5, T(5)))[1] == collect(1:5) + @test collect(partition(1:5, T(5)))[1] == 1:5 end @testset "collect finite iterators issue #12009" begin diff --git a/test/math.jl b/test/math.jl index 25bb6916b7b7e..97d266253f8fa 100644 --- a/test/math.jl +++ b/test/math.jl @@ -41,6 +41,7 @@ end @test Float16(3.0) < pi @test pi < Float16(4.0) @test contains(sprint(show,π),"3.14159") + @test widen(pi) === pi end @testset "frexp,ldexp,significand,exponent" begin diff --git a/test/numbers.jl b/test/numbers.jl index 083fd6c19e65b..8699c2d29a747 100644 --- a/test/numbers.jl +++ b/test/numbers.jl @@ -2711,7 +2711,8 @@ end yf = Complex{BigFloat}(1//2 + 1//5*im) yi = 4 - @test x^y ≈ xf^yf + # FIXME: reenable once #25221 is fixed + # @test x^y ≈ xf^yf @test x^yi ≈ xf^yi @test x^true ≈ xf^true @test x^false == xf^false diff --git a/test/random.jl b/test/random.jl index 88eb56a5a34d9..9c25bb00648a7 100644 --- a/test/random.jl +++ b/test/random.jl @@ -490,7 +490,7 @@ let mta = MersenneTwister(42), mtb = MersenneTwister(42) @test shuffle(mta, rand(mta, 2, 3)) == shuffle(mtb, rand(mtb, 2, 3)) @test randperm(mta,10) == randperm(mtb,10) - @test sort!(randperm(10)) == sort!(shuffle(1:10)) == collect(1:10) + @test sort!(randperm(10)) == sort!(shuffle(1:10)) == 1:10 @test randperm(mta,big(10)) == randperm(mtb,big(10)) # cf. #16376 @test randperm(0) == [] @test eltype(randperm(UInt(1))) === Int diff --git a/test/ranges.jl b/test/ranges.jl index db15dc9e98821..b99b08d9756ee 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -324,7 +324,7 @@ end @testset "sort/sort!/partialsort" begin @test sort(UnitRange(1,2)) == UnitRange(1,2) @test sort!(UnitRange(1,2)) == UnitRange(1,2) - @test sort(1:10, rev=true) == collect(10:-1:1) + @test sort(1:10, rev=true) == 10:-1:1 @test sort(-3:3, by=abs) == [0,-1,1,-2,2,-3,3] @test partialsort(1:10, 4) == 4 end @@ -500,28 +500,49 @@ end @test all(((1:5) - [1:5;]) .== 0) end @testset "tricky floating-point ranges" begin - @test [0.1:0.1:0.3;] == [linspace(0.1,0.3,3);] == [1:3;]./10 - @test [0.0:0.1:0.3;] == [linspace(0.0,0.3,4);] == [0:3;]./10 - @test [0.3:-0.1:-0.1;] == [linspace(0.3,-0.1,5);] == [3:-1:-1;]./10 - @test [0.1:-0.1:-0.3;] == [linspace(0.1,-0.3,5);] == [1:-1:-3;]./10 - @test [0.0:0.1:1.0;] == [linspace(0.0,1.0,11);] == [0:10;]./10 - @test [0.0:-0.1:1.0;] == [linspace(0.0,1.0,0);] == [] - @test [0.0:0.1:-1.0;] == [linspace(0.0,-1.0,0);] == [] - @test [0.0:-0.1:-1.0;] == [linspace(0.0,-1.0,11);] == [0:-1:-10;]./10 - @test [1.0:1/49:27.0;] == [linspace(1.0,27.0,1275);] == [49:1323;]./49 - @test [0.0:0.7:2.1;] == [linspace(0.0,2.1,4);] == [0:7:21;]./10 - @test [0.0:1.1:3.3;] == [linspace(0.0,3.3,4);] == [0:11:33;]./10 - @test [0.1:1.1:3.4;] == [linspace(0.1,3.4,4);] == [1:11:34;]./10 - @test [0.0:1.3:3.9;] == [linspace(0.0,3.9,4);] == [0:13:39;]./10 - @test [0.1:1.3:4.0;] == [linspace(0.1,4.0,4);] == [1:13:40;]./10 - @test [1.1:1.1:3.3;] == [linspace(1.1,3.3,3);] == [11:11:33;]./10 - @test [0.3:0.1:1.1;] == [linspace(0.3,1.1,9);] == [3:1:11;]./10 - @test [0.0:1.0:0.0;] == [linspace(0.0,0.0,1);] == [0.0] - @test [0.0:-1.0:0.0;] == [linspace(0.0,0.0,1);] == [0.0] - - @test [0.0:1.0:5.5;] == [0:10:55;]./10 - @test [0.0:-1.0:0.5;] == [] - @test [0.0:1.0:0.5;] == [0.0] + for (start, step, stop, len) in ((1, 1, 3, 3), (0, 1, 3, 4), + (3, -1, -1, 5), (1, -1, -3, 5), + (0, 1, 10, 11), (0, 7, 21, 4), + (0, 11, 33, 4), (1, 11, 34, 4), + (0, 13, 39, 4), (1, 13, 40, 4), + (11, 11, 33, 3), (3, 1, 11, 9), + (0, 10, 55, 0), (0, -1, 5, 0), (0, 10, 5, 0), + (0, 1, 5, 0), (0, -10, 5, 0), (0, -10, 0, 1), + (0, -1, 1, 0), (0, 1, -1, 0), (0, -1, -10, 11)) + r = start/10:step/10:stop/10 + a = collect(start:step:stop)./10 + ra = collect(r) + + @test r == a + @test isequal(r, a) + + @test r == ra + @test isequal(r, ra) + + @test hash(r) == hash(a) + @test hash(r) == hash(ra) + + if len > 0 + l = linspace(start/10, stop/10, len) + la = collect(l) + + @test a == l + @test r == l + @test isequal(a, l) + @test isequal(r, l) + + @test l == la + @test isequal(l, la) + + @test hash(l) == hash(a) + @test hash(l) == hash(la) + end + end + + @test 1.0:1/49:27.0 == linspace(1.0,27.0,1275) == [49:1323;]./49 + @test isequal(1.0:1/49:27.0, linspace(1.0,27.0,1275)) + @test isequal(1.0:1/49:27.0, collect(49:1323)./49) + @test hash(1.0:1/49:27.0) == hash(linspace(1.0,27.0,1275)) == hash(collect(49:1323)./49) @test [prevfloat(0.1):0.1:0.3;] == [prevfloat(0.1), 0.2, 0.3] @test [nextfloat(0.1):0.1:0.3;] == [nextfloat(0.1), 0.2] @@ -666,18 +687,22 @@ end # near-equal ranges @test 0.0:0.1:1.0 != 0.0f0:0.1f0:1.0f0 +# comparing and hashing ranges @testset "comparing and hashing ranges" begin - Rs = AbstractRange[1:2, map(Int32,1:3:17), map(Int64,1:3:17), 1:0, 17:-3:0, + Rs = AbstractRange[1:1, 1:1:1, 1:2, 1:1:2, + map(Int32,1:3:17), map(Int64,1:3:17), 1:0, 1:-1:0, 17:-3:0, 0.0:0.1:1.0, map(Float32,0.0:0.1:1.0), + 1.0:eps():1.0 .+ 10eps(), 9007199254740990.:1.0:9007199254740994, linspace(0, 1, 20), map(Float32, linspace(0, 1, 20))] for r in Rs local r ar = collect(r) - @test r != ar - @test !isequal(r,ar) + @test r == ar + @test isequal(r,ar) + @test hash(r) == hash(ar) for s in Rs as = collect(s) - @test !isequal(r,s) || hash(r)==hash(s) + @test isequal(r,s) == (hash(r)==hash(s)) @test (r==s) == (ar==as) end end diff --git a/test/reduce.jl b/test/reduce.jl index 50bd4ac693e43..185ed4c6411e9 100644 --- a/test/reduce.jl +++ b/test/reduce.jl @@ -280,10 +280,10 @@ end # 19151 - always short circuit let c = Int[], d = Int[], A = 1:9 all((push!(c, x); x < 5) for x in A) - @test c == collect(1:5) + @test c == 1:5 any((push!(d, x); x > 4) for x in A) - @test d == collect(1:5) + @test d == 1:5 end # any/all with non-boolean collections diff --git a/test/sparse/sparse.jl b/test/sparse/sparse.jl index 4dc81a7619e53..1dc792bda0f86 100644 --- a/test/sparse/sparse.jl +++ b/test/sparse/sparse.jl @@ -862,7 +862,7 @@ end X=reshape([trues(10); falses(15)],5,5) @test A[lininds] == A[X] == [1,0,0,0,0,0,1,0,0,0] A[lininds] = [1:10;] - @test A[lininds] == A[X] == collect(1:10) + @test A[lininds] == A[X] == 1:10 A[lininds] = zeros(Int, 10) @test nnz(A) == 13 @test count(!iszero, A) == 3 @@ -1488,7 +1488,7 @@ end @testset "expandptr" begin local A = sparse(1.0I, 5, 5) - @test Base.SparseArrays.expandptr(A.colptr) == collect(1:5) + @test Base.SparseArrays.expandptr(A.colptr) == 1:5 A[1,2] = 1 @test Base.SparseArrays.expandptr(A.colptr) == [1; 2; 2; 3; 4; 5] @test_throws ArgumentError Base.SparseArrays.expandptr([2; 3]) diff --git a/test/sparse/sparsevector.jl b/test/sparse/sparsevector.jl index 94ef0bf7bac2a..98061a89f22c5 100644 --- a/test/sparse/sparsevector.jl +++ b/test/sparse/sparsevector.jl @@ -1074,7 +1074,7 @@ end sv = sparse(1:10) sm = convert(SparseMatrixCSC, sv) sv[1] = 0 -@test Array(sm)[2:end] == collect(2:10) +@test Array(sm)[2:end] == 2:10 # Ensure that sparsevec with all-zero values returns an array of zeros @test sparsevec([1,2,3],[0,0,0]) == [0,0,0] diff --git a/test/tuple.jl b/test/tuple.jl index c5b3d1ba65b0b..b3020d5700045 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -3,7 +3,7 @@ struct BitPerm_19352 p::NTuple{8,UInt8} function BitPerm(p::NTuple{8,UInt8}) - sort(collect(p)) != collect(0:7) && error("$p is not a permutation of 0:7") + sort(collect(p)) != 0:7 && error("$p is not a permutation of 0:7") new(p) end BitPerm_19352(xs::Vararg{Any,8}) = BitPerm(map(UInt8, xs)) @@ -163,8 +163,8 @@ end @test_throws BoundsError next((5,6,7), 0) @test_throws BoundsError next((), 1) - @test collect(eachindex((2,5,"foo"))) == collect(1:3) - @test collect(eachindex((2,5,"foo"), (1,2,5,7))) == collect(1:4) + @test eachindex((2,5,"foo")) === 1:3 + @test eachindex((2,5,"foo"), (1,2,5,7)) === 1:4 end