整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

手把手教你字符串分割技巧

手把手教你字符串分割技巧

家好,我是 Echa。

假设我们现在有这样一个需求,把一段话拆分成有意义的句子:

你好,我是 Echa攻城狮小编。我来了!大佬是谁?大佬在哪?

你可能会第一时间想到,用 split 按所有可能断句的标点符号分割就好了,比如下面的代码:

var txt='你好,我是 Echa攻城狮小编。我来了!大佬是谁?大佬在哪?'
txt.split(/[。?!]/); 
// ['你好,我是 Echa攻城狮小编', '我来了', '大佬是谁', '大佬在哪', '']

看起来结果还不错,但是可以断句的中文标点符号只有这三个吗?显然不是,如果我们想要处理更复杂的文本,需要持续完善这个正则,另外这样分割还有一个最大的问题是标点符号会在分割后的结果中丢失。

如果我们想要按词语进行分割,而不是语句呢?

如果我们想要分割的文本是英语、阿拉伯语呢...

// 中文
const cn='你好,我是 Echa攻城狮小编。我来了!大佬是谁?大佬在哪?';

// 英文Hello, my name is Echa Attack Lion. I'm coming. Who is the 
const en="boss? Where is the boss?";

// 阿拉伯语
const ar=' ????? ? ??? ???? ???? ????? .  ??? ????  ?? ?? ????? ?????? ?  ??? ????? ??????';

这时候 split 可能就会表示无能为力了!

Intl API

IntlECMAScript 国际化 API 的一个命名空间,它提供了精确的字符串对比、数字格式化,和日期时间格式化能力。我们今天主要来看一下它提供的字符串分割能力!

Intl.Segmenter 对象专门为语言敏感的文本分割而生,它允许你将一个字符串分割成有意义的片段(字、词、句),下面我们看看它对以上三种语言的分割结果:

中文

const segmenter=new Intl.Segmenter(
  'zh', { granularity: 'sentence' }
);

console.log(
  Array.from(
    segmenter.segment('你好,我是 Echa攻城狮小编。我来了!大佬是谁?大佬在哪?'),
    s=> s.segment
  )
);
//  ['你好,我是 Echa攻城狮小编。', '我来了!', '大佬是谁?', '大佬在哪?']

英语

const segmenter=new Intl.Segmenter(
  'en', { granularity: 'sentence' }
);

console.log(
  Array.from(
    segmenter.segment(`Hello, my name is Echa Attack Lion. I'm coming. Who is the boss? Where is the boss?`),
    s=> s.segment
  )
);
//  ['Hello, my name is Echa Attack Lion. ', "I'm coming. ", 'Who is the boss? ', 'Where is the boss?']

阿拉伯语

const segmenter=new Intl.Segmenter(
  'ar', { granularity: 'sentence' }
);

console.log(
  Array.from(
    segmenter.segment(` ????? ? ??? ???? ???? ????? .  ??? ????  ?? ?? ????? ?????? ?  ??? ????? ??????`),
    s=> s.segment
  )
);
//  [' ????? ? ??? ???? ???? ????? .  ', '??? ????  ?? ?? ????? ?????? ?  ', '??? ????? ??????']

Intl 的兼容性也还不错,除了 Firefox 目前还没有对它提供支持,其他的各大浏览器均已支持。


下面我们来了解一些 Intl.Segmenter 的细节。

构造参数

在上面的示例中,我们在 Intl.Segmenter 的构造函数传入了两个参数。

第一个参数是语言地域编码,结构是:'语言编码-地区编码',因为同样的语言在不同的地区也可能会有区别,比如下面的一些常见示例:

  • zh :中文
  • zh-CN :简体中文
  • zh-HK :香港地区的中文(繁体中文)
  • en :英语
  • en-US :美式英语
  • en-CB :英式英语

第二个参数是一些更详细的配置参数,我们主要关注 granularity,它有三个值,分别表示我们要将字符串分割为句、词、还是字:

const segmenter=new Intl.Segmenter(
  'zh', { granularity: 'sentence' } // 句
);
//  ['你好,我是 Echa攻城狮小编。', '我来了!', '大佬是谁?', '大佬在哪?']

const segmenter=new Intl.Segmenter(
  'zh', { granularity: 'word' } // 词
);
//  ['你好', ',', '我是', ' ', 'Echa', '攻城', '狮', '小', '编', '。', '我来', '了', '!', '大', '佬', '是', '谁', '?', '大', '佬', '在', '哪', '?']

const segmenter=new Intl.Segmenter(
  'zh', { granularity: 'grapheme' } // 字
);
// ['你', '好', ',', '我', '是', ' ', 'E', 'c', 'h', 'a', '攻', '城', '狮', '小', '编', '。', '我', '来', '了', '!', '大', '佬', '是', '谁', '?', '大', '佬', '在', '哪', '?']

返回值

在上面的例子中可以发现,我们使用 Array.fromsegment 的返回值进行了处理:

console.log(
  Array.from(
    segmenter.segment('你好,我是 Echa攻城狮小编。我来了!大佬是谁?大佬在哪?'),
    s=> s.segment
  )
);

这是因为它返回的并不是一个数组,而是一个 iterable 对象,如果访问里面的字段,我可以用 for-of 或者使用 Array.from 函数转换为数组。

const segmenter=new Intl.Segmenter('zh', {
  granularity: 'sentence'
});
const segments=segmenter.segment('...');

// 结构转为数组
console.log([...segments]);

// Array.from 转为数组
console.log(Array.from(segments));

// for-of 遍历
for (let segment of segments) {
  console.log(segment);
}

完整的返回值包括分割后的字符、字符所在位置、输入的完整内容:


另外,在前面的示例中,当我们将文字分割为词时,可以发现标点符号、空格等都被分割出来了:

const segmenter=new Intl.Segmenter(
  'zh', { granularity: 'word' } // 词
);

const result=segmenter.segment(`你好,我是 Echa攻城狮小编。', '我来了!', '大佬是谁?', '大佬在哪?`)

Array.from( result , s=> s.segment)

//   ['你好', ',', '我是', ' ', 'Echa', '攻城', '狮', '小', '编', '。', "'", ',', ' ', "'", '我来', '了', '!', "'", ',', ' ', "'", '大', '佬', '是', '谁', '?', "'", ',', ' ', "'", '大', '佬', '在', '哪', '?']

这时返回值里还会包括一个 isWordLike 属性,可以用于过滤是否真的为词语:

Array.from(result).filter(s=> s.isWordLike).map(s=> s.segment)
// ['你好', '我是', 'Echa', '攻城', '狮', '小', '编', '我来', '了', '大', '佬', '是', '谁', '大', '佬', '在', '哪']

处理 emojis

一般我们要处理的文本里如果包括了 emojis ,那问题就可能变得麻烦起来了...


我们来看下面的示例:

const str1='12345';
const str2='12345?';

console.log(str1.length); // 5
console.log(str2.length); // 10

str1.split('') 
// ['1', '2', '3', '4', '5']
str2.split('')
// ['1', '2', '3', '4', '5', '\uD83D', '\uDE35', '?', '\uD83D', '\uDCAB']

str2.replaceAll(/\D/g, '0')
// '1234500000'

为啥会出现这种现象呢?我们先来回顾一下计算机最基础的概念:字符集与编码:


字符集 (Character Set) 是字符的集合,定义系统能处理哪些字符;编码( Encoding)则规定这些字符在计算机内部的表示方式。

Unicode 是一套标准,包含多语言统一的字符集及其相关编码,以及在这个字符集上进行文本处理的相关规则。

Unicode 中,每个字符被分配了一个数值 (Code Point,代码点) 和一个名称。比如字母 A 的名称是 LATIN CAPITAL LETTER A (大写拉丁字母A)。它对应的数值是 65,通常写作 U+0041( 41 是十六进制数,等于 10 进制的 65)。

字素是文本在书写时最小的单位,可以被理解为单独的“字”。

Unicode 标准中,字符(Character)一般指代码点(Code Point)。通常,一个字素就是一个字符。但是,也有些字素是由多个字符序列组合而成的。比如字母 é 可以用字母 e (U+0065) 加上重音符 (U+0301) 组合而成。像重音符这样用于修饰前一个字符的字符,被称为组合字符(Combining Character)。

比如你看到的这个字符:啊?????????????????????? ,就是个组合字符...

现在对于上面的 emojis 出现的字符串分割问题是不是就容易理解了,因为很多 emojis 都是下面这样的组合字符:

 + ?=?
 - U+1F3FB
? - U+270B
? - U+270B U+1F3FB

'?'.split('') // ['?', '\uD83C', '\uDFFB']

那么下面回归正题了,Intl.Segmenter 的出现也可以解决这样的组合字符的分割问题:

'12345?'.split('')
// ['1', '2', '3', '4', '5', '\uD83D', '\uDE35', '?', '\uD83D', '\uDCAB']


const segmenter=new Intl.Segmenter('en', {
  granularity: 'grapheme'
});

Array.from(
  segmenter.segment('12345?'),
  s=> s.segment
)
// ['1', '2', '3', '4', '5', '?']

最后在多提一下,最近有一个新的 ECMAScript 提案,为正则表达式新增了一个新的标识符 '/v' (目前已经到达 stage 3)就是用来解决正则中的组合字符匹配的问题的:

'12345?'.replaceAll(/\D/g, '0')
// '1234500000'


'12345?'.replaceAll(/\D/gv, '0')
// '123450'

本来是讲字符串分割的,写着写着有点跑偏了,大家凑合着看吧 ...

最后

参考链接:

  • https://deerchao.cn/blog/posts/unicode.html
  • https://2ality.com/2022/11/regexp-v-flag.html
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter/Segmenter
  • https://www.stefanjudis.com/today-i-learned/how-to-split-javascript-strings-with-intl-segmenter/

如果这篇文章帮助到了你,欢迎点赞和关注。



种分割线Html代码


一、基本线条:

1、<HR>


2、align线条位置(可选left、right、center);width线条长度;color颜色;size厚度

<HRalign=center width=300 color=#987cb9SIZE=1>


二、特效(效果并不是孤立的,可相互组合)


1、两头渐变透明:

<HR style="FILTER:alpha(opacity=100,finishopacity=0,style=2)" width="80%"color=#987cb9 SIZE=10>

2、纺锤形:

<HR style="FILTER:alpha(opacity=100,finishopacity=0,style=1)" width="80%"color=#987cb9 SIZE=3>

3、右边渐变透明:

<HR style="FILTER:alpha(opacity=0,finishopacity=100,style=1)" width="80%"color=#987cb9 SIZE=3>

4、左边渐变透明:

<HR style="border:1 dashed #987cb9" width="80%"color=#987cb9 SIZE=1>

5、虚线:

<HR style="border:3 double #987cb9" width="80%"color=#987cb9 SIZE=3>

6、双线:

<HR style="FILTER:progid:DXImageTransform.Microsoft.Shadow(color:#987cb9,direction:145,strength:15)"width="80%" color=#987cb9 SIZE=1>

7、立体效果:

<HR style="FILTER:progid:DXImageTransform.Microsoft.Glow(color=#987cb9,strength=10)"width="80%" color=#987cb9 SIZE=1>

8、钢针效果:

<table border="1px" cellpadding="0" cellspacing="0"style="height:265px;border-left-style:solid;border-bottom-style:none;border-right-style:none;border-top-style:none">

9.垂直分割线

<table border="1px" cellpadding="0" cellspacing="0"style="height:265px;border-left-style:solid;border-bottom-style:none;border-right-style:none;border-top-style:none">


虚线的Html代码

HTML代码:

<hr style="border: 1px dotted #FF0000; padding-left: 4px; padding-right: 4px; padding-top: 1px; padding-bottom: 1px">

[Ctrl+A 全部选择提示:你可先修改部分代码,再按运行]

我们日常的JavaScript编程中,有时需要将一个字符串只拆分一次。本篇文章将介绍如何使用JavaScript实现这一需求。

使用indexOf和slice方法拆分字符串

要在JavaScript中只拆分一次字符串,我们可以结合使用 indexOf 方法和 slice 方法来实现。首先,利用 indexOf 找到分隔符首次出现的位置,然后使用 slice 方法进行字符串的截取。

举个例子:

const s="1|Ceci n'est pas une pipe: | Oui";
const i=s.indexOf('|');
const splits=[s.slice(0, i), s.slice(i + 1)];
console.log(splits);

在这个例子中,我们有一个字符串 s,需要在第一个 | 处进行拆分。

  1. 我们使用 indexOf 方法,传入 | 作为参数,找到 | 首次出现的位置,并将其索引存储在变量 i 中。
  2. 然后使用 slice 方法,两次截取字符串:
  3. 第一次使用 slice(0, i) 获取 | 前面的子字符串。
  4. 第二次使用 slice(i + 1) 获取 | 后面的子字符串。
  5. 最后,将两个子字符串放入 splits 数组中。

因此,最终的 splits 数组为:

["1", "Ceci n'est pas une pipe: | Oui"]

结束

通过结合使用 indexOf 方法和 slice 方法,我们可以在JavaScript中轻松实现字符串的单次拆分。希望这个技巧能对你的开发工作有所帮助。