美文网首页
算法遇记 | 字符串段拆插问题 - 富文本

算法遇记 | 字符串段拆插问题 - 富文本

作者: _Jun | 来源:发表于2022-12-16 21:38 被阅读0次

    1. 场景说明

    最近遇到一个小问题,这里把问题模型简化,记录一下处理方式,也算是一个小纪念。先说一下场景,如下所示:

    已知字符串 src
    匹配段列表:matches
    

    这样,在 Flutter 中可以通过对 matches 的遍历,形成富文本段,进行展示,效果如下:

    TextSpan formSpan() {
      List<List<int>> matches = [
        [1, 2], [5, 8], [14, 15]
      ];
      String src = "toly 1994,hello!";
      List<InlineSpan> span = [];
      int cursor = 0;
      for (int i = 0; i < matches.length; i++) {
        List<int> match = matches[i];
        // 非匹配段
        String noMatchStr = src.substring(cursor, match[0]);
        span.add(TextSpan(text:noMatchStr , style: style0));
        // 匹配段
        String matchStr = src.substring(match[0], match[1]+1);
        span.add(TextSpan(text: matchStr, style: style1,));
        cursor = match[1]+1;
      }
      if (cursor != src.length - 1) {
        span.add(TextSpan(text: src.substring(cursor), style: style0));
      }
      return TextSpan(children: span);
    }
    

    2. 要解决的需求

    现在有个需求,给定槽点列表 slots,在 保持原有匹配效果 的前提下,在每个槽点对应的索引处,插入该槽点的索引值,如下所示:

    如下,是插入后的效果,其中原来的高亮样式保持不变,且在指定位置处额外插入了文字。这时候 有用怪 难抑心中疑问,发出灵魂呼喊:这有什么用呢?


    如下所示,如果在定点插入的东西不是文字,而是其他组件,比如 FlutterLogo 。就完成了在不影响原有高亮匹配情况下,在指定槽位插入其他组件的能力:

    说一个最直接的应用场景,如下代码高亮行号的插入,就是使用这种手段。不影响原有富文本,在定点插入指定组件。

    代码高亮 + 行号 代码高亮 + 行号

    3. 实现思路

    这个问题的本质是根据 slots 点,对已字符段进行分割。就像一个拼接手术:首先找到位置,然后剪开,把插入段放在两片之间,再黏在一起:


    由于槽点可以在任意位置,所以对于每段来说,操作都是一致的。这样对于每段字符,可以封装一个通用方法来处理。如下,定义 insertSlotWithBoundary 方法,传入每段的起止索引。第一步,应该校验当前段中是否存在槽点。如下左图所示,该段无槽点,就不需要进行什么处理:

    这里定义 slotCursor 记录槽点数组的游标,它会随着每次槽点被处理,而自加。所以某段在处理时,通过 slots[slotCursor] 可以得到当前待入槽点位置。如下所示,当 slotCursor 长度大于大于总槽位时,说明已经插入完毕,不需要关注槽点了;或者 待入槽点位置 要比 end 还大,说明当前段没有槽点:

    int slotCursor = 0;
    
    insertSlotWithBoundary(int start, int end, TextStyle style) {
    
      if (slotCursor >= slots.length || slots[slotCursor] > end) {
        // 说明当前段没有槽点,无需处理
        span.add(TextSpan(
          text: src.substring(start, end),
          style: style,
        ));
        return;
      }
      // TODO 槽点处理
    }
    

    在某段中,可能存在 n 个槽点,把段分割为 n+1 段。结合 slotCursor 游标和 end 值,可以通过 while 循环进行遍历处理:

    在进入循环时,将 slotCursor++,需要注意截取的终点需要额外处理一下。若干槽位已经结束,或下一槽位大于 end ,说明 下一槽点不再当前段。 将截取的终点设为 end :

    insertSlotWithBoundary(int start, int end, TextStyle style) {
      // 同上,略...
      // 有槽点,分割插槽
      String matchStr = src.substring(start, slots[slotCursor]);
      span.add(TextSpan(text: matchStr, style: style));
      while (slots[slotCursor] < end) {
        int slotPosition = slots[slotCursor];
        slotCursor++;
        int currentEndPosition = 0;
        if (slotCursor == slots.length || slots[slotCursor] > end) {
          // 说明插槽结束
          // 说明下一槽点不再当前段
          currentEndPosition = end;
        } else {
          currentEndPosition = slots[slotCursor];
        }
        // 插入槽点组件:
        span.add(const WidgetSpan(child: FlutterLogo()));
        String matchStr = src.substring(slotPosition, currentEndPosition);
        span.add(TextSpan(
          text: matchStr,
          style: style,
        ));
        if (slotCursor >= slots.length) break;
      }
    }
    

    到这里,处理就完成了,虽然代码量比较少,但是其中需要考虑的点挺多的。包括校验条件、循环流程、游标处理等。在实现期间也走了不少弯路,试错花了不少时间,在调试中逐步解决问题。本以为我完成不了代码高亮的行号显示的,但在耐心和分析中还是写出来了,过程可谓是痛快的。

    现在终于可以在 Flutter 中代码展示或者文本展示时加上行号了,仅以此文纪念这份自主解决问题的的愉悦感。下面是完整的 formSpan 方法,感兴趣的可以自己试一下:

    TextSpan formSpan() {
      List<List<int>> matches = [[1, 2], [5, 8], [14, 15]];
      List<int> slots = [0, 2, 6, 8, 11, 13];
      String src = "toly 1994,hello!";
      List<InlineSpan> span = [];
      int cursor = 0;
      int slotCursor = 0;
    
      insertSlotWithBoundary(int start, int end, TextStyle style) {
        if (slotCursor>=slots.length||slots[slotCursor] > end) {
          // 说明当前段没有槽点,无需处理
          span.add(TextSpan(
            text: src.substring(start, end),
            style: style,
          ));
          return;
        }
        // 有槽点,分割插槽
        String matchStr = src.substring(start, slots[slotCursor]);
        span.add(TextSpan(text: matchStr, style: style));
        while (slots[slotCursor] < end) {
          int slotPosition = slots[slotCursor];
          slotCursor++;
          int currentEndPosition = 0;
          if (slotCursor == slots.length || slots[slotCursor] > end) {
            // 说明插槽结束
            // 说明下一槽点不再当前段
            currentEndPosition = end;
          } else {
            currentEndPosition = slots[slotCursor];
          }
          span.add(const WidgetSpan(child: FlutterLogo()));
          String matchStr2 = src.substring(slotPosition, currentEndPosition);
          span.add(TextSpan(
            text: matchStr2,
            style: style,
          ));
          if (slotCursor >= slots.length) break;
        }
      }
    
      for (int i = 0; i < matches.length; i++) {
        List<int> match = matches[i];
        insertSlotWithBoundary(cursor, match[0], style0);
        insertSlotWithBoundary(match[0], match[1] + 1, style1);
        cursor = match[1] + 1;
      }
      if (cursor != src.length - 1) {
        insertSlotWithBoundary(cursor,src.length, style0);
      }
      return TextSpan(children: span);
    }
    

    作者:张风捷特烈
    链接:https://juejin.cn/post/7176805642303176762

    相关文章

      网友评论

          本文标题:算法遇记 | 字符串段拆插问题 - 富文本

          本文链接:https://www.haomeiwen.com/subject/zfrjqdtx.html