【问题标题】:Nested structure with O(1) "inside" determinateO(1) \"inside\" 确定的嵌套结构
【发布时间】:2022-08-19 02:45:02
【问题描述】:

背景

希望改进 tokenization logic 的卷曲直引号的库。

问题

在英文中,一些ambiguous的条件可以在多次通过文本后正确卷曲,例如:

“贾维斯,先生?为什么,他是“几年前列名的。”

这应该编码为:

“Jarvis, sir? Why, him as 'listed some years ago.”

并渲染为:

“贾维斯,先生?为什么,他在几年前就被列入名单了。”

我们知道前面的字形列出是撇号 ('),而不是开放单引号 (‘),因为嵌套引号可能不会在父引号之外关闭。

我的解析器是单通的,这意味着它不能正确地卷曲类似的明确标记。

可视化

考虑以下愚蠢的例子:

\“反向散射\'直到奶牛回家栖息或筑巢或\'无论我的鲍勃\',就像巴布斯所说的那样。\”

在下图中,索引 15 处的直单引号是不平衡的,但在一对平衡的直双引号之间(分别为索引 1 和 100),它们本身包括一对嵌套的直单引号(索引 59 和 75 , 分别):

所有信息都用于将\'til 上的撇号与其他直单引号区分开来,因为所有其他直引号都是平衡且明确的(没有\'whatever 收缩,也没有bob\' 收缩)。

数据结构是堆栈和平衡树之间的一种交叉,但两者都不是。

问题

什么数据结构允许跟踪具有 O(1) 查询的平衡嵌套对以确定结构中的值是否在平衡对之间?

可能是B-Tree


生成图表的代码如下:

graph tree {
  outputorder=\"edgesfirst\"
  graph[nodesep=0.5, ranksep=0.5, splines=line];
  node [fontname = \"Dejavu Sans\", fontsize=16,
        style=filled, fillcolor=lightgreen,
        shape=circle, fixedsize=true, width=0.8];

  doc [label=\"doc\", shape=square, fillcolor=lightblue];
  n1a [label=\"\\\" 1\"];
  n1b [label=\"\' 15\", fillcolor=pink];
  n1c [label=\"\\\" 100\"];

  bm [style=dashed, label=\"\", shape=square, color=lightblue];

  doc -- n1a;
  doc -- n1b;
  doc -- bm [style=dotted];
  doc -- n1c;

  { rank=same n1a -- n1b -- bm -- n1c [style=invis] }

  n2a [label=\"\' 59\"];
  n2b [label=\"\' 75\"];

  { rank=same n2a -- n2b [style=invis] }

  bm -- n2a;
  bm -- n2b;

  edge [style=dotted];
  node [fillcolor=none, shape=square, style=dotted];
  what  [label=\"what\\never\"];

  back -- n1a
  til -- n1b
  said -- n1c
  n2a -- what
  n2b -- bob
}
  • 在这个例子中,'直到奶牛回家栖息或筑巢或'无论我的鲍勃',你怎么知道哪个是撇号? \'直到? \'任何?
  • 已知的英语缩略集是相当有限的,并且包含在各种列表中(请参阅Contractions.java)。所以我们知道\'whatever 不是缩略词,因此它必须有一个单引号。 bob\' 也是如此,但作为结束语。也许这样的数据结构不存在。
  • 当你知道一组收缩时,为什么不使用它呢?将该列表添加到您的工作中并处理它似乎微不足道。你在完成这项工作时到底遇到了什么问题?
  • 按顺序浏览列表非常容易,只需标记已知平衡对之间的未解析引号即可。不过,我可能更喜欢动态编程方法来解决整个问题。
  • 谁能推荐什么数据结构对跟踪不平衡和平衡对有用?我想查询沿tree.isBalanced( token ) && tree.isNested( token ) 行的结构,其中token 是对示例图中直引号/索引15 的引用。查找需要是 O(1)。

标签: java data-structures binary-tree quotes linguistics


【解决方案1】:

使用m-ary tree

有关完整源代码,请参阅the repository

鉴于:

  • 一个Stem标记接口
  • Token实现Stem
  • Tree实现Stem

然后是经典 m-ary 树的变体,具有以下有效负载:

  • 子树和标记的插入顺序集(标记为Stems);
  • 打开令牌;和
  • 关闭令牌。

Tree 源代码类似于 isNested 为 O(1):

class Tree<T extends Token> implements Stem {
  private final Tree<T> mParent;

  /**
   * Provides O(1) lookup time.
   */
  private final Collection<Stem> mStems = new LinkedHashSet<>( 128 );

  private T mOpening = (T) NONE;
  private T mClosing = (T) NONE;

  public Tree() {
    mParent = null;
  }

  private Tree( final Tree<T> parent, final T opening ) {
    assert parent != null;
    assert opening != null;
    assert opening != NONE;

    mParent = parent;
    mOpening = opening;
  }

  public Tree<T> opening( final T opening ) {
    assert opening != null;

    final var tree = new Tree<>( this, opening );
    mStems.add( tree );

    return tree;
  }

  public Tree<T> closing( final T closing ) {
    assert closing != NONE;
    assert mOpening.isBefore( closing );

    mClosing = closing;

    return mParent == null ? this : mParent;
  }

  public void add( final Stem stem ) {
    assert stem != null;

    mStems.add( stem );
  }

  public boolean isNested( final Stem stem ) {
    return mStems.contains( stem );
  }

  public boolean isBalanced() {
    return
      mOpening.isType( QUOTE_OPENING_DOUBLE ) &&
        mClosing.isType( QUOTE_CLOSING_DOUBLE ) ||
        mOpening.isType( QUOTE_OPENING_SINGLE ) &&
          mClosing.isType( QUOTE_CLOSING_SINGLE );
  }

  public Tree<T> parent() {
    return mParent;
  }

  public String toXml() {
    final var sb = new StringBuilder( 128 );
    final var name = parent() == null ? "root" : "tree";

    sb.append( '<' );
    sb.append( name );
    sb.append( '>' );

    if( !mOpening.isType( TokenType.NONE ) ) {
      sb.append( mOpening.toXml() );
    }

    mStems.forEach( stem -> sb.append( stem.toXml() ) );

    if( !mClosing.isType( TokenType.NONE ) ) {
      sb.append( mClosing.toXml() );
    }

    sb.append( "</" );
    sb.append( name );
    sb.append( '>' );

    return sb.toString();
  }
}

给定一组引号标记,例如:

  • QUOTE_OPENING_SINGLE("开单")
  • QUOTE_OPENING_DOUBLE("开双")
  • QUOTE_CLOSING_SINGLE("收尾单")
  • QUOTE_CLOSING_DOUBLE(“关闭双”)
  • QUOTE_APO​​STROPHE("撇号")
  • QUOTE_AMBIGUOUS_LEADING("开头不明确")
  • QUOTE_AMBIGUOUS_LAGGING("关闭-模糊")
  • 模棱两可(“模棱两可”)

使用Tree 如下所示,其中每个Token 实例都由词法分析器发出(未显示):

  @Override
  public void accept( final Token token ) {
    // Create a new subtree when an opening quotation mark is found.
    if( token.isType( QUOTE_OPENING_SINGLE ) ||
      token.isType( QUOTE_OPENING_DOUBLE ) ) {
      mTree = mTree.opening( token );
    }
    // Close the subtree if it was open, try to close it.
    else if( token.isType( QUOTE_CLOSING_SINGLE ) ||
      token.isType( QUOTE_CLOSING_DOUBLE ) ) {
      mTree = mTree.closing( token );
    }
    // Add any ambiguous tokens to the subtree, which are resolved after
    // the in-memory AST is built.
    else if( token.isAmbiguous() ) {
      mTree.add( token );
    }
  }

这产生了一个嵌套的树结构,其中每个嵌套级别对应于一个引用中的一个引用(只要有可能识别)。

示例输出:

“她说,‘那是山姆的’,”山姆的猫说。

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <tree>
      <opening-double type="QUOTE_OPENING_DOUBLE" began="0" ended="1" />
      <tree>
         <opening-single type="QUOTE_OPENING_SINGLE" began="11" ended="12" />
         <closing-ambiguous type="QUOTE_AMBIGUOUS_LAGGING" began="24" ended="25" />
         <closing-double type="QUOTE_CLOSING_DOUBLE" began="26" ended="27" />
      </tree>
      <closing-ambiguous type="QUOTE_AMBIGUOUS_LAGGING" began="41" ended="42" />
   </tree>
</root>

示例输出:

“A”、“B”和“C”是字母。

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <opening-ambiguous type="QUOTE_AMBIGUOUS_LEADING" began="0" ended="1" />
   <tree>
      <opening-single type="QUOTE_OPENING_SINGLE" began="5" ended="6" />
      <closing-single type="QUOTE_CLOSING_SINGLE" began="7" ended="8" />
   </tree>
   <tree>
      <opening-single type="QUOTE_OPENING_SINGLE" began="14" ended="15" />
      <closing-single type="QUOTE_CLOSING_SINGLE" began="16" ended="17" />
   </tree>
   <closing-single type="QUOTE_CLOSING_SINGLE" began="2" ended="3" />
</root>

示例输出:

“没有多少同胞,我想,小姐;我所知道的——我的老主人,作为一个知道战争的人,曾经说过,他说,‘如果我播种小麦而没有卤汁’ ,我是个荷兰人,”他说,“那场战争就像荷兰人在打一场傻瓜或隔壁的战争一样。不,不,我不会因为荷兰人而打扰我的。还有傻瓜,一个流氓伊诺,我不会为他们看书。”

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <tree>
      <opening-double type="QUOTE_OPENING_DOUBLE" began="0" ended="1" />
      <tree>
         <opening-single type="QUOTE_OPENING_SINGLE" began="115" ended="116" />
         <closing-single type="QUOTE_CLOSING_SINGLE" began="170" ended="171" />
      </tree>
      <closing-ambiguous type="QUOTE_AMBIGUOUS_LAGGING" began="362" ended="363" />
      <closing-double type="QUOTE_CLOSING_DOUBLE" began="378" ended="379" />
   </tree>
</root>

【讨论】: