Example #1
0
    /// <summary>
    /// B+树节点
    /// </summary>
    /// <param name="tree">包含该节点的树</param>
    /// <param name="parent">节点的父节点</param>
    /// <param name="indexInParent">在父节点中的索引</param>
    /// <param name="isLeaf">是否为叶子节点</param>
    public BPlusTreeNode(BPlusTreeLong tree, BPlusTreeNode parent, int indexInParent, bool isLeaf)
    {
      if(tree == null)
        throw new ArgumentNullException("tree");

      this.Tree = tree;
      this.Parent = parent;
      this.IsLeaf = isLeaf;

      this.IndexInParent = -1;
      this.BlockNumber = StoredConstants.NullBlockNumber;
      this.Capacity = tree.NodeCapacity;
      this.Dirty = true;

      this.Clear();

      // 存在父节点
      if (parent != null && indexInParent >= 0)
      {
        // B+树 父节点中键值数与子节点数量相等
        if (indexInParent > this.Capacity)
        {
          throw new BPlusTreeException("parent index too large");
        }
        // 建立与父节点关系
        this.Parent.ChildNodes[indexInParent] = this;
        this.BlockNumber = this.Parent.ChildValues[indexInParent];
        this.IndexInParent = indexInParent;
      }
    }
Example #2
0
    /// <summary>
    /// 加载指定插入的索引点的节点对象
    /// </summary>
    /// <param name="insertPosition">指定插入的索引点</param>
    /// <returns>节点对象</returns>
    private BPlusTreeNode LoadNodeAtIndex(int insertPosition)
    {
      if (this.IsLeaf) // 只对中间节点应用有效
      {
        throw new BPlusTreeException("cannot materialize child for leaf");
      }

      // 获取指定位置对应子节点的块序号
      long childBlockNumber = this.ChildValues[insertPosition];
      if (childBlockNumber == StoredConstants.NullBlockNumber)
      {
        throw new BPlusTreeException("can't search null subtree at position " + insertPosition + " in " + this.BlockNumber);
      }

      // 节点对象已经存在吗
      BPlusTreeNode node = this.ChildNodes[insertPosition];
      if (node != null)
      {
        return node;
      }

      // 如果不存在则从块中加载
      node = new BPlusTreeNode(this.Tree, this, insertPosition, true); // dummy isLeaf item
      node.LoadFromBlock(childBlockNumber);
      this.ChildNodes[insertPosition] = node;

      // 该节点不在是终端节点
      this.Tree.ForgetTerminalNode(this); // 我已经有了一个具体化的子节点,我不再是终端节点

      return node;
    }
Example #3
0
    /// <summary>
    /// 查找指定索引处的下一个键值,如果没有则遍历右子树,如果仍没找到则返回空
    /// </summary>
    /// <param name="atIndex">开始查找的索引</param>
    /// <param name="foundInLeaf">找到键值的叶子节点</param>
    /// <param name="foundKey">找到的键值</param>
    private void TraverseToFollowingKey(int atIndex, out BPlusTreeNode foundInLeaf, out string foundKey)
    {
      foundInLeaf = null;
      foundKey = null;

      bool lookInParent = false;
      if (this.IsLeaf)
      {
        lookInParent = (atIndex >= this.Capacity) || (this.ChildKeys[atIndex] == null);
      }
      else
      {
        lookInParent = (atIndex > this.Capacity) || (atIndex > 0 && this.ChildKeys[atIndex - 1] == null);
      }

      if (lookInParent)
      {
        // if it's anywhere it's in the next child of parent
        if (this.Parent != null && this.IndexInParent >= 0)
        {
          this.Parent.TraverseToFollowingKey(this.IndexInParent + 1, out foundInLeaf, out foundKey);
          return;
        }
        else
        {
          return; // no such following key
        }
      }

      if (this.IsLeaf)
      {
        // leaf, we found it.
        foundInLeaf = this;
        foundKey = this.ChildKeys[atIndex];
        return;
      }
      else
      {
        // nonleaf, look in child (if there is one)
        if (atIndex == 0 || this.ChildKeys[atIndex - 1] != null)
        {
          BPlusTreeNode child = this.LoadNodeAtIndex(atIndex);
          child.TraverseToFollowingKey(0, out foundInLeaf, out foundKey);
        }
      }
    }
Example #4
0
    /// <summary>
    /// 在节点中插入键值对并作为叶子节点
    /// </summary>
    /// <param name="key">叶子节点的键</param>
    /// <param name="value">键对应的值</param>
    /// <param name="splitFirstKey">if not null then the smallest key in the new split leaf</param>
    /// <param name="splitNode">if not null then the node was split and this is the leaf to the right.</param>
    /// <returns>smallest key item in keys, or null if no change</returns>
    public string InsertLeaf(string key, long value, out string splitFirstKey, out BPlusTreeNode splitNode)
    {
      splitFirstKey = null;
      splitNode = null;

      if (!this.IsLeaf)
      {
        throw new BPlusTreeException("bad call to insert leaf, this is not a leaf");
      }

      // 标示节点已被更改
      this.Soil();

      // 查找新键的位置 键可能已经存在
      int insertPosition = this.FindAtOrNextPosition(key, false);

      bool doSplit = false;

      // 节点未满
      if (insertPosition < this.Capacity)
      {
        // 如果键已存在,则更改其对应值及位置,不支持重复的条目
        if (this.ChildKeys[insertPosition] == null || this.Tree.Compare(this.ChildKeys[insertPosition], key) == 0)
        {
          this.ChildKeys[insertPosition] = key;
          this.ChildValues[insertPosition] = value;

          // 返回键序列中的最小值,如果无更改则返回空
          if (insertPosition == 0)
          {
            return key;
          }
          else
          {
            return null;
          }
        }
        // 插入点为比指定键稍大的键
      }
      else
      {
        // 节点已满,准备分割节点
        doSplit = true;
      }

      // 查看是否还有空位置
      int nullIndex = insertPosition;
      while (nullIndex < this.ChildKeys.Length && this.ChildKeys[nullIndex] != null)
      {
        nullIndex++;
      }
      if (nullIndex >= this.ChildKeys.Length)
      {
        doSplit = true;
      }

      // 做分割的准备 数组增加1
      if (doSplit)
      {
        this.PrepareBeforeSplit();
      }

      // 将新数据插入至数组中,将已存在的值向右移动
      string nextKey = this.ChildKeys[insertPosition];
      long nextValue = this.ChildValues[insertPosition];
      this.ChildKeys[insertPosition] = key;
      this.ChildValues[insertPosition] = value;
      while (nextKey != null)
      {
        key = nextKey;
        value = nextValue;

        insertPosition++;

        nextKey = this.ChildKeys[insertPosition];
        nextValue = this.ChildValues[insertPosition];

        this.ChildKeys[insertPosition] = key;
        this.ChildValues[insertPosition] = value;
      }

      // 如果需要分割
      if (doSplit)
      {
        // 从中间开始分割 折半
        int splitPoint = this.ChildKeys.Length / 2;
        int splitLength = this.ChildKeys.Length - splitPoint;

        // 新创建的分割出的节点,始终是右节点
        splitNode = new BPlusTreeNode(this.Tree, this.Parent, -1, this.IsLeaf);

        // 将指定分割点左侧的数据拷贝至新的节点
        Array.Copy(this.ChildKeys, splitPoint, splitNode.ChildKeys, 0, splitLength);
        Array.Copy(this.ChildValues, splitPoint, splitNode.ChildValues, 0, splitLength);
        Array.Copy(this.ChildNodes, splitPoint, splitNode.ChildNodes, 0, splitLength);

        // 记录分割节点的第一个键,右节点的第一个键
        splitFirstKey = splitNode.ChildKeys[0];

        // 存储新节点至块文件
        splitNode.DumpToNewBlock();

        // 分割完毕 恢复之前的准备 处理分割点右侧数据,保留左侧数据,删除右侧数据
        this.RepairAfterSplit(splitPoint);

        // 记录新的节点
        this.Tree.RecordTerminalNode(splitNode); // InsertLeaf

        // 新节点及其父节点需要处理
        splitNode.Soil();
      }

      if (insertPosition == 0)
      {
        return key; // smallest key changed.
      }
      else
      {
        return null; // no change in smallest key
      }
    }
Example #5
0
    /// <summary>
    /// 重新组织分割树,新的根节点将有两个子节点
    /// </summary>
    /// <param name="oldRoot">原根节点</param>
    /// <param name="splitFirstKey">新根节点的第一个Key</param>
    /// <param name="splitNode">新分割出的节点</param>
    /// <param name="tree">指定的树</param>
    /// <returns>新根节点</returns>
    public static BPlusTreeNode BinaryRoot(BPlusTreeNode oldRoot, string splitFirstKey, BPlusTreeNode splitNode, BPlusTreeLong tree)
    {
      if (oldRoot == null)
        throw new ArgumentNullException("oldRoot");
      if (splitNode == null)
        throw new ArgumentNullException("splitNode");

      // 已不是叶子节点
      BPlusTreeNode newRoot = MakeRoot(tree, false);

      // 新的跟记录分割节点的第一个Key
      newRoot.ChildKeys[0] = splitFirstKey;

      // 新旧节点分别为新的根节点的索引 0 1 位置
      oldRoot.ResetParent(newRoot, 0);
      splitNode.ResetParent(newRoot, 1);

      // new root is stored elsewhere
      return newRoot;
    }
Example #6
0
    /// <summary>
    /// 重置节点的父节点
    /// </summary>
    /// <param name="newParent">父节点</param>
    /// <param name="indexInParent">在父节点中的索引</param>
    private void ResetParent(BPlusTreeNode newParent, int indexInParent)
    {
      // keys and existing parent structure must be updated elsewhere.
      this.Parent = newParent;
      this.IndexInParent = indexInParent;

      newParent.ChildValues[indexInParent] = this.BlockNumber; // 中间节点存储的值为子节点的块序号
      newParent.ChildNodes[indexInParent] = this;

      // parent is no longer terminal
      this.Tree.ForgetTerminalNode(this.Parent); // 父节点已经有了一个具体化的子节点,父节点不再是终端节点
    }
Example #7
0
    /// <summary>
    /// 合并叶子节点,当节点的使用率不足50%时,则需要合并
    /// </summary>
    /// <param name="left">左节点</param>
    /// <param name="right">右节点</param>
    /// <param name="canDeleteRightNode">是否可以删除右节点</param>
    public static void MergeLeaves(BPlusTreeNode left, BPlusTreeNode right, out bool canDeleteRightNode)
    {
      if (left == null)
        throw new ArgumentNullException("left");
      if (right == null)
        throw new ArgumentNullException("right");

      canDeleteRightNode = false;

      string[] allKeys = new string[left.Capacity * 2];
      long[] allValues = new long[left.Capacity * 2];

      int index = 0;
      for (int i = 0; i < left.Capacity; i++)
      {
        if (left.ChildKeys[i] == null)
        {
          break;
        }
        allKeys[index] = left.ChildKeys[i];
        allValues[index] = left.ChildValues[i];
        index++;
      }

      for (int i = 0; i < right.Capacity; i++)
      {
        if (right.ChildKeys[i] == null)
        {
          break;
        }
        allKeys[index] = right.ChildKeys[i];
        allValues[index] = right.ChildValues[i];
        index++;
      }

      // 如果左节点的容量足够,则可删除右节点
      if (index <= left.Capacity)
      {
        canDeleteRightNode = true;

        left.Clear();
        
        for (int i = 0; i < index; i++)
        {
          left.ChildKeys[i] = allKeys[i];
          left.ChildValues[i] = allValues[i];
        }

        left.Soil();
        right.Free();        

        return;
      }

      // 左节点的容量不够了
      left.Clear();
      right.Clear();
      left.Soil();
      right.Soil();

      int rightContent = index / 2;
      int leftContent = index - rightContent;
      int newIndex = 0;
      for (int i = 0; i < leftContent; i++)
      {
        left.ChildKeys[i] = allKeys[newIndex];
        left.ChildValues[i] = allValues[newIndex];
        newIndex++;
      }
      for (int i = 0; i < rightContent; i++)
      {
        right.ChildKeys[i] = allKeys[newIndex];
        right.ChildValues[i] = allValues[newIndex];
        newIndex++;
      }
    }
Example #8
0
    /// <summary>
    /// 在节点中插入键值对
    /// </summary>
    /// <param name="key">叶子节点的键</param>
    /// <param name="value">键对应的值</param>
    /// <param name="splitFirstKey">if not null then the smallest key in the new split leaf</param>
    /// <param name="splitNode">if not null then the node was split and this is the leaf to the right.</param>
    /// <returns>
    /// null unless the smallest key under this node has changed, in which case it returns the smallest key.
    /// </returns>
    public string Insert(string key, long value, out string splitFirstKey, out BPlusTreeNode splitNode)
    {
      if (this.IsLeaf)
      {
        return this.InsertLeaf(key, value, out splitFirstKey, out splitNode);
      }

      // 我不是叶子 我是中间节点 找到Key对应的位置 在子节点中插入键值对
      splitFirstKey = null;
      splitNode = null;

      // 查找新键的位置 由于是中间节点 则新键的位置必须存在
      int insertPosition = this.FindAtOrNextPosition(key, false);

      // 非叶子节点中的值数组存储叶子节点的块序号
      long insertValue = this.ChildValues[insertPosition];
      if (insertValue == StoredConstants.NullBlockNumber)
      {
        throw new BPlusTreeException("key not followed by block number in non-leaf");
      }

      // 加载子节点
      BPlusTreeNode insertChild = this.LoadNodeAtIndex(insertPosition);

      string childSplitFirstKey;
      BPlusTreeNode childSplitNode;
      // 在子节点中插入新的键值对
      string childInsert = insertChild.Insert(key, value, out childSplitFirstKey, out childSplitNode);

      // 发现子节点已满,也需要分割
      if (childSplitNode != null)
      {
        // 我即将被更改
        this.Soil(); // redundant -- a child will have a change so this node will need to be copied

        // 为新的子节点创建位置索引,即下一个
        int newChildPosition = insertPosition + 1;

        // 自己是否已满
        bool doSplit = false;

        // 如果我作为中间节点容量也满了,则中间节点也需要被分割
        if (this.ChildValues[this.Capacity] != StoredConstants.NullBlockNumber)
        {
          doSplit = true;
        }

        if (doSplit)
        {
          // 做分割准备
          this.PrepareBeforeSplit();
        }

        // bubble over the current values to make space for new child
        // 新节点位置上及其右侧内容全部向右移动1位,为新节点空出位置
        for (int i = this.ChildKeys.Length - 2; i >= newChildPosition - 1; i--)
        {
          int iPlus1 = i + 1;
          int iPlus2 = iPlus1 + 1;
          this.ChildKeys[iPlus1] = this.ChildKeys[i];
          this.ChildValues[iPlus2] = this.ChildValues[iPlus1];
          this.ChildNodes[iPlus2] = this.ChildNodes[iPlus1];
        }

        // record the new child
        // 新节点的位置存放新节点的第一个键
        this.ChildKeys[newChildPosition - 1] = childSplitFirstKey;

        // 被分割出的子节点的父节点为自己
        childSplitNode.ResetParent(this, newChildPosition);

        // 如果我作为中间节点容量也满了,则中间节点也需要被分割
        if (doSplit)
        {
          // 从中间开始分割 折半
          int splitPoint = this.ChildNodes.Length / 2 - 1;

          // 分割出的新节点的第一个Key
          splitFirstKey = this.ChildKeys[splitPoint];

          // 新建节点 包含分割点右侧所有数据
          splitNode = new BPlusTreeNode(this.Tree, this.Parent, -1, this.IsLeaf);
          splitNode.Clear(); // redundant.

          // 记录已经扩充的数据结构         
          long[] values = this.ChildValues;
          string[] keys = this.ChildKeys;
          BPlusTreeNode[] nodes = this.ChildNodes;

          // 重置和清空数据
          this.Clear();

          // 将分割点左侧的数据拷贝至此节点
          Array.Copy(keys, 0, this.ChildKeys, 0, splitPoint);
          Array.Copy(values, 0, this.ChildValues, 0, splitPoint + 1);
          Array.Copy(nodes, 0, this.ChildNodes, 0, splitPoint + 1);

          // 将分割点右侧的数据拷贝至新的分割节点
          int remainingKeys = this.Capacity - splitPoint;          
          Array.Copy(keys, splitPoint + 1, splitNode.ChildKeys, 0, remainingKeys);
          Array.Copy(values, splitPoint + 1, splitNode.ChildValues, 0, remainingKeys + 1);
          Array.Copy(nodes, splitPoint + 1, splitNode.ChildNodes, 0, remainingKeys + 1);

          // 重置新节点中所有的子节点的父节点
          splitNode.ResetAllChildrenParent();

          // 存储新节点
          splitNode.DumpToNewBlock();
          splitNode.CheckIfTerminal();
          splitNode.Soil();

          this.CheckIfTerminal();
        } // end do split

        // 重置节点中所有的子节点的父节点
        this.ResetAllChildrenParent();
      }

      // 返回最小的那个键
      if (insertPosition == 0)
      {
        return childInsert; // the smallest key may have changed
      }
      else
      {
        return null;  // no change in smallest key
      }
    }
Example #9
0
    /// <summary>
    /// 合并节点,当节点的使用率不足50%时,则需要合并
    /// </summary>
    /// <param name="left">左节点</param>
    /// <param name="keyBetween">左右节点的中间键</param>
    /// <param name="right">右节点</param>
    /// <param name="rightLeastKey">合并后的键的最小值</param>
    /// <param name="canDeleteRightNode">是否可以删除右节点</param>
    public static void Merge(BPlusTreeNode left, string keyBetween, BPlusTreeNode right, out string rightLeastKey, out bool canDeleteRightNode)
    {
      if (left == null)
        throw new ArgumentNullException("left");
      if (right == null)
        throw new ArgumentNullException("right");

      rightLeastKey = null; // only if DeleteRight

      // 合并叶子节点
      if (left.IsLeaf || right.IsLeaf)
      {
        if (!(left.IsLeaf && right.IsLeaf))
        {
          throw new BPlusTreeException("can't merge leaf with non-leaf");
        }

        // 合并子节点
        MergeLeaves(left, right, out canDeleteRightNode);

        rightLeastKey = right.ChildKeys[0];

        return;
      }

      // 合并非叶子节点
      canDeleteRightNode = false;

      if (left.ChildValues[0] == StoredConstants.NullBlockNumber || right.ChildValues[0] == StoredConstants.NullBlockNumber)
      {
        throw new BPlusTreeException("cannot merge empty non-leaf with non-leaf");
      }

      string[] allKeys = new string[left.Capacity * 2 + 1];
      long[] allValues = new long[left.Capacity * 2 + 2];
      BPlusTreeNode[] allNodes = new BPlusTreeNode[left.Capacity * 2 + 2];

      // 拷贝左节点的数据
      int index = 0;
      allValues[0] = left.ChildValues[0];
      allNodes[0] = left.ChildNodes[0];
      for (int i = 0; i < left.Capacity; i++)
      {
        if (left.ChildKeys[i] == null)
        {
          break;
        }

        allKeys[index] = left.ChildKeys[i];
        allValues[index + 1] = left.ChildValues[i + 1];
        allNodes[index + 1] = left.ChildNodes[i + 1];

        index++;
      }

      // 拷贝中间键
      allKeys[index] = keyBetween;
      index++;

      // 拷贝右节点的数据
      allValues[index] = right.ChildValues[0];
      allNodes[index] = right.ChildNodes[0];
      int rightCount = 0;
      for (int i = 0; i < right.Capacity; i++)
      {
        if (right.ChildKeys[i] == null)
        {
          break;
        }

        allKeys[index] = right.ChildKeys[i];
        allValues[index + 1] = right.ChildValues[i + 1];
        allNodes[index + 1] = right.ChildNodes[i + 1];
        index++;

        rightCount++;
      }

      // 如果数量小于左节点的能力,则右节点可以删除掉
      if (index <= left.Capacity)
      {
        // it will all fit in one node
        canDeleteRightNode = true;

        for (int i = 0; i < index; i++)
        {
          left.ChildKeys[i] = allKeys[i];
          left.ChildValues[i] = allValues[i];
          left.ChildNodes[i] = allNodes[i];
        }

        left.ChildValues[index] = allValues[index];
        left.ChildNodes[index] = allNodes[index];

        left.ResetAllChildrenParent();
        left.Soil();

        right.Free();

        return;
      }

      // otherwise split the content between the nodes
      left.Clear();
      right.Clear();
      left.Soil();
      right.Soil();

      int leftContent = index / 2;
      int rightContent = index - leftContent - 1;

      rightLeastKey = allKeys[leftContent];

      int outputIndex = 0;
      for (int i = 0; i < leftContent; i++)
      {
        left.ChildKeys[i] = allKeys[outputIndex];
        left.ChildValues[i] = allValues[outputIndex];
        left.ChildNodes[i] = allNodes[outputIndex];
        outputIndex++;
      }

      rightLeastKey = allKeys[outputIndex];

      left.ChildValues[outputIndex] = allValues[outputIndex];
      left.ChildNodes[outputIndex] = allNodes[outputIndex];
      outputIndex++;

      rightCount = 0;
      for (int i = 0; i < rightContent; i++)
      {
        right.ChildKeys[i] = allKeys[outputIndex];
        right.ChildValues[i] = allValues[outputIndex];
        right.ChildNodes[i] = allNodes[outputIndex];
        outputIndex++;

        rightCount++;
      }

      right.ChildValues[rightCount] = allValues[outputIndex];
      right.ChildNodes[rightCount] = allNodes[outputIndex];

      left.ResetAllChildrenParent();
      right.ResetAllChildrenParent();
    }
Example #10
0
    /// <summary>
    /// 将当前节点的容量扩大(+1),为插入和分割做准备
    /// </summary>
    private void PrepareBeforeSplit()
    {
      int superSize = this.Capacity + 1;

      string[] keys = new string[superSize];
      long[] positions = new long[superSize + 1];
      BPlusTreeNode[] materialized = new BPlusTreeNode[superSize + 1];

      Array.Copy(this.ChildKeys, 0, keys, 0, this.Capacity);
      keys[this.Capacity] = null;
      Array.Copy(this.ChildValues, 0, positions, 0, this.Capacity + 1);
      positions[this.Capacity + 1] = StoredConstants.NullBlockNumber;
      Array.Copy(this.ChildNodes, 0, materialized, 0, this.Capacity + 1);
      materialized[this.Capacity + 1] = null;

      this.ChildValues = positions;
      this.ChildKeys = keys;
      this.ChildNodes = materialized;
    }
Example #11
0
    /// <summary>
    /// Find near-index of comparekey in leaf under this node. 
    /// </summary>
    /// <param name="compareKey">the key to look for</param>
    /// <param name="inLeaf">the leaf where found</param>
    /// <param name="lookPastOnly">If true then only look for a greater item, not an exact match.</param>
    /// <returns>index of match in leaf</returns>
    private int FindAtOrNextPositionInLeaf(string compareKey, out BPlusTreeNode inLeaf, bool lookPastOnly)
    {
      int keyPosition = this.FindAtOrNextPosition(compareKey, lookPastOnly);

      // 如果自己即是叶子节点
      if (this.IsLeaf)
      {
        inLeaf = this;
        return keyPosition;
      }

      // 尝试在子节点中查找
      BPlusTreeNode child = this.LoadNodeAtIndex(keyPosition);
      return child.FindAtOrNextPositionInLeaf(compareKey, out inLeaf, lookPastOnly);
    }
Example #12
0
    /// <summary>
    /// 由树记录节点是否为终端节点,终端节点没有子节点,而根或中间节点有子节点
    /// </summary>
    /// <param name="nonTerminalNode">这个节点已经不是终端节点</param>
    public void ForgetTerminalNode(BPlusTreeNode nonTerminalNode)
    {
      if (!this._TerminalNodeToId.ContainsKey(nonTerminalNode))
      {
        // silently ignore (?)
        return;
      }

      int id = (int)this._TerminalNodeToId[nonTerminalNode];
      if (id == this._LowerTerminalNodeCount)
      {
        this._LowerTerminalNodeCount++;
      }

      this._IdToTerminalNode.Remove(id);
      this._TerminalNodeToId.Remove(nonTerminalNode);
    }
Example #13
0
    /// <summary>
    /// 由树记录节点是否为终端节点,终端节点没有子节点,而根或中间节点有子节点
    /// </summary>
    /// <param name="terminalNode">这个节点是终端节点</param>
    public void RecordTerminalNode(BPlusTreeNode terminalNode)
    {
      if (terminalNode == this.RootNode)
      {
        return; // never record the root node
      }
      if (this._TerminalNodeToId.ContainsKey(terminalNode))
      {
        return; // don't record it again
      }

      int id = this._TerminalNodeCount;
      this._TerminalNodeCount++;

      this._TerminalNodeToId[terminalNode] = id;
      this._IdToTerminalNode[id] = terminalNode;
    }