diff --git a/src/Groups.jl b/src/Groups.jl index 0934ddb..baf308a 100644 --- a/src/Groups.jl +++ b/src/Groups.jl @@ -21,6 +21,7 @@ include("words.jl") include("hashing.jl") include("freereduce.jl") include("arithmetic.jl") +include("findreplace.jl") include("FreeGroup.jl") include("FPGroups.jl") @@ -70,189 +71,6 @@ end end end -############################################################################### -# -# Replacement of symbols / sub-words -# - -issubsymbol(s::GSymbol, t::GSymbol) = - s.id == t.id && (0 ≤ s.pow ≤ t.pow || 0 ≥ s.pow ≥ t.pow) - -function issubsymbol(s::FreeSymbol, w::GWord, sindex::Integer) - @boundscheck 1 ≤ sindex ≤ syllablelength(w) || throw(BoundsError(w, sindex)) - return issubsymbol(s, syllables(w)[sindex]) -end - -function issubword(z::GWord, w::GWord, sindex::Integer) - isempty(z) && return true - @boundscheck 1 ≤ sindex ≤ syllablelength(w) || throw(BoundsError(w, sindex)) - n = syllablelength(z) - n == 1 && return issubsymbol(first(syllables(z)), syllables(w)[sindex]) - - lastindex = sindex + n - 1 - lastindex > syllablelength(w) && return false - - issubsymbol(first(z), syllables(w)[sindex]) || return false - issubsymbol(syllables(z)[end], syllables(w)[lastindex]) || return false - for (zidx, widx) in zip(2:n-1, sindex+1:lastindex-1) - syllables(z)[zidx] == syllables(w)[widx] || return false - end - return true -end - -"""doc -Find the first syllable index k>=i such that Z < syllables(W)[k:k+syllablelength(Z)-1] -""" -function findnext(subword::GWord, word::GWord, start::Integer) - @boundscheck 1 ≤ start ≤ syllablelength(word) || throw(BoundsError(word, start)) - isempty(subword) && return start - stop = syllablelength(word) - syllablelength(subword) +1 - - for idx in start:1:stop - issubword(subword, word, idx) && return idx - end - return nothing -end - -function findnext(s::FreeSymbol, word::GWord, start::Integer) - @boundscheck 1 ≤ start ≤ syllablelength(word) || throw(BoundsError(word, start)) - isone(s) && return start - stop = syllablelength(word) - - for idx in start:1:stop - issubsymbol(s, word, idx) && return idx - end - return nothing -end - -function findprev(subword::GWord, word::GWord, start::Integer) - @boundscheck 1 ≤ start ≤ syllablelength(word) || throw(BoundsError(word, start)) - isempty(subword) && return start - stop = 1 - - for idx in start:-1:1 - issubword(subword, word, idx) && return idx - end - return nothing -end - -function findprev(s::FreeSymbol, word::GWord, start::Integer) - @boundscheck 1 ≤ start ≤ syllablelength(word) || throw(BoundsError(word, start)) - isone(s) && return start - stop = 1 - - for idx in start:-1:stop - issubsymbol(s, word, idx) && return idx - end - return nothing -end - -findfirst(subword::GWord, word::GWord) = findnext(subword, word, 1) -findlast(subword::GWord, word::GWord) = - findprev(subword, word, syllablelength(word)-syllablelength(subword)+1) - -function replace!(out::GW, W::GW, lhs_rhs::Pair{GS, T}; count::Integer=typemax(Int)) where - {GS<:GSymbol, T<:GWord, GW<:GWord} - (count == 0 || isempty(W)) && return W - count < 0 && throw(DomainError(count, "`count` must be non-negative.")) - - lhs, rhs = lhs_rhs - - sW = syllables(W) - sW_idx = 1 - r = something(findnext(lhs, W, sW_idx), 0) - - sout = syllables(out) - resize!(sout, 0) - sizehint!(sout, syllablelength(W)) - - c = 0 - - while !iszero(r) - append!(sout, view(sW, sW_idx:r-1)) - a, b = divrem(sW[r].pow, lhs.pow) - - if b != 0 - push!(sout, change_pow(sW[r], b)) - end - - append!(sout, repeat(syllables(rhs), a)) - - sW_idx = r+1 - sW_idx > syllablelength(W) && break - - r = something(findnext(lhs, W, sW_idx), 0) - c += 1 - c == count && break - end - append!(sout, sW[sW_idx:end]) - return freereduce!(out) -end - -function replace!(out::GW, W::GW, lhs_rhs::Pair{T, T}; count::Integer=typemax(Int)) where - {GW<:GWord, T <: GWord} - (count == 0 || isempty(W)) && return W - count < 0 && throw(DomainError(count, "`count` must be non-negative.")) - - lhs, rhs = lhs_rhs - lhs_slen = syllablelength(lhs) - lhs_slen == 1 && return replace!(out, W, first(syllables(lhs))=>rhs; count=count) - - sW = syllables(W) - sW_idx = 1 - r = something(findnext(lhs, W, sW_idx), 0) - - sout = syllables(out) - resize!(sout, 0) - sizehint!(sout, syllablelength(W)) - - c = 0 - - while !iszero(r) - append!(sout, view(sW, sW_idx:r-1)) - - exp = sW[r].pow - first(syllables(lhs)).pow - if exp != 0 - push!(sout, change_pow(sW[r], exp)) - end - - append!(sout, syllables(rhs)) - - exp = sW[r+lhs_slen-1].pow - last(syllables(lhs)).pow - if exp != 0 - push!(sout, change_pow(sW[r+lhs_slen-1], exp)) - end - - sW_idx = r+lhs_slen - sW_idx > syllablelength(W) && break - - r = something(findnext(lhs, W, sW_idx), 0) - c += 1 - c == count && break - end - - # copy the rest - append!(sout, sW[sW_idx:end]) - return freereduce!(out) -end - -function replace(W::GW, lhs_rhs::Pair{T, T}; count::Integer=typemax(Int)) where - {GW<:GWord, T <: GWord} - return replace!(one(W), W, lhs_rhs; count=count) -end - -function replace(W::GW, subst_dict::Dict{T,T}) where {GW<:GWord, T<:GWord} - out = W - for toreplace in reverse!(sort!(collect(keys(subst_dict)), by=length)) - replacement = subst_dict[toreplace] - if length(toreplace) > length(out) - continue - end - out = replace(out, toreplace=>replacement) - end - return out -end - ############################################################################### # # Misc diff --git a/src/findreplace.jl b/src/findreplace.jl new file mode 100644 index 0000000..50fc358 --- /dev/null +++ b/src/findreplace.jl @@ -0,0 +1,182 @@ +############################################################################### +# +# Replacement of symbols / sub-words +# + +issubsymbol(s::GSymbol, t::GSymbol) = + s.id == t.id && (0 ≤ s.pow ≤ t.pow || 0 ≥ s.pow ≥ t.pow) + +function issubsymbol(s::FreeSymbol, w::GWord, sindex::Integer) + @boundscheck 1 ≤ sindex ≤ syllablelength(w) || throw(BoundsError(w, sindex)) + return issubsymbol(s, syllables(w)[sindex]) +end + +function issubword(z::GWord, w::GWord, sindex::Integer) + isempty(z) && return true + @boundscheck 1 ≤ sindex ≤ syllablelength(w) || throw(BoundsError(w, sindex)) + n = syllablelength(z) + n == 1 && return issubsymbol(first(syllables(z)), syllables(w)[sindex]) + + lastindex = sindex + n - 1 + lastindex > syllablelength(w) && return false + + issubsymbol(first(z), syllables(w)[sindex]) || return false + issubsymbol(syllables(z)[end], syllables(w)[lastindex]) || return false + for (zidx, widx) in zip(2:n-1, sindex+1:lastindex-1) + syllables(z)[zidx] == syllables(w)[widx] || return false + end + return true +end + +"""doc +Find the first syllable index k>=i such that Z < syllables(W)[k:k+syllablelength(Z)-1] +""" +function findnext(subword::GWord, word::GWord, start::Integer) + @boundscheck 1 ≤ start ≤ syllablelength(word) || throw(BoundsError(word, start)) + isempty(subword) && return start + stop = syllablelength(word) - syllablelength(subword) +1 + + for idx in start:1:stop + issubword(subword, word, idx) && return idx + end + return nothing +end + +function findnext(s::FreeSymbol, word::GWord, start::Integer) + @boundscheck 1 ≤ start ≤ syllablelength(word) || throw(BoundsError(word, start)) + isone(s) && return start + stop = syllablelength(word) + + for idx in start:1:stop + issubsymbol(s, word, idx) && return idx + end + return nothing +end + +function findprev(subword::GWord, word::GWord, start::Integer) + @boundscheck 1 ≤ start ≤ syllablelength(word) || throw(BoundsError(word, start)) + isempty(subword) && return start + stop = 1 + + for idx in start:-1:1 + issubword(subword, word, idx) && return idx + end + return nothing +end + +function findprev(s::FreeSymbol, word::GWord, start::Integer) + @boundscheck 1 ≤ start ≤ syllablelength(word) || throw(BoundsError(word, start)) + isone(s) && return start + stop = 1 + + for idx in start:-1:stop + issubsymbol(s, word, idx) && return idx + end + return nothing +end + +findfirst(subword::GWord, word::GWord) = findnext(subword, word, 1) +findlast(subword::GWord, word::GWord) = + findprev(subword, word, syllablelength(word)-syllablelength(subword)+1) + +function replace!(out::GW, W::GW, lhs_rhs::Pair{GS, T}; count::Integer=typemax(Int)) where + {GS<:GSymbol, T<:GWord, GW<:GWord} + (count == 0 || isempty(W)) && return W + count < 0 && throw(DomainError(count, "`count` must be non-negative.")) + + lhs, rhs = lhs_rhs + + sW = syllables(W) + sW_idx = 1 + r = something(findnext(lhs, W, sW_idx), 0) + + sout = syllables(out) + resize!(sout, 0) + sizehint!(sout, syllablelength(W)) + + c = 0 + + while !iszero(r) + append!(sout, view(sW, sW_idx:r-1)) + a, b = divrem(sW[r].pow, lhs.pow) + + if b != 0 + push!(sout, change_pow(sW[r], b)) + end + + append!(sout, repeat(syllables(rhs), a)) + + sW_idx = r+1 + sW_idx > syllablelength(W) && break + + r = something(findnext(lhs, W, sW_idx), 0) + c += 1 + c == count && break + end + append!(sout, sW[sW_idx:end]) + return freereduce!(out) +end + +function replace!(out::GW, W::GW, lhs_rhs::Pair{T, T}; count::Integer=typemax(Int)) where + {GW<:GWord, T <: GWord} + (count == 0 || isempty(W)) && return W + count < 0 && throw(DomainError(count, "`count` must be non-negative.")) + + lhs, rhs = lhs_rhs + lhs_slen = syllablelength(lhs) + lhs_slen == 1 && return replace!(out, W, first(syllables(lhs))=>rhs; count=count) + + sW = syllables(W) + sW_idx = 1 + r = something(findnext(lhs, W, sW_idx), 0) + + sout = syllables(out) + resize!(sout, 0) + sizehint!(sout, syllablelength(W)) + + c = 0 + + while !iszero(r) + append!(sout, view(sW, sW_idx:r-1)) + + exp = sW[r].pow - first(syllables(lhs)).pow + if exp != 0 + push!(sout, change_pow(sW[r], exp)) + end + + append!(sout, syllables(rhs)) + + exp = sW[r+lhs_slen-1].pow - last(syllables(lhs)).pow + if exp != 0 + push!(sout, change_pow(sW[r+lhs_slen-1], exp)) + end + + sW_idx = r+lhs_slen + sW_idx > syllablelength(W) && break + + r = something(findnext(lhs, W, sW_idx), 0) + c += 1 + c == count && break + end + + # copy the rest + append!(sout, sW[sW_idx:end]) + return freereduce!(out) +end + +function replace(W::GW, lhs_rhs::Pair{T, T}; count::Integer=typemax(Int)) where + {GW<:GWord, T <: GWord} + return replace!(one(W), W, lhs_rhs; count=count) +end + +function replace(W::GW, subst_dict::Dict{T,T}) where {GW<:GWord, T<:GWord} + out = W + for toreplace in reverse!(sort!(collect(keys(subst_dict)), by=length)) + replacement = subst_dict[toreplace] + if length(toreplace) > length(out) + continue + end + out = replace(out, toreplace=>replacement) + end + return out +end