Re-sort string based on precedence of separator
Question:
I have a string with a certain meaning for example "a,b;X1"
or e&r1
. In total there are 3 possible separators between values: ,;&
where ;
has low precedence.
Also "a,b;X1"
and "b,a;X1"
are the same but to be able to compare they are the same, I want to predictably resort the string so that indeed the 2 can be compared to be equal. In essence "b,a;X1"
must be "sorted" to become "a,b;X1"
and this is rather a simple example. The expression can be more complex.
The precedence is of importance as "a,b;X1"
is not the same as "a;b,X1"
.
In general I would need to split into "groups by precedence and then sort the groups and merge things together again but unclear how to achieve this.
So far I have:
example = "b,a;X1"
ls = example.split(';')
ls2 = [x.split(",") for x in ls]
ls3 = [[y.split("&") for y in x] for x in ls2]
ls3.sort()
print(ls3)
# [[['X1']], [['b'], ['a']]]
Sorting doesn’t yet work as a should be before b and then I’m not sure how to "stitch" the result back together again.
For clarification:
,
means OR
&
means AND (high precedence)
;
means AND (low precedence)
"a,b;X1"
therefore means (a OR b) AND X1
"b,a;X1"
therefore means (b OR a) AND X1 i.e. the same
Answers:
I would suggest writing a function which recursively sorts each list. Here’s an example of how to do that:
delim_precedence = (';', ',', '&')
def recursive_split(s, delim):
if isinstance(s, str):
return s.split(delim)
elif isinstance(s, list):
return [recursive_split(i, delim) for i in s]
else:
raise Exception("unknown type")
def split_by_precedence(s):
for delim in delim_precedence:
s = recursive_split(s, delim)
return s
def recursive_sort(s):
if isinstance(s, str):
return s
elif isinstance(s, list):
return sorted([recursive_sort(i) for i in s])
else:
raise Exception("unknown type")
def rejoin(s, delims=delim_precedence):
if len(delims) == 0:
return s
return delims[0].join(rejoin(i, delims[1:]) for i in s)
def canonicalize(s):
return rejoin(recursive_sort(split_by_precedence(s)))
print(canonicalize(example))
You could use split
, sort
as you did (combined with join
) , but it should happen at every level of operator:
def normalize(s):
return ";".join(sorted(
",".join(sorted(
"&".join(sorted(factor.split("&")))
for factor in term.split(",")
))
for term in s.split(";")
))
example = "b,a&z&x;x1;m&f,q&c"
print(normalize(example)) # a&x&z,b;c&q,f&m;x1
@trincot’s answer works but is a maintenance nightmare since it hard-codes the separators for nested splits and joins, so if there is a need for a change of a separator, both the corresponding split and join need to be modified, and if there is a need for an additional separator, an additional layer of split and join needs to be nested. .
A more general approach would be to use a recursive function to split by and join with one separator at a time, and pass the rest of the separators to the next level of recursive call:
def sort(string, separators):
sep, *rest = separators
pieces = string.split(sep)
return sep.join(sorted((sort(p, rest) for p in pieces) if rest else pieces))
ieces))
so that (using @trincot’s test case):
example = "b,a&z&x;x1;m&f,q&c"
separators = ';,&'
print(sort(example, separators))
would output:
a&x&z,b;c&q,f&m;x1
I have a string with a certain meaning for example "a,b;X1"
or e&r1
. In total there are 3 possible separators between values: ,;&
where ;
has low precedence.
Also "a,b;X1"
and "b,a;X1"
are the same but to be able to compare they are the same, I want to predictably resort the string so that indeed the 2 can be compared to be equal. In essence "b,a;X1"
must be "sorted" to become "a,b;X1"
and this is rather a simple example. The expression can be more complex.
The precedence is of importance as "a,b;X1"
is not the same as "a;b,X1"
.
In general I would need to split into "groups by precedence and then sort the groups and merge things together again but unclear how to achieve this.
So far I have:
example = "b,a;X1"
ls = example.split(';')
ls2 = [x.split(",") for x in ls]
ls3 = [[y.split("&") for y in x] for x in ls2]
ls3.sort()
print(ls3)
# [[['X1']], [['b'], ['a']]]
Sorting doesn’t yet work as a should be before b and then I’m not sure how to "stitch" the result back together again.
For clarification:
,
means OR&
means AND (high precedence);
means AND (low precedence)
"a,b;X1"
therefore means (a OR b) AND X1
"b,a;X1"
therefore means (b OR a) AND X1 i.e. the same
I would suggest writing a function which recursively sorts each list. Here’s an example of how to do that:
delim_precedence = (';', ',', '&')
def recursive_split(s, delim):
if isinstance(s, str):
return s.split(delim)
elif isinstance(s, list):
return [recursive_split(i, delim) for i in s]
else:
raise Exception("unknown type")
def split_by_precedence(s):
for delim in delim_precedence:
s = recursive_split(s, delim)
return s
def recursive_sort(s):
if isinstance(s, str):
return s
elif isinstance(s, list):
return sorted([recursive_sort(i) for i in s])
else:
raise Exception("unknown type")
def rejoin(s, delims=delim_precedence):
if len(delims) == 0:
return s
return delims[0].join(rejoin(i, delims[1:]) for i in s)
def canonicalize(s):
return rejoin(recursive_sort(split_by_precedence(s)))
print(canonicalize(example))
You could use split
, sort
as you did (combined with join
) , but it should happen at every level of operator:
def normalize(s):
return ";".join(sorted(
",".join(sorted(
"&".join(sorted(factor.split("&")))
for factor in term.split(",")
))
for term in s.split(";")
))
example = "b,a&z&x;x1;m&f,q&c"
print(normalize(example)) # a&x&z,b;c&q,f&m;x1
@trincot’s answer works but is a maintenance nightmare since it hard-codes the separators for nested splits and joins, so if there is a need for a change of a separator, both the corresponding split and join need to be modified, and if there is a need for an additional separator, an additional layer of split and join needs to be nested. .
A more general approach would be to use a recursive function to split by and join with one separator at a time, and pass the rest of the separators to the next level of recursive call:
def sort(string, separators):
sep, *rest = separators
pieces = string.split(sep)
return sep.join(sorted((sort(p, rest) for p in pieces) if rest else pieces))
ieces))
so that (using @trincot’s test case):
example = "b,a&z&x;x1;m&f,q&c"
separators = ';,&'
print(sort(example, separators))
would output:
a&x&z,b;c&q,f&m;x1