| Bytes | Lang | Time | Link |
|---|---|---|---|
| 042 | Haskell | 250212T214010Z | xnor |
| 053 | JavaScript Node.js | 250209T234015Z | emanresu |
| 017 | Charcoal | 250209T215657Z | Neil |
| 008 | Jelly | 250210T205300Z | Unrelate |
| 047 | R | 250210T120707Z | pajonk |
| 082 | Maple | 250209T235801Z | dharr |
| 037 | APLNARS | 250209T190525Z | Rosario |
| 014 | Japt | 250210T102734Z | Shaggy |
| 010 | 05AB1E | 250210T084252Z | Kevin Cr |
| 082 | Google Sheets | 250209T175106Z | doubleun |
| 010 | Jelly | 250209T162908Z | Jonathan |
| 056 | Python 3 | 250209T090856Z | xnor |
| 023 | Retina 0.8.2 | 250209T080720Z | Neil |
| 059 | JavaScript Node.js | 250209T023102Z | l4m2 |
Haskell, 42 bytes
(.g).(==).g
g s=elem 'P's<$s++['T'|'T'<-s]
The top line is point-free for \a b->g a==g b. The "fingerprint" function g appends to the input s an additional character for each T in s, yielding a length of one per P and two per T. The entries of this list/string are all replaced via <$ with a Boolean of whether the list has any P's, to tell apart all-T lists.
The [?|'T'<-s] can use any character for the ?, since they'll be replaced by the Boolean anyway, but Haskell's type-checking prevents something shorter like a number or the variable s.
JavaScript (Node.js), 55 53 bytes
x=>y=>g(x)==g(y)
g=x=>x.split`T`.join`PP`+/P/.test(x)
Uses xnor's P fingerprint idea, and I borrowed l4m2's test harness. -2 bytes thanks to Arnauld.
As per xnor's answer, we replace T with PP, but need some way to check whether there was a catalyst P in the first place - this is done via appending /P/.test(x), which is coerced to either "true" or "false".
Charcoal, 21 17 bytes
⁼⭆⁺⁻θPθ⌊θ⭆⁺⁻ηPη⌊η
Try it online! Link is to verbose version of code. Outputs a Charcoal boolean, i.e. - for equal, nothing if not. Explanation: Now a port of @UnrelatedString's approach; strings containing only Ts are doubled in length but strings that (also) contain Ps have any Ts doubled and changed into Ps.
θ First input
⁻ Remove occurrences of
P Literal string `P`
⁺ Concatenated with
θ First input
⭆ Replace all characters with
θ First input
⌊ Minimum (`P` if there is one, else `T`)
⁼ Equals
⭆⁺⁻ηPη⌊η As above but using the second input
Implicitly print
Note that the Minimum is evaluated inside the loop, thus it will never be called for empty strings.
Jelly, 8 bytes
f”T;aṂ)E
Wanted to beat this with something using Ġ, but the best I could get was the fairly silly ĠẈUḄ×Ṃ)E which ties.
)E Are the elements of the input equal under?:
f”T Filter to only "T"s
; and concatenate to unfiltered.
aṂ Replace all elements with the minimum. ("P" if it exists)
R, 49 47 bytes
\(x)all(table(gsub("T","PP",x),grepl("P",x))>1)
Port of @xnor's fingerprint solution.
Maple, 82 bytes
(s,t)->evalb(`=`((p:=(c:=x->numboccur~([s,t],x)[])(P))+`if`(`*`(p)=0,-1,2)*c(T)));
Input is two lists containing Ps and Ts. The TP->PPP rule means that, provided we have at least one P, we just need to check if the number of Ps plus twice the number of Ts is the same for each list.
Function c(X) counts the number of Xs (X=P or X=T) in each list and outputs a sequence like (2,0). If c(P) has a zero (detected by the product being zero), we need to check that c(P) - c(T) has the same components, otherwise we need to check c(P) + 2*c(T) has the same two components. Actually, we define a multiplier m=-1 if c(P) has a zero and m=2 otherwise, and then check if the two components of c(P)+m*c(T) are the same.
APL(NARS), 37 chars
{≡/{'P'∊k←⍵:∊k⊣k[⍸⍵='T']←⊂'PP'⋄k}¨⍺⍵}
the nested function, if P is element of the string, replace each 'T' char with 2 chars 'PP' and return the string, else return the string of input unchanged.
It would return 1 for true 0 for false. Test:
f←{≡/{'P'∊k←⍵:∊k⊣k[⍸⍵='T']←⊂'PP'⋄k}¨⍺⍵}
''f''
1
''f'P'
0
''f'T'
0
'TT'f'TT'
1
'PTPTPPP'f'PPPPPTPP'
1
05AB1E, 10 bytes
€{„PT¬3×:Ë
Input as a pair of strings.
Try it online or verify all test cases.
Explanation:
€ # Map over both strings in the (implicit) input-pair:
{ # Sort its characters
„PT # Push string "PT"
¬ # Push its first character (without popping): "P"
3× # Repeat it three times: "PPP"
: # Replace all "PT" for "PPP" until no "PT" are left
Ë # Check if both strings in the pair are the same
# (which is output implicitly as result)
Google Sheets, 82 bytes
=let(f,lambda(s,substitute(regexreplace(s,"^(T*)T$","$1_"),"T","PP")),f(A1)=f(B1))
Uses xnor's method.

Jelly, 10 bytes
9 if we may accept lists of code points (remove O); 7 if we may accept lists of "is T"s (remove Oọ3).
Oọ3~Ạ¡€‘§E
A monadic Link that accepts a pair of lists of PT characters and yields 1 if equivalent or 0 if not.
How
Oọ3~Ạ¡€‘§E - Link: list of lists of characters, [A, B]
O - ordinals (P -> 80; T -> 84)
ọ3 - multiplicity of three (P -> 0; T -> 1)
€ - for each {list, L, of 0s and 1s}:
¡ - repeat...
Ạ - ...times: all? (i.e. Once iff only Ts existed)
~ - ...action: bitwise NOT (replaces all the values (1s) with -2s)
‘ - increment all these 0s, 1s and -2s (P -> 1; T -> 2 or -1 if all were T)
§ - sums
E - equal?
Python 3, 56 bytes
lambda*l:len({s.replace('T','PP',len(s)-1)for s in l})<2
Replaces every T in each string with PP before checking for equality. But, to tell apart strings of only T with no "catalyst" P, the number of replacements is capped at len(s)-1, which leaves one T unconverted in such strings.
57 bytes
lambda*l:len({(s.replace('T','PP'),'P'in s)for s in l})<2
Here the fingerprinting function is (s.replace('T','PP'),'P'in s), which can be more easily with a helper function g (62 bytes)
lambda a,b:g(a)==g(b)
g=lambda s:(s.replace('T','PP'),'P'in s)
We effectively get the number of P's plus double the number of T's, but to separate out the all-T's case, we also record a bit for whether the string has any P's.
55 bytes
lambda*l:len({s.replace('T',min(s+'~')*2)for s in l})<2
If not for empty inputs, we could get rid if +'~'
Retina 0.8.2, 23 bytes
%O`.
+`PT
PPP
^(.*)¶\1$
Try it online! Takes input on separate lines but link is to test suite that splits on commas for convenience. Explanation:
%O`.
Sort each input separately.
+`PT
PPP
Convert Dyck's surface to a chosen "canonical" representation.
^(.*)¶\1$
Compare the two results.
JavaScript (Node.js), 59 bytes
x=>y=>g(x)==g(y)
g=x=>x.replace(/T(?=.*P)|T(?<=P.*)/g,'PP')
if backtrack (?<=P.*) unavailable then
JavaScript (Node.js), 60 bytes
x=>y=>g(x)==g(y)
g=x=>x==(x=x.replace(/TP|PT/,'PPP'))?x:g(x)