-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add replace & replace! for collections #22324
Conversation
30e97aa
to
d0c2d57
Compare
test/algorithms.jl
Outdated
@@ -0,0 +1,28 @@ | |||
|
|||
@testset "replace! & replace" begin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new test files would need to be added to test/choosetests.jl to actually run, but aren't there existing files with related tests this would fit with?
base/algorithms.jl
Outdated
@@ -0,0 +1,119 @@ | |||
module Algorithms |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
while reorganizing some of base into modules is a good idea, I don't see why these two functions make sense as the only thing in an Algorithms module
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I forgot to mention that in the OP. It's just that I didn't know to put those functions, but I open to suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems they'd simply belong in the files corresponding to the types they're implemented for? That is, base/abstractarray.jl, base/set.jl, and base/associative.jl. I agree it doesn't make sense to separate these into their own module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But where to put the general methods which work on Union{AbstractArray,Associative,AbstractSet}
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
find*
methods are defined in array.jl
even when they apply to any iterator, which isn't ideal but I guess OK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just saw that the unique
methods are defined in set.jl
and multidimensional
; the reason seems to be that they are defined right after the needed tools (e.g. Set
or multidim tools) are defined. This would make much more sense to me put those functions also in an "algorithm.jl" file. E.g. I would expect to find in "set.jl" only the functions implementing the interface of Set
, and possibly methods specialized for Set
, but functions merely using a set in their implementation have nothing to do there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But then what's are the boundaries of this file? Any function uses an "algorithm". Maybe we'd need a collections.jl file with all generic functions which apply to any collection. Anyway, better use an existing file for this PR, and open another one to regroup functions in a subsequent PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree "algorithm" is a bit vague (I "algorihtms" like in the c++ STL...), but no problem I will update and follow existing practices here.
base/exports.jl
Outdated
@@ -1351,6 +1351,9 @@ export | |||
nzrange, | |||
nnz, | |||
|
|||
# Algorithms module re-exports | |||
replace!, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs to be added to stdlib doc index
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
base/algorithms.jl
Outdated
julia> replace!(x->x.first=>3, Dict(1=>2, 3=>4), 1) do (k, v) | ||
v < 3 | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there shouldn't be an empty line between input and output, should there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No indeed.
base/algorithms.jl
Outdated
|
||
Return a copy of collection `A` where all occurrences `x` for which | ||
`pred(x)` is true are replaced by `new` or `f(x)` (exactly one among | ||
`f` and `new` must be specified). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to list the different signatures instead of trying to explain constraints on non-syntactic square brackets here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall I put the 2 stacked signatures (one below the other), followed by a common doctstring?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that would be clearer, yes
Given this is new behaviour not needed by Base itself, could it live in https://github.com/JuliaCollections/DataStructures.jl or another package instead? |
Why not, but this is very basic functionality... For example, C++ which is quite barebones in its standard library, has a |
A lot of what is in the C++ standard library (e.g. deque, sorted sets/maps, etc.) lives in other packages such as DataStructures.jl. Some functionality which exists in the C++ standard library was moved from Base out to other packages (e.g. priority queues, combinatorics, etc.). Code in packages can be updated more frequently and doesn't contribute to Base size or the time it takes to run Julia's tests. Just thought I'd suggest it, since this has come up for discussion before in cases where the code is not as easily decoupled from Base. |
Technically since |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds useful, thanks!
base/algorithms.jl
Outdated
Replace all occurrences `x` in collection `A` for which `pred(x)` is true | ||
by `new` or `f(x)` (exactly one among `f` and `new` must be specified). | ||
If `count` is specified, then replace at most `count` occurrences. | ||
This is the in-place version of [`replace`](@ref). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we usually refer to copying versions of the functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thougth it was a good thing to document more related functions in docstrings.
base/algorithms.jl
Outdated
!!! note | ||
When `A` is an `Associative` or `AbstractSet` collection, if | ||
there are collisions among old and newly created keys, the result | ||
can be unexpected: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"can be unexpected" is a bit vague: the last insert key wins, which can be undefined results when the iteration order is undefined. For Set
, is the order defined or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I believe the text in Documenter !!! note
blocks has to be indented four spaces.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The order is not defined for Set
, but reproducible, so I chose values which work to illustrate my point. Will try to find a better wording.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it better now?
base/algorithms.jl
Outdated
`pred(x)` is true are replaced by `new` or `f(x)` (exactly one among | ||
`f` and `new` must be specified). | ||
If `count` is given, then replace at most `count` occurrences. | ||
See the in-place version [`replace!`](@ref) for examples. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better repeat examples (that's what we do e.g. for sum
).
base/algorithms.jl
Outdated
replace!(pred, A::ReplaceCollection, new, n::Integer=-1) = replace!(pred, y->new, A, n) | ||
|
||
""" | ||
replace(pred, [f::Function], A, [new], [count]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
count
is called n
for the AbstractString
method. Better stay consistent (in one direction or the other).
base/algorithms.jl
Outdated
```jldoctest | ||
julia> a = [1, 2, 3, 1]; | ||
|
||
julia> replace!(isodd, a, 0, 2); a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why ; a
? Doesn't the function return it already? BTW, maybe it would be useful to return count
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was to clarify that a
is modified in-place, but this may not be necessary. As to return count
, I see that it could be useful, but I'm uncomfortable with the inconsistency of having the non-mutating replace
returning the collection instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The inconsistency with replace
doesn't sound a big issue to me, but I would be more concerned about consistency with other in-place functions.
base/algorithms.jl
Outdated
If `count` is given, then replace at most `count` occurrences. | ||
See the in-place version [`replace!`](@ref) for examples. | ||
""" | ||
replace(pred, new::Function, A::ReplaceCollection, n::Integer=-1) = replace!(pred, new, copy(A), n) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrap line at 92 chars.
base/algorithms.jl
Outdated
``` | ||
""" | ||
function replace!(pred, new::Function, A::AbstractArray, n::Integer=-1) | ||
n == 0 && return A |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The AbstractString
method uses n=0
by default, so it means "unlimited". Your choice sounds better since it allows using 0
for a no-op, which can be useful if that's the result of a computation, but better adapt the existing method then. Would also make sense to check that n > -1
to prevent unexpected things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I try propose to update the existing method in #22325. I will document the behavior for negatibe inputs, but I'm not sure it's useful to prevent n < -1
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm converting to a default value of typemax(Int)
, like for the string method.
base/algorithms.jl
Outdated
A | ||
end | ||
|
||
const ReplaceCollection = Union{AbstractArray,Associative,AbstractSet} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you should leave the signature unrestricted until we have a MutableCollection
type? Else, it won't be extensible for custom package types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid ambiguities, then pred
has to be constrained to be a Function
(or Base.Callable
), which seems OK.
base/algorithms.jl
Outdated
|
||
# Examples | ||
```jldoctest | ||
julia> replace!(Set([1, 2, 3]), 1, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not illustrate this with a plain Array
first?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because a previous method of the same method (hence in the same docstring) was already having an example with an Array
, so I tried to vary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fine to show examples using more complex structures, but I would start with the simplest example. Else it's not immediately obvious whether Set
is particularly associated with that method or whether that's an arbitrary variation.
FWIW, I have found out for Maybe something to consider to make |
The string |
I don't know CategoricalArrays yet, but for the
It may be possible to replace subsets for collection too... (but than may require to be more strict type for the |
Sorry, I guess my comment wasn't clear. What I mean is that using pairs allows The element vs. subset debate reminds me of issues discussed in the Search & Find Julep, with |
Seems like that concern does fit in with the Julep. If there was a Lately I've been feeling that the string methods don't really jive with their iterator/array counterparts. But that's a general issue for somewhere else.
Suppose there was an iterator version of replace |
Yes, but at that point we would also add a specialized |
Ah, right, I forgot about that deprecation, sorry. |
I think I addressed all the comments. I still have to know how to set the link to On the design level:
It would be cleaner to have instead |
Can you elaborate? I think generally we point the docstring to the function, which concatenates docstrings for all methods. That's not ideal, but at least it includes the expected docstring.
Actually, no, the pairs should be passed as a varargs so that the compiler is able to unroll the checks rather than using a loop. With a small number of pairs, a dict isn't faster than repeated equality tests. But you're right that the handling of
Yes, that's a tough call. The API based on pairs would solve this. |
base/array.jl
Outdated
|
||
## replace/replace! ## | ||
|
||
# NOTE: to implement replace! and replace for a new type T, it's enough to define: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is the place to document this. Though I don't really know where it could go. Also, I don't like recommending people to override an unexported function whose name starts with an underscore. Really, they can reimplement the three lines in replace
that appear before the call to _replace
, that's a negligible repetition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I will replace _replace
by replace
in the recommandation. Until a better place is suggested, I will keep it here though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not have this at all, AFAIK we don't generally do this, and people are not supposed to read this file to find out...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This note is intended also for people adding a new method in Base... What do you think about keeping/removing this note @tkelman ?
By specifying the signatures in .md files, it's possible to show only the docstring for the string version in "strings.md", and the general collections version in "collection.md". But unfortunately, the link from
OK, I see. Then indeed, why not set the
I don't see that. Assuming there is |
I see. Maybe something like
Mmm, actually I was thinking about the possible ambiguity with the
Interesting! It makes sense to do everything from a single function indeed. (Regarding the implementation of the pair version, I'm not sure what's your suggestion but you'd have to be very careful that everything is optimized.) |
So I implemented your idea, and I like it very much:
Compare:
PS: your trick to use |
Cool. I'd rename |
@rfourquet I guess this won't be in 0.7, but maybe we should deprecate |
I like that idea. It would also allow giving multiple replacements, which can potentially be done more efficiently in a single pass. |
That sounds good indeed. I thought we didn't reach a decision yet on this design with pairs, but I still like it. It's true that I didn't plan to get it into 0.7, but mostly because it's not breaking and I didn't want to overload triage, so this could wait for 1.1. But if we go for this design and deprecate the existing |
9b22856
to
124ca4c
Compare
Let me recap the API proposed here, if it can help:
Also included the mutating variant, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! If @StefanKarpinski is fine with merging this now, I think we should go ahead. Most of my comments could be addressed later as they don't affect the API, but I think the AbstractString
-specific method should be deprecated (see below).
base/set.jl
Outdated
|
||
# we use this wrapper because using directly eltype(A) as the type | ||
# parameter below for Some degrades performance | ||
function _replace!(A, ::Type{K}, count::Integer, old_new #=::Pair...=#) where {K} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mark old_new
with ::Tuple{Vararg{Pair}}
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
base/set.jl
Outdated
## replace/replace! ## | ||
|
||
# NOTE: to implement replace! and replace for a new type T, it's enough to define: | ||
# function _replace!(pred::Callable, A::T, count::Int) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I don't really like recommending people to overload an internal function. Is there a way to make this replace!(pred::Callable, A::T; count::Int)
? BTW, keyword arguments are supposed to be faster now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right, I didn't re-evaluate this choice in light of the supposedly faster keyword args. I would be willing to benchmark that, but as I'm very short on time now, I will accept your offer to address that later by giving it a lower priority.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not go with the simplest solution of defining only the keyword argument version for now, and see later whether performance can be improved?
base/set.jl
Outdated
|
||
# Examples | ||
```jldoctest | ||
julia> replace!(Dict(1=>2, 3=>4)) do kv |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a simpler example which doesn't use the do
syntax, and an example using Some
?
base/set.jl
Outdated
replace(prednew::Callable, A; count::Integer=typemax(Int)) = _replace!(prednew, copy(A), count) | ||
|
||
# Handle ambiguities | ||
replace!(a::Callable, b::Pair; count::Integer=-1) = throw(MethodError(replace!, a, b)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be (a, b)
. Else you get a MethodError
, but it happens when failing to create the MethodError
. ;-)
But should these really be errors? It's perfectly valid to have an array of pairs and to replace some pairs with others.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes right! Those were not tested, just inserted to silence some ambiguities. You are right that what you said is perfectly valid, but this method doesn't have an array as parameter! This is just a silly signature, but Julia needs it!
base/set.jl
Outdated
### _replace! for AbstractDict/AbstractSet | ||
|
||
askey(k, ::AbstractDict) = k.first | ||
askey(k, ::AbstractSet) = k |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two convenience functions could interest @andyferris, who tried to unify iteration over sets and dicts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe that would justify defining pairs(::AbstractSet)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How so? That seems backwards from the problem. The issue here seems to be that pop!(::AbstractDict, key)
/ delete!
is inconsistent with the way the rest of the way the Associative interface is defined (e.g. it should remove an element, not a key). For consistency with our other indexed containers, these methods should perhaps be renamed splice!
or deleteat!
. (later in v1.x we can then re-add pop!(dict, element)
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, deleteat!
vs delete!
has been discussed at #20531.
base/set.jl
Outdated
askey(k, ::AbstractDict) = k.first | ||
askey(k, ::AbstractSet) = k | ||
|
||
function _replace_update_hash!(repl::Vector{<:Pair}, x, y::Some) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why it's called "hash". Maybe "dict" instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just had to quicly find a name, and both Dict
and Set
use a hash, but it's a wrong name indeed as this is a helper function for Union{AbstractDict,AbstractSet}
in general
base/set.jl
Outdated
repl = Pair{eltype(A),eltype(A)}[] | ||
c = 0 | ||
for x in A | ||
c +=_replace_update_hash!(repl, x, prednew(x)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it possible to do the replacement in a single pass?
Missing space after +=
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's possible for particular containers, but in general we don't know if mutating the container while iterating is valid (e.g. for a hash-table, there could be a rehash after one insertion, and the rest of the iteration may not visit all non-visited elements. As a first step I wanted something always safe, but it's likely that this can be improved at least for Set
and Dict
.
c = 0 | ||
for i in eachindex(A) | ||
c += _replace_update!(A, i, prednew(A[i])) | ||
c == count && break |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's probably an overkill optimization, but I wonder whether using a special loop for count == typemax(Int)
could be worth it in some cases where the function is so simple that SIMD might be enabled without the break
call. Maybe leave that to future improvements after some benchmarking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add that on my todo list to not forget :)
@@ -38,7 +38,7 @@ Base.searchindex | |||
Base.rsearchindex | |||
Base.contains(::AbstractString, ::AbstractString) | |||
Base.reverse(::Union{String,SubString{String}}) | |||
Base.replace | |||
Base.replace(s::AbstractString, pat, f) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This old method is inconsistent with the new ones. Maybe deprecate it in favor of pat => f
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in #25165 :)
test/sets.jl
Outdated
@@ -344,3 +344,37 @@ end | |||
@test typeof(cssset) == Set{String} | |||
@test cssset == Set(["foo", "bar"]) | |||
end | |||
|
|||
@testset "replace! & replace" begin | |||
maybe1(v, p) = if p Some(v) end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point of using Some
is to be able to distinguish nothing
. Can you add a test with Some(nothing)
to ensure this works?
124ca4c
to
4ff02f0
Compare
I addressed most of comments @nalimilan thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Looks good, though it needs a NEWS entry.
The deprecation of the other method should also happen quite quickly since we're theoretically past feature freeze.
c == count && break | ||
end | ||
for oldnew in repl | ||
pop!(A, askey(first(oldnew), A)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this do all deletions before doing all insertions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did it this way to avoid 2 passes, and as a result had to put a warning in the docstring. But you are definitely right. It's much more predictable, and the basic benchmarks I just did don't show any overhead :) thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
splitting the oldnew
replacements list into old
and new
lists might even help performance? (for non-isbits Pair{Key, Value}
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just did a benchmark with strings, and it doesn't seem to make a difference. I think I will not bother now, and will investigate when I have time again, but thanks.
4ff02f0
to
babf788
Compare
@rfourquet I've tried rebasing via GitHub but I apparently failed. Would you do it, so that the PR can be merged ASAP? |
Sure, but I thought it would need a more clear decision? I put earlier today the triage label on the |
Sure, but there seems to be a broad consensus that it's a positive change, so... |
9a0cf08
to
158ddfe
Compare
Triage said yes to this API for strings, so I will take it we go with this one too! CI is remarkably all green and there are not yet conflicts, I would say this is a good oppotunity window for someone to click the green button. |
@rfourquet Can you remind me why we use |
Because I initially used
Sounds like an excellent idea! then it's a bit more than purely replacing values, but after all, |
Cool. We don't necessarily need to allow dropping some elements, but if we stop using |
My preliminary benchmarks seem to indicate mainly no performance regression :)
indeed, except when the replacement value is |
This adds methods to
replace
forAbstractArray
,AbstractSet
andAssociative
and a corresponding inplacereplace!
function.Did I miss already existing similar functionality in Base?
The order in which to put the 2 function arguments
pred
andf
can be discussed.Also,
replace!(f::Function, A, old)
is not included as it would be ambiguous withreplace!(pred, A, new)
(and a new function name would be necessary, e.g.replaceif!
); But it is assumed that in most of the cases, the replacement valuef(x)
can be computed byf(old)
and then usereplace!(A, old, f(old))
. Otherwise,replace!(pred, f, A)
can be used.