技术可以解决大部分的问题,但是只是从技术层面上出发去考虑,往往具有较大的局限性,且容易出现吃力不讨好的局面。因为,技术最终是用来解决实际问题的,需求源自实际场景,目标也是实际场景,单纯去完成一个纯技术的动作,很容易指标不治本,缺少足够的健壮性和可扩展性。

我们有一个学校的跑步打卡群,每个月建立一个微信群,然后每个人需要设置好自己的真实姓名和目标,目标的范围是50km的整数倍,比如50、100、150,最多一般到400的样子。我们要解决的问题是,通过手段获取到每个用户的昵称后,自动解析出他的中文姓名和目标。虽然我们已经明确了,要按照中文真实姓名+目标的格式,但是具体到每个人实施的时候,就会出现各种各样的形式。比如:

  • 张帅-150KM
  • 50KM-王帅
  • 陈帅@-100KM
  • 梁帅50km
  • 杨帅☆200
  • 关帅~100
  • 100KM-2018级孔帅
  • 2019邓帅200Km

还会有各种千奇百怪的连接符和表现形式。从纯技术角度出发,肯定就会想到万能的正则表达式,从里边想办法辨别出中文、连接符、数字,然后过滤掉一些固定的黑名单,大概率就能从里边找出姓名和目标来。我们最开始的代码大概是这样的:

const regExp = /^(?:(\d+)(?:km[\b\-\—\~\~\d\s级]*|)([\u4e00-\u9fa5]+))|(?:([\u4e00-\u9fa5]+)(?:[\b\-\—\~\~\s级]*|\-\d+\-)(\d+)(?:km)?)$/i
let targets = []
nicknames.forEach(nickname => {
let ms = nickname.match(regExp)
if (!ms || !ms.length) {
console.error('识别错误:' + nickname)
return
}
let target = null
if (ms[1]) {
target = {
nickname: ms[2],
target: ms[1],
origin: nickname,
}
} else {
target = {
nickname: ms[3],
target: ms[4],
origin: nickname,
}
}
if (target.target == 0 || target.target % 50 != 0) {
console.error('识别错误:' + nickname);
} else {
targets.push(target)
}
})

实际上,每个月执行下来,总会出现几个例外,发现一些新的连接符和构成模式,于是,正则表达式就会越来越复杂。

最后,我们觉得这样下去不行了,每个月都得重新调整我们的代码,好麻烦。于是,就回过头来重新思考,人们去做这件事情时,会从人的角度去把自己的真实姓名和目标放置进去,还有一部分则还会把自己是哪一级的学生也标识进去。那么,从人的角度去理解,这些内容都有什么特点呢?

首先是年份:一般都会是年份或者年份+级,实际上,这里边最影响判断的就是,这个是不是姓名的一部分,年份的话,4个数字,它和目标还是有很大的区分度的。那么,只要出现\d{4}级 的内容,把这部分过滤掉就行。

接下来就是找姓名,很明显,姓名肯定是中文,而中文的话,编码都是特定区间的,可以很简单的和目标以及其他无意义的分隔符区分出来。

最后就是目标了,这个还真是最简单的,因为只有可能是50的整倍。

至此,识别的算法就非常简单明了了。

const yearExp = /\d+级/
const nameExp = /([\u4e00-\u9fa5]{2,3})/
const distanceExp = /(50|100|150|200|250|300|350|400|450|500)/
let targets = []
nicknames.forEach( nickname => {
let target = {
distances: 0,
name: '',
origin: nickname
}
// 去掉干扰因素
if (nickname.indexOf('级') > -1) {
nickname = nickname.replace(yearExp, '')
}
// 匹配姓名
let ms = nickname.match(nameExp)
if (ms && ms.length) {
target.name = ms[1]
} else {
console.error(`name not found:${nickname}`)
}
// 匹配目标
ms = nickname.match(distanceExp)
if (ms && ms.length) {
target.distances = parseInt(ms[1], 10)
} else {
console.error(`distance not found:${nickname}`)
}
targets.push(target)
})

这样,从人性的角度,从实际场景出发,我们做出来了非常稳定的识别算法,后面再也没有去动过这部分代码了。所以,精准地理解业务需求,才能提供更加精炼的解决方案。

标签: javascript

仅有一条评论

  1. good good

    这种文本映射是GPT最擅长的:

    这是一个跑步群的名单,每个人都会在群昵称里写上自己的名字和目标跑量,你帮我从以下文本中解析出人名和目标跑量,目标跑量一般为50的倍数,形成一个json数组{name:string,target:number}[] 张帅-150KM 50KM-王帅 陈帅@-100KM 梁帅50km 杨帅☆200 关帅~100 100KM-2018级孔帅 2019邓帅200Km ChatGPT 说: 当然,以下是从文本中解析出的JSON数组,其中包含了人名和目标跑量: json 复制代码 [ {"name": "张帅", "target": 150}, {"name": "王帅", "target": 50}, {"name": "陈帅", "target": 100}, {"name": "梁帅", "target": 50}, {"name": "杨帅", "target": 200}, {"name": "关帅", "target": 100}, {"name": "孔帅", "target": 100}, {"name": "邓帅", "target": 200} ] 在解析过程中,我忽略了文本中的一些非目标跑量的信息,并统一了目标跑量的格式。

添加新评论