How to order vectors with priority layout?

Viewed 84

Let's consider these vector of strings following:

x <- c("B", "C_small", "A", "B_big", "C", "A_huge", "D", "A_big", "B_tremendous")

As you can see there are certain strings in this vector starting the same e.g. "B", "B_big".

What I want to end up with is a vector ordered in such layout that all strings with same starting should be next to each other. But order of letter should stay the same (that "B" should be first one, "C" second one and so on). Let me put an example to clarify it:

In simple words, I want to end up with vector:

"B", "B_big", "B_tremendous", "C_small", "C", "A", "A_huge", "A_big", "D"

What I've done to achive this vector: I read from the left and I see "B" so I'm looking on all other vector which starts the same and put it to the right of "B". Then is "C", so I'm looking on all remaining strings and put all starting with "C" e.g. "C_small" to the right and so on.

I'm not sure how to do it. I'm almost sure that gsub function can be used to approach this result, however I'm not sure how to combine it with this searching and replacing. Could you please give me a hand doing so ?

3 Answers

Here's one option:

x <- c("B", "C_small", "A", "B_big", "C", "A_huge", "D", "A_big", "B_tremendous")

xorder <- unique(substr(x, 1, 1))
xnew <- c()

for (letter in xorder) {
  if (letter %in% substr(x, 1, 1)) {
    xnew <- c(xnew, x[substr(x, 1, 1) == letter])
  }
}

xnew

[1] "B"            "B_big"        "B_tremendous" "C_small"      "C"           
[6] "A"            "A_huge"       "A_big"        "D"   

Use the "prefix" as factor levels and then order:

sx = substr(x, 1, 1)
x[order(factor(sx, levels = unique(sx)))]
# [1] "B"   "B_big"   "B_tremendous"  "C_small"   "C"   "A"   "A_huge"   "A_big"   "D"

If you are open for non-base alternatives, data.table::chgroup may be used, "groups together duplicated values but retains the group order (according the first appearance order of each group), efficiently":

x[chgroup(substr(x, 1, 1))] 
# [1] "B"   "B_big"   "B_tremendous"  "C_small"   "C"   "A"   "A_huge"   "A_big"   "D"

I suggest splitting the two parts of the text into separate dimensions. Then, define a clear rank order for the descriptive part of the name using a named character vector. From there you can reorder the input vector on the fly. Bundled as a function:

x <- c("B", "C_small", "A", "B_big", "C", "A_huge", "D", "A_big", "B_tremendous")

sorter <- function(x) {
    # separate the two parts
    prefix <- sub("_.*$", "", x)
    suffix <- sub("^.*_", "", x)
    # identify inputs with no suffix
    suffix <- ifelse(suffix == "", "none", suffix)
    
    # map each suffix to a rank ordering 
    suffix_order <- c(
        "small"      = -1,
        "none"       =  0, 
        "big"        =  1,
        "huge"       =  2,
        "tremendous" =  3
    )
    
    # return input vector, 
    # ordered by the prefix and the mapping of suffix to rank
    x[order(prefix, suffix_order[suffix])]
    
}

sorter(x)

Result

[1] "A_big" "A_huge" "A"  "B_big" "B_tremendous" "B" "C_small" "C"           
[9] "D"   
Related