This works for your example (- but not checked for edge case), It uses the idea that dag's (directed acyclic graphs) have a natural ordering. This is the case for your example. An impossible scenario in statements would result in a cycle in the graph hence we can use that to throw an error.
This works if all your statements are strict
library(igraph)
f <- function(X) {
d <- do.call("rbind", strsplit(X , "<"))
g <- graph.data.frame(d)
if(is.dag(g))
V(g)$name[topological.sort(g)]
else
stop("Graph has cycles")
}
f(c("A<B", "C<B", "A<C"))
f(c("C<B", "A<C", "A<B"))
f(c("A<B", "B<C", "C<A"))
About the code:
# this splits the string into two columns
# interpreted as from -> to by graph
do.call("rbind", strsplit(X , "<"))
# generate graph from edges (d)
g <- graph.data.frame(d)
is.dag(g) # checks if the graph is acyclic
# if there are no cycles return the vertice names in topological order
V(g)$name[topological.sort(g)]
EDIT
To include the case where vertices are equivalent restricts the use of the above. But we can still use graphical method (although it is less natural and not the R way to do it) - by describing equal vertices with bidirectional edges. If model / statements are fully specified (which it should be),we have a complete graph, and we can use the fact that if two vertices are equal they should have the same set of vertices greater than them and the same less than them - otherwise throw an error.
So we use the ideas of parents
and children
and compare these between equivalent vertices (A < B <=> parent < child).
The first part of the function is for the cases where the statements are all strictly less or greater than. The second part compares the parent and children of nodes that have equivalence statement.
f <- function(X) {
l <- do.call("rbind", strsplit(X[grepl("<", X)] , "<"))
if(!any(grepl("==", X))) {
g <- graph.data.frame(l)
if(is.dag(g))
V(g)$name[topological.sort(g)]
else
stop("Impossible")
}
else {
e <- do.call("rbind", strsplit(X[grepl("==", X)] , "=="))
g <- graph.data.frame(rbind(l, e, e[,2:1]))
par <- function(g)
setNames(lapply(neighborhood(g, 1, mode="in"),
function(i) sort(V(g)$name[i])), V(g)$name)
ch <- function(g)
setNames(lapply(neighborhood(g, 1, mode="out"),
function(i) sort(V(g)$name[i])), V(g)$name)
pareq <- apply(e, 1,
function(i)
identical(par(g)[[i[1]]], par(g)[[i[2]]]))
cheq <- apply(e, 1,
function(i)
identical(ch(g)[[i[1]]], ch(g)[[i[2]]]))
if(all(pareq & cheq)) {
g <- graph.data.frame(rbind(l,e))
V(g)$name[topological.sort(g)]
}
else
stop("Impossible")
}
}
A couple of example
f(X = c("C<B", "A<C", "A<B"))
f(X = c("C==B", "C==A", "A<B"))
f(X = c("C==B", "C<A", "A<B"))
f(X = c("B==C", "C<A", "B<A"))
I have not checked this for all edge cases or larger graphs but it should give an idea to get you started (if you want to go this way)