Skip to contents

cjk_sort() returns x ordered by an ICU collation; cjk_order() returns the permutation that would do it, for reordering a data frame.

Usage

cjk_sort(x, locale = NULL, decreasing = FALSE)

cjk_order(x, locale = NULL, decreasing = FALSE)

Arguments

x

A character vector. Anything else is coerced with as.character(). That coercion is R's, not this package's, so a numeric vector is measured as R chooses to write it – which moves with options(scipen) and options(OutDec), and can therefore differ between sessions. Convert deliberately if you mean to measure numbers; these verbs are for text. A list is not coerced – as.character() deparses one rather than coercing it, so the text measured would be the R code that builds the list – so a list, a data frame or a function is an error naming what to do instead.

locale

ICU locale naming the collation, e.g. "zh". NULL, the default, uses the session default – which is unlikely to order Han usefully, so name one.

decreasing

Reverse the order.

Value

cjk_sort() a character vector the same length as x; cjk_order() an integer vector of indices.

Details

sort() reads LC_COLLATE, so the order it gives Han text is a property of the session rather than of the data. Under C or en_US.UTF-8 it falls back to code point order; under zh_CN.utf8 glibc supplies a pinyin collation and the answer changes; under ja_JP.utf8 it changes again. None of those is wrong in itself, and that is the difficulty: the same script sorts a name column differently on two machines and says nothing.

The fallback order is worth knowing, because it is not arbitrary. The unified ideographs are laid out in KangXi radical-stroke order, so within the base block sorting by code point sorts by radical. What it is not is phonetic, which is what someone sorting names is after. It also stops being radical order across blocks: every Extension A character (U+3400-U+4DBF) sorts ahead of every base-block one, so a rare character lands nowhere near the common characters sharing its radical.

ICU carries real collations. With locale = "zh" Han sorts by pinyin, so a column of Chinese surnames comes out in the order a Chinese reader expects.

Choosing the order

The collation is selected by the locale string, including the BCP 47 variants:

  • "zh" – Mandarin pinyin.

  • "zh-u-co-stroke" – stroke count, the convention in many indexes.

  • "zh-u-co-zhuyin" – bopomofo order.

  • "ja" and "ko" – Japanese and Korean.

A locale whose language ICU has no collation for is an error here rather than the silent fallback to root collation stringi would otherwise give you, because that fallback produces a plausible-looking wrong order. An unrecognised region is not an error: "zh-CH", a typo for "zh-CN", resolves to "zh" and still sorts by pinyin, which is the right answer. The language is matched without regard to case, as BCP 47 specifies, so "ZH" is "zh"; and "root" asks for ICU's root collation by name, which is a choice rather than a fallback.

Missing values

NA sorts last and is kept, so the result is always the same length as x. stringi::stri_sort() drops missing values by default, which would silently shorten a column.

See also

cjk_romanize(), whose ascii = TRUE output gives a sort key you can use where no ICU locale is available.

Examples

# Chinese surnames: Zhang, Wang, Li
x <- c("\u5f35", "\u738b", "\u674e")

sort(x)                        # depends on LC_COLLATE
#> [1] "張" "李" "王"
cjk_sort(x, locale = "zh")     # pinyin: Li, Wang, Zhang -- on any machine
#> [1] "李" "王" "張"

# stroke order instead
cjk_sort(x, locale = "zh-u-co-stroke")
#> [1] "王" "李" "張"

# reorder a data frame
df <- data.frame(name = x)
df[cjk_order(x, locale = "zh"), , drop = FALSE]
#>   name
#> 3   李
#> 2   王
#> 1   張