目录

  1. 概述
  2. 算法功能
  3. 核心实现
  4. 实战案例
  5. 编译过程详解
  6. 算法扩展
  7. 最佳实践
  8. 常见问题

概述

本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中实现一个完整的正则表达式匹配和替换工具系统。这个案例展示了如何使用 Kotlin 的正则表达式功能来创建一个功能丰富的文本处理工具。通过 KMP,这个工具可以无缝编译到 JavaScript,在 OpenHarmony 应用中运行,并支持用户输入进行实时处理。

工具的特点

  • 多模式匹配:支持邮箱、电话、URL、数字、单词等多种模式
  • 灵活替换:支持将匹配项替换为指定内容
  • 详细统计:提供匹配项的详细统计信息
  • 频率分析:分析单词和数字的出现频率
  • 跨端兼容:一份 Kotlin 代码可同时服务多个平台

算法功能

1. 模式匹配

  • 邮箱匹配:匹配标准的电子邮件地址
  • 电话匹配:匹配格式化的电话号码
  • URL匹配:匹配 HTTP/HTTPS 链接
  • 数字匹配:匹配整数和浮点数
  • 单词匹配:匹配英文单词

2. 匹配统计

  • 匹配数量:统计每种模式的匹配数量
  • 匹配列表:列出所有匹配项
  • 唯一匹配:统计唯一的匹配项

3. 替换操作

  • 邮箱替换:将邮箱替换为占位符
  • 电话替换:将电话替换为占位符
  • 数字替换:将数字替换为占位符

4. 字符统计

  • 字母数:统计字母个数
  • 数字数:统计数字个数
  • 空格数:统计空格个数
  • 特殊字符:统计特殊字符个数
  • 大小写:统计大写和小写字母

5. 频率分析

  • 单词频率:统计每个单词出现的次数
  • 数字频率:统计每个数字出现的次数
  • 排序:按频率从高到低排序

核心实现

1. 定义正则表达式模式

val emailPattern = Regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}")
val phonePattern = Regex("\\d{3}-\\d{3}-\\d{4}")
val urlPattern = Regex("https?://[^\\s]+")
val numberPattern = Regex("\\d+")
val wordPattern = Regex("\\b[a-zA-Z]+\\b")

2. 提取匹配项

val emails = emailPattern.findAll(inputText).map { it.value }.toList()
val phones = phonePattern.findAll(inputText).map { it.value }.toList()
val urls = urlPattern.findAll(inputText).map { it.value }.toList()
val numbers = numberPattern.findAll(inputText).map { it.value }.toList()
val words = wordPattern.findAll(inputText).map { it.value }.toList()

3. 替换匹配项

val emailReplaced = inputText.replace(emailPattern, "[EMAIL]")
val phoneReplaced = inputText.replace(phonePattern, "[PHONE]")
val numberReplaced = inputText.replace(numberPattern, "[NUM]")

4. 频率统计

val uniqueWords = words.toSet()
val wordStats = uniqueWords.map { word ->
    val count = words.count { it == word }
    "$word: $count 次"
}.sorted()

5. 字符统计

val letterCount = inputText.count { it.isLetter() }
val digitCount = inputText.count { it.isDigit() }
val spaceCount = inputText.count { it.isWhitespace() }
val specialCount = inputText.count { !it.isLetterOrDigit() && !it.isWhitespace() }

实战案例

案例:完整的正则表达式匹配和替换工具

Kotlin 源代码
@OptIn(ExperimentalJsExport::class)
@JsExport
fun regexMatcherReplacer(inputText: String = "Email: john@example.com, Phone: 123-456-7890"): String {
    if (inputText.isEmpty()) {
        return "❌ 错误: 输入不能为空\n请输入要处理的文本"
    }
    
    // 1. 常用正则表达式模式
    val emailPattern = Regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}")
    val phonePattern = Regex("\\d{3}-\\d{3}-\\d{4}")
    val urlPattern = Regex("https?://[^\\s]+")
    val numberPattern = Regex("\\d+")
    val wordPattern = Regex("\\b[a-zA-Z]+\\b")
    
    // 2. 提取匹配项
    val emails = emailPattern.findAll(inputText).map { it.value }.toList()
    val phones = phonePattern.findAll(inputText).map { it.value }.toList()
    val urls = urlPattern.findAll(inputText).map { it.value }.toList()
    val numbers = numberPattern.findAll(inputText).map { it.value }.toList()
    val words = wordPattern.findAll(inputText).map { it.value }.toList()
    
    // 3. 统计匹配数量
    val emailCount = emails.size
    val phoneCount = phones.size
    val urlCount = urls.size
    val numberCount = numbers.size
    val wordCount = words.size
    
    // 4. 替换操作
    val emailReplaced = inputText.replace(emailPattern, "[EMAIL]")
    val phoneReplaced = inputText.replace(phonePattern, "[PHONE]")
    val numberReplaced = inputText.replace(numberPattern, "[NUM]")
    
    // 5. 提取所有单词
    val uniqueWords = words.toSet()
    val wordStats = uniqueWords.map { word ->
        val count = words.count { it == word }
        "$word: $count 次"
    }.sorted()
    
    // 6. 提取所有数字
    val uniqueNumbers = numbers.toSet()
    val numberStats = uniqueNumbers.map { num ->
        val count = numbers.count { it == num }
        "$num: $count 次"
    }.sorted()
    
    // 7. 字符类型统计
    val letterCount = inputText.count { it.isLetter() }
    val digitCount = inputText.count { it.isDigit() }
    val spaceCount = inputText.count { it.isWhitespace() }
    val specialCount = inputText.count { !it.isLetterOrDigit() && !it.isWhitespace() }
    
    // 8. 大小写统计
    val uppercaseCount = inputText.count { it.isUpperCase() }
    val lowercaseCount = inputText.count { it.isLowerCase() }
    
    return "🔍 正则表达式匹配和替换\n" +
           "━━━━━━━━━━━━━━━━━━━━━\n" +
           "原始文本: $inputText\n\n" +
           "1️⃣ 模式匹配统计:\n" +
           "  邮箱: $emailCount 个\n" +
           "  电话: $phoneCount 个\n" +
           "  URL: $urlCount 个\n" +
           "  数字: $numberCount 个\n" +
           "  单词: $wordCount 个\n\n" +
           "2️⃣ 匹配项详情:\n" +
           (if (emails.isEmpty()) "  邮箱: 无" else "  邮箱: ${emails.joinToString(", ")}") + "\n" +
           (if (phones.isEmpty()) "  电话: 无" else "  电话: ${phones.joinToString(", ")}") + "\n" +
           (if (urls.isEmpty()) "  URL: 无" else "  URL: ${urls.joinToString(", ")}") + "\n\n" +
           "3️⃣ 字符统计:\n" +
           "  字母: $letterCount 个\n" +
           "  数字: $digitCount 个\n" +
           "  空格: $spaceCount 个\n" +
           "  特殊字符: $specialCount 个\n" +
           "  大写: $uppercaseCount 个\n" +
           "  小写: $lowercaseCount 个\n\n" +
           "4️⃣ 单词频率 (Top 10):\n" +
           wordStats.take(10).mapIndexed { index, stat -> "  ${index + 1}. $stat" }.joinToString("\n") + "\n\n" +
           "5️⃣ 数字频率:\n" +
           numberStats.take(5).mapIndexed { index, stat -> "  ${index + 1}. $stat" }.joinToString("\n") + "\n\n" +
           "6️⃣ 替换示例:\n" +
           "  邮箱替换: $emailReplaced\n" +
           "  电话替换: $phoneReplaced\n" +
           "  数字替换: $numberReplaced\n\n" +
           "━━━━━━━━━━━━━━━━━━━━━\n" +
           "✅ 处理完成!"
}
ArkTS 调用代码(带输入框)
import { regexMatcherReplacer } from './hellokjs';

@Entry
@Component
struct Index {
  @State message: string = '加载中...';
  @State results: string[] = [];
  @State caseTitle: string = '正则表达式匹配和替换';
  @State inputText: string = 'Email: john@example.com, Phone: 123-456-7890';

  aboutToAppear(): void {
    this.loadResults();
  }

  loadResults(): void {
    try {
      const results: string[] = [];
      const algorithmResult = regexMatcherReplacer(this.inputText);
      results.push(algorithmResult);
      
      this.results = results;
      this.message = '✓ 匹配完成';
    } catch (error) {
      this.message = `✗ 错误: ${error}`;
    }
  }

  build() {
    Column() {
      // 顶部标题栏
      Row() {
        Text('KMP 鸿蒙跨端')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor(Color.White)
        Spacer()
        Text('Kotlin 案例')
          .fontSize(14)
          .fontColor(Color.White)
      }
      .width('100%')
      .height(50)
      .backgroundColor('#3b82f6')
      .padding({ left: 20, right: 20 })
      .alignItems(VerticalAlign.Center)
      .justifyContent(FlexAlign.SpaceBetween)

      // 案例标题
      Column() {
        Text(this.caseTitle)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#1f2937')
        Text(this.message)
          .fontSize(13)
          .fontColor('#6b7280')
          .margin({ top: 5 })
      }
      .width('100%')
      .padding({ left: 20, right: 20, top: 20, bottom: 15 })
      .alignItems(HorizontalAlign.Start)

      // 输入框区域
      Column() {
        Text('输入文本:')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#1f2937')
          .margin({ bottom: 8 })
        
        TextInput({ placeholder: '输入要匹配的文本...', text: this.inputText })
          .width('100%')
          .height(60)
          .padding(12)
          .border({ width: 1, color: '#d1d5db' })
          .borderRadius(6)
          .onChange((value: string) => {
            this.inputText = value
          })
        
        Button('匹配')
          .width('100%')
          .height(40)
          .margin({ top: 12 })
          .backgroundColor('#3b82f6')
          .fontColor(Color.White)
          .onClick(() => {
            this.loadResults()
          })
      }
      .width('100%')
      .padding({ left: 16, right: 16, bottom: 16 })

      // 结果显示区域
      Scroll() {
        Column() {
          ForEach(this.results, (result: string) => {
            Column() {
              Text(result)
                .fontSize(13)
                .fontFamily('monospace')
                .fontColor('#374151')
                .width('100%')
                .margin({ top: 10 })
            }
            .width('100%')
            .padding(16)
            .backgroundColor(Color.White)
            .border({ width: 1, color: '#e5e7eb' })
            .borderRadius(8)
            .margin({ bottom: 12 })
          })
        }
        .width('100%')
        .padding({ left: 16, right: 16 })
      }
      .layoutWeight(1)
      .width('100%')

      // 底部按钮区域
      Row() {
        Button('示例文本')
          .width('48%')
          .height(44)
          .backgroundColor('#10b981')
          .fontColor(Color.White)
          .fontSize(14)
          .onClick(() => {
            this.inputText = 'Email: john@example.com, Phone: 123-456-7890'
            this.loadResults()
          })

        Button('清空')
          .width('48%')
          .height(44)
          .backgroundColor('#6b7280')
          .fontColor(Color.White)
          .fontSize(14)
          .onClick(() => {
            this.inputText = ''
            this.results = []
          })
      }
      .width('100%')
      .padding({ left: 16, right: 16, bottom: 20 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f9fafb')
  }
}

编译过程详解

Kotlin 到 JavaScript 的转换

Kotlin 特性 JavaScript 等价物
Regex RegExp
findAll() match() 或 matchAll()
map() 数组 map 方法
count() 条件计数循环
toSet() Set 对象

关键转换点

  1. 正则表达式:转换为 JavaScript RegExp
  2. 集合操作:转换为数组操作
  3. Lambda 表达式:转换为 JavaScript 函数
  4. 字符串处理:保持功能一致

算法扩展

扩展 1:添加自定义模式

fun matchCustomPattern(text: String, pattern: String): List<String> {
    return try {
        Regex(pattern).findAll(text).map { it.value }.toList()
    } catch (e: Exception) {
        emptyList()
    }
}

扩展 2:添加分组提取

fun extractGroups(text: String, pattern: String): List<List<String>> {
    return Regex(pattern).findAll(text).map { match ->
        match.groupValues
    }.toList()
}

扩展 3:添加条件替换

fun conditionalReplace(text: String, pattern: String, condition: (String) -> Boolean, replacement: String): String {
    return Regex(pattern).replace(text) { match ->
        if (condition(match.value)) replacement else match.value
    }
}

扩展 4:添加多模式替换

fun multiPatternReplace(text: String, replacements: Map<String, String>): String {
    var result = text
    for ((pattern, replacement) in replacements) {
        result = result.replace(Regex(pattern), replacement)
    }
    return result
}

最佳实践

1. 使用 findAll() 提取所有匹配项

// ✅ 好:使用 findAll()
val matches = pattern.findAll(text).map { it.value }.toList()

// ❌ 不好:使用 find() 循环
val matches = mutableListOf<String>()
var remaining = text
while (true) {
    val match = pattern.find(remaining)
    if (match != null) {
        matches.add(match.value)
        remaining = remaining.substring(match.range.last + 1)
    } else break
}

2. 使用 toSet() 去重

// ✅ 好:使用 toSet()
val uniqueMatches = matches.toSet()

// ❌ 不好:手动去重
val uniqueMatches = mutableSetOf<String>()
for (match in matches) {
    if (!uniqueMatches.contains(match)) {
        uniqueMatches.add(match)
    }
}

3. 使用 count() 统计

// ✅ 好:使用 count()
val emailCount = emails.size

// ❌ 不好:使用 filter().size
val emailCount = emails.filter { it.isNotEmpty() }.size

4. 预编译正则表达式

// ✅ 好:预编译
val emailPattern = Regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}")
val emails = emailPattern.findAll(text)

// ❌ 不好:每次都编译
val emails = Regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}").findAll(text)

常见问题

Q1: 如何匹配中文字符?

A: 使用 Unicode 范围:

val chinesePattern = Regex("[\\u4e00-\\u9fa5]+")
val chineseWords = chinesePattern.findAll(text).map { it.value }.toList()

Q2: 如何实现不区分大小写的匹配?

A: 使用 RegexOption.IGNORE_CASE:

val pattern = Regex("hello", RegexOption.IGNORE_CASE)
val matches = pattern.findAll(text)

Q3: 如何提取分组内容?

A: 使用 groupValues:

val pattern = Regex("(\\w+)@(\\w+\\.\\w+)")
pattern.findAll(text).forEach { match ->
    val username = match.groupValues[1]
    val domain = match.groupValues[2]
}

Q4: 如何处理特殊字符?

A: 使用 Regex.escape():

val specialChars = "$100"
val pattern = Regex(Regex.escape(specialChars))

Q5: 如何实现多行匹配?

A: 使用 RegexOption.MULTILINE:

val pattern = Regex("^\\w+$", RegexOption.MULTILINE)
val lines = pattern.findAll(text)

总结

关键要点

  • ✅ 使用 findAll() 提取所有匹配项
  • ✅ 使用 toSet() 去重
  • ✅ 使用 count() 统计
  • ✅ 预编译正则表达式
  • ✅ KMP 能无缝编译到 JavaScript

下一步

  1. 实现自定义模式匹配
  2. 添加分组提取功能
  3. 实现条件替换
  4. 添加多模式替换
  5. 实现正则表达式验证

参考资源

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐