px普贤教程PuXian
普贤教程 › 生态包

文本与格式化实战

这 12 个包补的是「写业务逻辑时天天要用、但语言本体没给」的那一层:数组操作、类型判断、格式化、相似度、进制、语音编码、颜色。全部纯函数,双模式(解释轨 / 编译轨)输出一致。

一个脚本跑完 12 个包

px
import arrutil
import compact
import ctype
import multisort
import numconv
import phonetic
import simtext
import sprintf
import strpad
import textfmt
import vardump
import colorutil

def main():
    print(json_stringify(arr_flip({"a": 1, "b": 2})))
    print(json_stringify(arr_column([{"id": 1, "name": "甲"}, {"id": 2, "name": "乙"}], "name")))
    print(json_stringify(arr_chunk_fixed([1, 2, 3, 4, 5], 2)))
    print(json_stringify(arr_unique_stable([3, 1, 3, 2, 1])))

    print(json_stringify(cp_compact(["host", "port"], ["127.0.0.1", 8080])))
    print(json_stringify(cp_only({"a": 1, "b": 2, "c": 3}, ["a", "c"])))
    print(json_stringify(cp_pluck([{"id": 1}, {"id": 2}], "id")))

    print(str(ct_digit("12345")) + " " + str(ct_digit("12a")) + " " + str(ct_alpha("abc")) + " " + str(ct_xdigit("ff00")))

    var rows = [{"n": "乙", "age": 30}, {"n": "甲", "age": 25}, {"n": "丙", "age": 30}]
    print(json_stringify(ms_sort_by(rows, ["age"], ["desc"])))

    print(nc_dec2hex(255) + " " + nc_dec2bin(10) + " " + nc_dec2roman(1987) + " " + str(nc_base_convert("ff", 16, 36)))

    print(ph_soundex("Robert") + " " + ph_soundex("Rupert") + " " + str(ph_match("Smith", "Smyth")))

    print(str(st_similar("kitten", "sitting")) + "% " + str(st_lcs("ABCBDAB", "BDCABA")) + " " + st_lcs_str("kitten", "sitting"))

    print(sf_format("%05.2f|%+d|%x|%-6s|", [3.14159, 42, 255, "ab"]))

    print("[" + sp_pad("7", 3, "0", "left") + "] [" + sp_pad_both("mid", 9, "-") + "] [" + sp_truncate("一口气写完这篇文档", 8) + "]")

    print(tf_nl2br("line1\nline2"))
    print(tf_strtr_table("Hello {name}", {"{name}": "世界"}))

    print(vd_dump({"ok": true, "n": 3}))

    print(json_stringify(co_hex_to_rgb("#FF8800")))
    print(co_lighten("#3366cc", 0.2) + " " + co_complement("#3366cc") + " " + str(co_contrast_ratio("#000000", "#ffffff")))
text
{"1":"a","2":"b"}
["甲","乙"]
[[1,2],[3,4],[5]]
[3,1,2]
{"host":"127.0.0.1","port":8080}
{"a":1,"c":3}
[1,2]
true false true true
[{"n":"乙","age":30},{"n":"丙","age":30},{"n":"甲","age":25}]
ff 1010 MCMLXXXVII 73
R163 R163 true
61.53846153846154% 4 ittn
03.14|+42|ff|ab    |
[007] [---mid---] [一口气写完...]
line1<br>
line2
Hello 世界
array(2) {
  ["ok"]=>
    bool(true)
  ["n"]=>
    int(3)
}
[255,136,0]
#84A3E0 #CC9932 21.0

arrutil:19 个数组函数

arr_flip / arr_column / arr_chunk_fixed / arr_unique_stable / arr_zip / arr_flat / arr_rotate / arr_partition / arr_sliding … 名字基本能对上 PHP 的 array_*。

键一律变成字符串arr_flip({"a":1}) 得到的是 {"1":"a"},不是 {1:"a"}。普贤的 dict 只接受字符串键,所以「翻转后按数字键取值」要写 d[str(1)]。arr_unique_stable 保序([3,1,3,2,1] → [3,1,2]),需要排序去重就再接 sort。

compact:变量与字典互转

px
cp_compact(["host", "port"], ["127.0.0.1", 8080])   # {"host":"127.0.0.1","port":8080}
cp_only({"a":1,"b":2,"c":3}, ["a", "c"])            # {"a":1,"c":3}
cp_except({"a":1,"b":2}, ["b"])                     # {"a":1}
cp_pluck([{"id":1},{"id":2}], "id")                 # [1,2]
cp_flip({"a":1})                                    # {"1":"a"}

这是从 PHP 迁过来最省事的一组:compact('host','port') 在普贤里没有对应语法(局部变量不可枚举),所以显式传名字列表。

ctype:整串判断 + 单字符判断

两套接口:ct_digit(s) 判断整串是否全数字,ct_is_digit(c) 判断单个字符。空串一律返回 false(PHP 的 ctype_digit("") 也是 false,口径一致)。

别用它校验 UTF-8 文本ct_alpha("中文") 是 false——这套函数按字节判断 ASCII。中文合法性请用 validator 包的字符类规则。

multisort:多键联动排序

px
var rows = [{"n":"乙","age":30}, {"n":"甲","age":25}, {"n":"丙","age":30}]
print(json_stringify(ms_sort_by(rows, ["age"], ["desc"])))
# 同值时保持原顺序(稳定排序)

两个入口:ms_sort(arrays, orders) 是 PHP array_multisort 的等价物(多个平行数组一起排);ms_sort_by(rows, keys, orders) 排 list[dict],日常用后者。

numconv:进制与罗马数字

px
nc_dec2hex(255)                    # ff
nc_dec2bin(10)                     # 1010
nc_dec2roman(1987)                 # MCMLXXXVII
nc_base_convert("ff", 16, 36)      # 73
nc_dec2words(42)                   # forty-two

支持 2–64 进制。注意 nc_base_convert 的参数顺序是 (字符串, 源进制, 目标进制),与 PHP base_convert 的 (数字, 从, 到) 一致,但返回值是字符串。

phonetic:三种语音编码

px
ph_soundex("Robert")      # R163
ph_soundex("Rupert")      # R163  ← 同音同码
ph_match("Smith","Smyth") # true

Soundex / Metaphone / NYSIIS 三种都有,ph_distance 给编辑距离。只对拉丁字母有意义,中文拼音要先转写。

simtext:相似度与 LCS

px
st_similar("kitten", "sitting")     # 61.53846153846154(百分比,float)
st_lcs("ABCBDAB", "BDCABA")         # 4
st_lcs_str("kitten", "sitting")     # ittn
st_jaro_winkler("martha", "marhta") # 0.961...
返回百分比是 float,不是整数st_similar 沿用 PHP similar_text 的算法与口径,结果是浮点百分比。要整数自己 int() 截断。字符串很长时这个算法是递归 LCS,复杂度不低——比对几千字符以上的文本用 st_lcs(DP)更稳。

sprintf:C 风格格式化

px
sf_format("%05d", [42])            # 00042
sf_format("%8.3f", [3.5])          #    3.500
sf_format("%08.3f", [3.5])         # 0003.500
sf_format("%+05d", [42])           # +0042
sf_format("%x|%-6s", [255, "ab"])  # ff|ab
宽度、补零、符号都按 C 语义零填充在浮点与带符号整数上同样生效(%08.3f → 0003.500、%+05d → +0042);宽度不够时左补填充字符,%-6s 左对齐。

strpad:填充与截断

px
sp_pad("7", 3, "0", "left")        # 007
sp_pad_both("mid", 9, "-")         # ---mid---
sp_truncate("一口气写完这篇文档", 8) # 一口气写完...(按字符算,不是字节)
sp_repeat("ab", 3)                 # ababab
sp_zeropad(42, 5)                  # 00042

按字符计宽,不是字节——中文不会被切坏。这是它比手写 substr 强的地方。

textfmt:PHP 文本函数合集

px
tf_nl2br("line1\nline2")                    # line1<br>\nline2
tf_strtr_table("Hello {name}", {"{name}":"世界"})  # Hello 世界
tf_qp_encode("中文 = 编码")                  # Quoted-Printable
tf_wordwrap("一段很长的话…", 20, "\n", true)
tf_addslashes("it's")                        # it\'s

最常用的是 tf_strtr_table:给一个映射字典批量替换,比链式 replace 快且不会互相覆盖。注意 tf_nl2br 输出的是 <br>,直接塞进 HTML 可以;要塞进属性或 JSON 先 hs_escape。

vardump:三种转储格式

vd_dump(带类型与长度)、vd_export(可当代码复用)、vd_print_r(人读)。调试接口时把 vd_export 的结果贴进日志,比 json_stringify 更容易看出类型。

输出是给人看的,不要拿去解析三种格式都没有机器可读保证(缩进、类型写法都可能变)。要结构化落库一律 json_stringify。

colorutil:颜色换算

px
co_hex_to_rgb("#FF8800")                 # [255,136,0]
co_lighten("#3366cc", 0.2)               # #84A3E0
co_complement("#3366cc")                 # #CC9932
co_contrast_ratio("#000000", "#ffffff")  # 21.0
co_is_dark("#101010")                    # true

对比度用的是 WCAG 口径(1–21)。做配色检查时 正文对比度 ≥ 4.5、大字号 ≥ 3.0,co_contrast_ratio 直接给判定值。它不做取色(从图片里取主色),那是 gfx / png 的活。