Skip to content

Commit 246f2d4

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

File tree

3 files changed

+58
-11
lines changed

3 files changed

+58
-11
lines changed

NEWS.md

+2
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

+55-10
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,12 @@ 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,
705+
patterns::NTuple{N}, replaces::NTuple{N}, rs::NTuple{N}) where N
705706
while true
706707
p = argmin(map(first, rs)) # TODO: or argmin(rs), to pick the shortest first match ?
707708
r = rs[p]
@@ -737,12 +738,38 @@ function replace(str::String, pat_repl::Vararg{Pair,N}; count::Integer=typemax(I
737738
end
738739
foreach(_free_pat_replacer, patterns)
739740
write(out, SubString(str, i))
740-
return String(take!(out))
741+
return out
741742
end
742743

744+
# note: leave str untyped here to make it easier for packages like StringViews to hook in
745+
function _replace_io(out::IO, retval, str, pat_repl::Pair...; count::Integer=typemax(Int))
746+
if count == 0
747+
write(out, str)
748+
return out
749+
end
750+
patterns, replaces, rs, notfound = _replace_init(str, pat_repl, count)
751+
if notfound
752+
foreach(_free_pat_replacer, patterns)
753+
write(out, str)
754+
return out
755+
end
756+
return _replace_finish(out, str, count, patterns, replaces, rs)
757+
end
758+
759+
# note: leave str untyped here to make it easier for packages like StringViews to hook in
760+
function _replace_str(str, pat_repl::Pair...; count::Integer=typemax(Int))
761+
count == 0 && return str
762+
patterns, replaces, rs, notfound = _replace_init(str, pat_repl, count)
763+
if notfound
764+
foreach(_free_pat_replacer, patterns)
765+
return str
766+
end
767+
out = IOBuffer(sizehint=floor(Int, 1.2sizeof(str)))
768+
return String(take!(_replace_finish(out, str, count, patterns, replaces, rs)))
769+
end
743770

744771
"""
745-
replace(s::AbstractString, pat=>r, [pat2=>r2, ...]; [count::Integer])
772+
replace([out::IO], s::AbstractString, pat=>r, [pat2=>r2, ...]; [count::Integer])
746773
747774
Search for the given pattern `pat` in `s`, and replace each occurrence with `r`.
748775
If `count` is provided, replace at most `count` occurrences.
@@ -755,13 +782,21 @@ If `pat` is a regular expression and `r` is a [`SubstitutionString`](@ref), then
755782
references in `r` are replaced with the corresponding matched text.
756783
To remove instances of `pat` from `string`, set `r` to the empty `String` (`""`).
757784
785+
The return value is a new string after the replacements. If the `out::IO` argument
786+
is supplied, the transformed string is instead written to `out` (returning `out`).
787+
(For example, this can be used in conjunction with an [`IOBuffer`](@ref) to re-use
788+
a pre-allocated buffer array in-place.)
789+
758790
Multiple patterns can be specified, and they will be applied left-to-right
759791
simultaneously, so only one pattern will be applied to any character, and the
760792
patterns will only be applied to the input text, not the replacements.
761793
762794
!!! compat "Julia 1.7"
763795
Support for multiple patterns requires version 1.7.
764796
797+
!!! compat "Julia 1.10"
798+
The `out::IO` argument requires version 1.10.
799+
765800
# Examples
766801
```jldoctest
767802
julia> replace("Python is a programming language.", "Python" => "Julia")
@@ -780,8 +815,18 @@ julia> replace("abcabc", "a" => "b", "b" => "c", r".+" => "a")
780815
"bca"
781816
```
782817
"""
818+
replace(out::IO, s::AbstractString, pat_f::Pair...; count=typemax(Int)) =
819+
_replace_io(out, String(s), pat_f..., count=count)
820+
783821
replace(s::AbstractString, pat_f::Pair...; count=typemax(Int)) =
784-
replace(String(s), pat_f..., count=count)
822+
_replace_str(String(s), pat_f..., count=count)
823+
824+
# no copy needed for SubString{String}
825+
replace(out::IO, s::SubString{String}, pat_f::Pair...; count=typemax(Int)) =
826+
_replace_io(out, s, pat_f..., count=count)
827+
replace(s::SubString{String}, pat_f::Pair...; count=typemax(Int)) =
828+
_replace_str(s, pat_f..., count=count)
829+
785830

786831
# TODO: allow transform as the first argument to replace?
787832

doc/src/base/strings.md

+1-1
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)