Replacing multiple strings errors

Am I having a stroke or something? Is this not supposed to work (version 1.0):

julia> replace("ABC", "A"=>"a", "B"=>"b")
ERROR: MethodError: no method matching replace(::String, ::Pair{String,String}, ::Pair{String,String})
Closest candidates are:
  replace(::AbstractString, ::Pair, ::Pair) at set.jl:487
  replace(::Any, ::Pair...; count) at set.jl:428
  replace(::Union{Function, Type}, ::Pair, ::Pair; count) at set.jl:486
  ...
Stacktrace:
 [1] replace(::String, ::Pair{String,String}, ::Pair{String,String}) at ./set.jl:487
 [2] top-level scope at none:0

It works for vectors:

julia> replace([1,2,3], 2=>20, 3=>30)
3-element Array{Int64,1}:
  1
 20
 30

This is the code being called, which explicitly throws an error for this case (set.jl:487). But why?

replace(a::AbstractString, b::Pair, c::Pair) = throw(MethodError(replace, (a, b, c)))

Adding more replacement pairs also fails, but in a different way:

julia> replace("ABC", "A"=>"a", "B"=>"b", "C"=>"c")
ERROR: MethodError: no method matching similar(::String, ::Type{Any})
Closest candidates are:
  similar(::Array{T,1}, ::Type) where T at array.jl:329
  similar(::Array{T,2}, ::Type) where T at array.jl:330
  similar(::Array, ::Type, ::Tuple{Vararg{Int64,N}}) where N at array.jl:332
  ...
Stacktrace:
 [1] _similar_or_copy(::String, ::Type{Any}) at ./set.jl:321
 [2] #replace#232(::Nothing, ::Function, ::String, ::Pair{String,String}, ::Vararg{Pair{String,String},N} where N) at ./set.jl:431
 [3] replace(::String, ::Pair{String,String}, ::Pair{String,String}, ::Pair{String,String}) at ./set.jl:428
 [4] top-level scope at none:0

while this is no problem:

julia> replace([1,2,3], 1=>10, 2=>20, 3=>30)
3-element Array{Int64,1}:
 10
 20
 30

You can always do:

join(replace(split("ABC", ""), "A"=>"a", "B"=>"b"))

Strings being immutable might be the problem…?

I guess the design of replace for strings is to allow only one pattern because patterns in a string can overlap so it would be ambiguous in what sequence to apply them.
But I have not implemented it so it is just a guess.

If you want to apply patterns sequentially you can do:

reduce(replace, ["A"=>"a", "B"=>"b", "C"=>"c"], init="ABC")

EDIT. Just to clarify what I mean by this ambiguity. The following codes do not produce identical results:

julia> reduce(replace, [1=>10, 10=>20], init=[1,2,3])
3-element Array{Int64,1}:
 20
  2
  3

julia> replace([1,2,3], 1=>10, 10=>20)
3-element Array{Int64,1}:
 10
  2
  3

because in replace([1,2,3], 1=>10, 10=>20) the patterns are not applied sequentially.

8 Likes

I guess the error message could clarify this, then, because it seemed to me like something that should obviously work. Especially this:

ERROR: MethodError: no method matching replace(::String, ::Pair{String,String}, ::Pair{String,String})
Closest candidates are:
  replace(::AbstractString, ::Pair, ::Pair) at set.jl:487

made me think it was a bug.

I still don’t get what is ambiguous. Would it not just be understood as

  • Iterate through string
  • if original char == “A”: replace with “a”
  • elseif original char == “B”: replace with “b”

So that replace("ABC", "A"=>"B", "B"=>"A") would return "BAC".

Yes, I would expect the second pattern. That would even be consistent with your example here. Why would this not be the same for strings?

Edit: I mean, the behaviour for strings should be the same as for vectors.

I guess it’s not ambiguous for single characters, but might be for longer string replacements:

 replace("ABC", "AB"=>"1", "BC"=>"2")

This is exactly what I mean.

In general I guess you could use the rule:

always use the leftmost pattern that matches the earliest in the string and move forward in the source string as much as the pattern eats up and repeat the process.

which would be unambiguous but probably not easy to digest for a user (this problem is not present with arrays as you always substitute one element for one element).

And I agree that replace(::AbstractString, ::Pair{<:AbstractChar}...) can be safely defined and should be 100% clear. Currently we have only replace(::AbstractString, ::Pair{<:AbstractChar}).

I guess you could open an issue or PR to Julia for this clarification.

1 Like

I will at least open an issue regarding the documentation and error message. I still need to read up on how to contribute PRs.

An issue is already opened, see https://github.com/JuliaLang/julia/issues/25383.
The primary reason why this doesn’t work is that it’s simply as yet unimplemented (multiple replacement in other collections with the pair API was implemented not so long ago), and IIRC the issue is that it’s not trivial to implement, cf. this open PR: https://github.com/JuliaLang/julia/pull/25732 (and https://github.com/JuliaLang/julia/pull/25396).

1 Like

Maybe you can use like this way
replace(replace(“ABC”,“A”=>“a”),“B”=>“b”)

Welcome to the forum!

This is largely the same as the reduce solution suggested above. (Which by the way should probably not use reduce but foldl.)

Your suggestion is likely slightly more performant than foldl, but using foldl is more flexible since you can pass in an array with any number of replacement strings.

By the way, you should quote your code using triple backticks ``` for better readability and to prevent things like “smart quotes” that prevents your code from working as is.

1 Like