【问题标题】:How can I transform a string into an abbreviated form?如何将字符串转换为缩写形式?
【发布时间】:2010-10-29 04:19:31
【问题描述】:

我想将字符串适应到一个特定的宽度。例如,“Hello world” -> “...world”、“Hello...”、“He...rld”。

你知道我在哪里可以找到代码吗?这是一个巧妙的技巧,对于表示信息非常有用,我想将它添加到我的应用程序中(当然)。

编辑:抱歉,我忘了提及字体部分。不仅适用于固定宽度的字符串,还适用于字体。

【问题讨论】:

    标签: c++ algorithm mfc string abbreviation


    【解决方案1】:

    如果你在任何地方都找不到它,这是一个非常简单的算法,可以自己编写 - 伪代码类似于:

    if theString.Length > desiredWidth:
        theString = theString.Left(desiredWidth-3) + "...";
    

    或者,如果您希望在字符串的开头使用省略号,则第二行将是:

        theString = "..." + theString.Right(desiredWidth-3);
    

    或者如果你想要它在中间:

        theString = theString.Left((desiredWidth-3)/2) + "..." + theString.Right((desiredWidth-3)/2 + ((desiredWidth-3) mod 2))
    

    编辑:
    我假设您使用的是 MFC。由于您需要字体,因此可以使用 CDC::GetOutputTextExtent 函数。试试:

    CString fullString
    CSize size = pDC->GetOutputTextExtent(fullString);
    bool isTooWide = size.cx > desiredWidth;
    

    如果太大,那么您可以进行搜索以尝试找到您可以容纳的最长字符串;它可以是你想要的聪明的搜索 - 例如,你可以尝试“Hello Worl ...”然后“Hello Wor ...”然后“Hello Wo ...”;删除一个字符,直到找到合适为止。或者,您可以使用binary search - 尝试“Hello Worl ...” - 如果这不起作用,则只需使用原始文本的一半字符:“Hello ...” - 如果合适,请尝试中途在它和:“Hello Wo...”之间,直到找到仍然合适的最长。或者您可以尝试一些估计启发式方法(将总长度除以所需长度,按比例估计所需字符数,然后从那里搜索。

    简单的解决方案是这样的:

    unsigned int numberOfCharsToUse = fullString.GetLength();
    bool isTooWide = true;
    CString ellipsis = "...";
    while (isTooWide)
    {
        numberOfCharsToUse--;
        CString string = fullString.Left(numberOfCharsToUse) + ellipsis;
        CSize size = pDC->GetOutputTextExtent(string);
        isTooWide = size.cx > desiredWidth;
    }
    

    【讨论】:

    • 是的,它的 MFC 和我将实现一个 智能 匹配。在尝试实现我自己的之前,我想看看是否有这种东西的有效实现。但我想我必须坐下来发挥创造力:)
    • 真棒,完整,有用的答案。
    【解决方案2】:

    这真的很微不足道;我不认为你会找到特定的代码,除非你有更结构化的想法。

    你基本上想要:

    1. 获取字符串的长度和窗口宽度。
    2. 算出你可以从原始字符串中取出多少个字符,这基本上是窗口宽度为 3。称之为k
    3. 根据您要将省略号放在中间还是在右手端,从一端取一楼(k/2)个字符,与“...”连接,然后与最后一个连接floor(k/2) 个字符(由于除法,可能还需要一个字符);或取前 k 个字符,后跟“...”。

    【讨论】:

      【解决方案3】:

      我认为 Smashery 的回答是一个好的开始。获得最终结果的一种方法是编写一些带有一些测试输入和所需输出的测试代码。一旦你有一套好的测试设置,你就可以实现你的字符串操作代码,直到你通过所有的测试。

      【讨论】:

        【解决方案4】:
        • 计算文本的宽度( 基于字体)

        如果您使用 MFC,API GetOutputTextExtent 将为您提供价值。

        • 如果宽度超过给定 具体宽度,先计算椭圆宽度:

          ellipseWidth = 计算 (...) 的宽度

        • 从末尾删除宽度为ellipseWidth的字符串部分并附加椭圆。

          类似:你好...

        【讨论】:

        • 您可能会发现,宽度(文本)+宽度(省略号)不等于宽度(文本+省略号)。这是因为某些字形在渲染时会重叠以使其看起来更漂亮。因此,您不能只从所需宽度中减去椭圆宽度 - 您必须检查全长(包括省略号)
        • 是的,没错。在一个循环中,我们需要检查椭圆的大小是否对应于给定的大小(以便识别要从字符串中删除的字符)。
        【解决方案5】:

        对于那些对完整例程感兴趣的人,这是我的答案

        /**
         *  Returns a string abbreviation
         *  example: "hello world" -> "...orld" or "hell..." or "he...rd" or "h...rld"
         *
         *  style:
              0: clip left
              1: clip right
              2: clip middle
              3: pretty middle
         */
        CString*
        strabbr(
          CDC* pdc,
          const char* s,
          const int area_width,
          int style  )
        {
          if (  !pdc || !s || !*s  ) return new CString;
        
          int len = strlen(s);
          if (  pdc->GetTextExtent(s, len).cx <= area_width  ) return new CString(s);
        
          int dots_width = pdc->GetTextExtent("...", 3).cx;
          if (  dots_width >= area_width  ) return new CString;
        
          // My algorithm uses 'left' and 'right' parts of the string, by turns.
          int n = len;
          int m = 1;
          int n_width = 0;
          int m_width = 0;
          int tmpwidth;
          // fromleft indicates where the clip is done so I can 'get' chars from the other part
          bool  fromleft = (style == 3  &&  n % 2 == 0)? false : (style > 0);
          while (  TRUE  ) {
            if (  n_width + m_width + dots_width > area_width  ) break;
            if (  n <= m  ) break; // keep my sanity check (WTF), it should never happen 'cause of the above line
        
            //  Here are extra 'swap turn' conditions
            if (  style == 3  &&  (!(n & 1))  )
              fromleft = (!fromleft);
            else if (  style < 2  )
              fromleft = (!fromleft); // (1)'disables' turn swapping for styles 0, 1
        
            if (  fromleft  ) {
              pdc->GetCharWidth(*(s+n-1), *(s+n-1), &tmpwidth);
              n_width += tmpwidth;
              n--;
            }
            else {
              pdc->GetCharWidth(*(s+m-1), *(s+m-1), &tmpwidth);
              m_width += tmpwidth;
              m++;
            }
        
            fromleft = (!fromleft); // (1)
          }
        
          if ( fromleft ) m--; else n++;
        
          // Final steps
          // 1. CString version
          CString*  abbr = new CString;
          abbr->Format("%*.*s...%*.*s", m-1, m-1, s, len-n, len-n, s + n);
          return abbr;
        
          /* 2. char* version, if you dont want to use CString (efficiency), replace CString with char*,
                               new CString with _strdup("") and use this code for the final steps:
        
          char* abbr = (char*)malloc(m + (len-n) + 3 +1);
          strncpy(abbr, s, m-1);
          strcpy(abbr + (m-1), "...");
          strncpy(abbr+ (m-1) + 3, s + n, len-n);
          abbr[(m-1) + (len-n) + 3] = 0;
          return abbr;
          */
        }
        

        【讨论】:

        • +1 干得好!提个醒:我在用另一种语言做类似的事情时发现,当你在字体中组合字母时,它们可能不一定等于它们各自长度的总和。例如,当您将“W”放在“A”旁边时,字体渲染器可能会将它们靠得更近一些,以使其在人眼看来更漂亮。所以宽度('W')+宽度('A')可能不等于宽度('WA')。对于小字符串,它可能只会产生几个像素的差异,但对于较长的字符串,它可能会非常明显。
        • 也感谢您在答案中建议二进制搜索,非常好的主意!也许我会在有时间的时候实现它 - 二进制搜索可能很棘手,所以我在第一个版本中跳过了它:)
        【解决方案6】:

        如果您只想要“标准”省略号格式,(​​“Hello...”),您可以使用 DrawText(或等效的MFC function)函数传递DT_END_ELLIPSIS

        还可以查看DT_WORD_ELLIPSIS(它会截断任何不适合矩形的单词并添加省略号)或DT_PATH_ELLIPSIS(Explorer 用于显示长路径的操作)。

        【讨论】:

        • 我认为我在发布问题时需要一个算法,但无论如何谢谢,这超级方便!
        • 我发现你的问题正在寻找同样的问题,在 MSDN GetTextExtent 上查找,并在 DrawText 中找到了这个简单的选项。由于最后需要省略号,对我来说已经足够了,也许对于一些未来的读者来说也足够了!
        • 是的,如果我们使用 GDI 并且需要基本的文本修剪,这就是这样做的方法。
        猜你喜欢
        • 2020-02-19
        • 2020-10-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-10
        • 2017-06-29
        • 1970-01-01
        相关资源
        最近更新 更多