Skip to content

Commit 84874c9

Browse files
committed
add replace(io, str, patterns...)
1 parent ce292c1 commit 84874c9

File tree

3 files changed

+53
-11
lines changed

3 files changed

+53
-11
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ New library features
3333
* The `initialized=true` keyword assignment for `sortperm!` and `partialsortperm!`
3434
is now a no-op ([#47979]). It previously exposed unsafe behavior ([#47977]).
3535
* `binomial(x, k)` now supports non-integer `x` ([#48124]).
36+
* `replace(string, pattern...)` now supports an optional `IO` argument to
37+
write the output to a stream rather than returning a string ([#48625]).
3638
* A `CartesianIndex` is now treated as a "scalar" for broadcasting ([#47044]).
3739

3840
Standard library changes

base/strings/util.jl

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -681,8 +681,8 @@ _free_pat_replacer(x) = nothing
681681
_pat_replacer(x::AbstractChar) = isequal(x)
682682
_pat_replacer(x::Union{Tuple{Vararg{AbstractChar}},AbstractVector{<:AbstractChar},Set{<:AbstractChar}}) = in(x)
683683

684-
function replace(str::String, pat_repl::Vararg{Pair,N}; count::Integer=typemax(Int)) where N
685-
count == 0 && return str
684+
# note: leave str untyped here to make it easier for packages like StringViews to hook in
685+
function _replace_init(str, pat_repl::NTuple{N, Pair}, count::Int) where N
686686
count < 0 && throw(DomainError(count, "`count` must be non-negative."))
687687
n = 1
688688
e1 = nextind(str, lastindex(str)) # sizeof(str)
@@ -697,11 +697,11 @@ function replace(str::String, pat_repl::Vararg{Pair,N}; count::Integer=typemax(I
697697
r isa Int && (r = r:r) # findnext / performance fix
698698
return r
699699
end
700-
if all(>(e1), map(first, rs))
701-
foreach(_free_pat_replacer, patterns)
702-
return str
703-
end
704-
out = IOBuffer(sizehint=floor(Int, 1.2sizeof(str)))
700+
return patterns, replaces, rs, all(>(e1), map(first, rs))
701+
end
702+
703+
# note: leave str untyped here to make it easier for packages like StringViews to hook in
704+
function _replace_finish(out::IO, str, count::Int, patterns::NTuple{N}, replaces::NTuple{N}, rs) where N
705705
while true
706706
p = argmin(map(first, rs)) # TODO: or argmin(rs), to pick the shortest first match ?
707707
r = rs[p]
@@ -737,12 +737,34 @@ function replace(str::String, pat_repl::Vararg{Pair,N}; count::Integer=typemax(I
737737
end
738738
foreach(_free_pat_replacer, patterns)
739739
write(out, SubString(str, i))
740-
return String(take!(out))
740+
return out
741+
end
742+
743+
# note: leave str untyped here to make it easier for packages like StringViews to hook in
744+
function _replace_io(out::IO, retval, str, pat_repl::Pair...; count::Integer=typemax(Int))
745+
count == 0 && return out
746+
patterns, replaces, rs, notfound = _replace_init(str, pat_repl, count)
747+
if notfound
748+
foreach(_free_pat_replacer, patterns)
749+
return out
750+
end
751+
return _replace_finish(out, str, count, patterns, replaces, rs)
741752
end
742753

754+
# note: leave str untyped here to make it easier for packages like StringViews to hook in
755+
function _replace_str(str, pat_repl::Pair...; count::Integer=typemax(Int))
756+
count == 0 && return str
757+
patterns, replaces, rs, notfound = _replace_init(str, pat_repl, count)
758+
if notfound
759+
foreach(_free_pat_replacer, patterns)
760+
return str
761+
end
762+
out = IOBuffer(sizehint=floor(Int, 1.2sizeof(str)))
763+
return String(take!(_replace_finish(out, str, count, patterns, replaces, rs)))
764+
end
743765

744766
"""
745-
replace(s::AbstractString, pat=>r, [pat2=>r2, ...]; [count::Integer])
767+
replace([out::IO], s::AbstractString, pat=>r, [pat2=>r2, ...]; [count::Integer])
746768
747769
Search for the given pattern `pat` in `s`, and replace each occurrence with `r`.
748770
If `count` is provided, replace at most `count` occurrences.
@@ -755,13 +777,21 @@ If `pat` is a regular expression and `r` is a [`SubstitutionString`](@ref), then
755777
references in `r` are replaced with the corresponding matched text.
756778
To remove instances of `pat` from `string`, set `r` to the empty `String` (`""`).
757779
780+
The return value is a new string with the replacements. If the `out::IO` argument
781+
is supplied, the transformed string is instead written to `out` (returning `out`).
782+
(For example, this can be used in conjunction with [`IOBuffer`](@ref) to re-use
783+
an pre-allocated buffer array in-place.)
784+
758785
Multiple patterns can be specified, and they will be applied left-to-right
759786
simultaneously, so only one pattern will be applied to any character, and the
760787
patterns will only be applied to the input text, not the replacements.
761788
762789
!!! compat "Julia 1.7"
763790
Support for multiple patterns requires version 1.7.
764791
792+
!!! compat "Julia 1.10"
793+
The `out::IO` argument requires version 1.10.
794+
765795
# Examples
766796
```jldoctest
767797
julia> replace("Python is a programming language.", "Python" => "Julia")
@@ -780,8 +810,18 @@ julia> replace("abcabc", "a" => "b", "b" => "c", r".+" => "a")
780810
"bca"
781811
```
782812
"""
813+
replace(out::IO, s::AbstractString, pat_f::Pair...; count=typemax(Int)) =
814+
_replace_io(out, String(s), pat_f..., count=count)
815+
783816
replace(s::AbstractString, pat_f::Pair...; count=typemax(Int)) =
784-
replace(String(s), pat_f..., count=count)
817+
_replace_str(String(s), pat_f..., count=count)
818+
819+
# no copy needed for SubString{String}
820+
replace(out::IO, s::SubString{String}, pat_f::Pair...; count=typemax(Int)) =
821+
_replace_io(out, s, pat_f..., count=count)
822+
replace(s::SubString{String}, pat_f::Pair...; count=typemax(Int)) =
823+
_replace_str(s, pat_f..., count=count)
824+
785825

786826
# TODO: allow transform as the first argument to replace?
787827

doc/src/base/strings.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Base.findlast(::AbstractChar, ::AbstractString)
5151
Base.findprev(::AbstractString, ::AbstractString, ::Integer)
5252
Base.occursin
5353
Base.reverse(::Union{String,SubString{String}})
54-
Base.replace(s::AbstractString, ::Pair...)
54+
Base.replace(::IO, s::AbstractString, ::Pair...)
5555
Base.eachsplit
5656
Base.split
5757
Base.rsplit

0 commit comments

Comments
 (0)