【问题标题】:Rotating a multiline string 90 degrees - lines become columns将多行字符串旋转 90 度 - 行变成列
【发布时间】:2021-03-08 14:23:24
【问题描述】:

假设我有一个字符串

Line 1
Line 2

我想把这根弦顺时针旋转90度,这样就变成了

LL
ii
nn
ee

12

旋转只需执行一次,这样就可以有效地“将行变成列”。执行两次应该会给出原始字符串。 (如果它真的旋转了 90 度,则必须重复四次才能回到原来的位置。)

【问题讨论】:

  • 这个问题不是更适合Code Golf 而不是 StackOverflow 吗?从 OP 和它迄今为止吸引的答案来看......

标签: c# string unicode


【解决方案1】:
using System;
using System.Collections;
using System.Collections.Generic;

namespace rotateString
{
    class Program
    {
        static void Main(string[] args)
        {
            var strings = new string[] { "Line 1", "Line 2" };

            var lines = new List<string>();

            var done = false;
            var j = 0;
            do
            {
                var line = "";
                for (var i = 0; i < strings.Length; ++i)
                {
                    var s = strings[i];
                    if (j >= s.Length)
                    {
                        done = true;
                        break;
                    }
                    line += s[j];
                }
                lines.Add(line);
                ++j;
            } while (!done);

            for(int i=0; i < lines.Count; ++i)
            {
                Console.WriteLine(string.Format("{0} : '{1}'", i, lines[i]));
            }
        }
    }
}

输出:

0 : 'LL'
1 : 'ii'
2 : 'nn'
3 : 'ee'
4 : '  '
5 : '12'
6 : ''

请注意,这几乎假设字符串的长度都相同。调整它以使用最长的字符串长度是微不足道的。

使用StringBuilder 会更高效一些,但除非您处理大量非常长的字符串,否则您永远不会看到性能差异。

【讨论】:

  • @KayZed 在 Windows 或 MacOS 上不会失败。你用的是什么编译器?在 Visual Studio 上运行良好。而且,你的问题没有提到 unicode。
  • @KayZed Dave 解决方案绝不会给您任何问题。它只是在移动 char 。它不会以任何方式改变它们。我可以理解 EOF 字符可能会导致问题,但否则这应该 100% 的时间。
  • 除此之外,这是一个很好的解决方案,有效。我喜欢它。
  • 标签上写着 unicode。它也被标记为 C# - 你没有错过它。在 .NET 中查找任何字符串答案。一直都是 Unicode。
  • @KayZed 几乎在所有方面都不正确。除非您的问题实际上包含有关 unicode 的内容,否则仅标记 unicode 是没有意义的。如果你不喜欢这个答案,没关系。否决它。绝大多数生产 C# 代码不必处理 unicode。
【解决方案2】:

尝试和编写这很有趣。我没有花时间在验证和字符上,但我想看看我是否可以写一些有点“紧凑”的东西(主要是在晚上对自己提出挑战)。

@KayZed 我还将运行您的实现,我看到您对输入进行了更多验证。

@3Dave 我知道我们对此有类似的想法;)

我对此的想法是将输入字符串(加上现在我将Span&lt;char&gt; 的长度硬编码,计算可以变得简单)投影到所有字符的“扁平化”结构中,并带有偏移量基于输入数量的索引。

我的“解决方案”:

using System.Collections.Generic;

public class Program
{
    public static void Main(string[] args)
    {
        var inputs = new[] { "Short", "Line 1", "Line 2", "Much longer line 3", "?" };
        
        Rotate(inputs);
    }

    public static void Rotate(IReadOnlyCollection<string> aValues)
    {
        Span<char> chars = stackalloc char[100];
        var offset = 0; 
        foreach (var value in aValues)
        {
            var index = 0;
            foreach (char character in value)
            {
                chars[index + offset] = character;
                index += aValues.Count;
            }
            offset++;
        }

        var position = 0;
        foreach (char character in chars)
        {
            Console.Write(character == default(char) ? ' ' : character);
            Console.Write(' ');

            if (position == aValues.Count - 1)
            {
                Console.WriteLine();
                position = 0;
                continue;
            }

            position++;
        }
    }
}

我可能错过了一些边缘情况(并且没有处理“特殊”字符),但希望这能提供一些“优化”的想法。

输出:

S L L M � 
h i i u � 
o n n c   
r e e h   
t         
  1 2 l   
      o   
      n   
      g   
      e   
      r   
          
      l   
      i   
      n   
      e   
          
      3   
          

【讨论】:

  • 不错。简短,甜美,切中要害。在此之前我也不熟悉stackalloc。我认为我们的 C# 实现落后了几圈。
  • 谢谢。我最近也开始玩它。计划在周末编写几个基准测试并比较实现。还有与 StringBuilder & 数组等的对比。
  • StringBuilder 只会对 very 长的字符串产生明显的影响。但是,这是一个很好的分析等练习。玩得开心!
【解决方案3】:

它将需要添加前导空格(即原始字符串中较短行之前的列。)此方法为您提供了使其可见的选项(fillChar - 作为常量提供的几个选项。)

另外的ReverseLineOrder 方法也会反转行顺序,以防您想旋转 90 度,反转所有行。

using System;
using System.Globalization;
using System.Text;

namespace Library.Text
{
    public static class TextRotate
    {
        public const char Space = ' ';
        public const char MiddleDot = '\u00b7';
        public const char Circle = '\u25cb';
        public const char BlackSquare = '\u25a0';
        public const char NoBreakSpace = '\u00a0';

        public static string LinesToColumns(string s, char fillChar = Space)
        {
            // A line feed at the end of the text is not seen as content.
            // However, if the text ends in a line feed, we make sure the output ends in one, too.

            bool endsWithNewline = s.EndsWith(Environment.NewLine);

            string[] linesIn = s.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);

            int[][] textElements = new int[linesIn.Length][];

            int longestLine_chars = 0;
            int longestLine_elems = 0; // The longest line, in text elements

            for (int l = 0; l < linesIn.Length; l++)
            {
                string line = linesIn[l];

                if (line.Length > longestLine_chars)
                {
                    longestLine_chars = line.Length;
                }

                var elems = StringInfo.ParseCombiningCharacters(line); // Gets indices of surrogate pairs, combining characters etc.

                if (elems.Length > longestLine_elems)
                {
                    longestLine_elems = elems.Length;
                }

                textElements[l] = elems;
            }

            // Go through columns (columns in the input text, and columns in terms of text elements - NOT chars)

            string[] columns = new string[longestLine_elems];

            var builder = new StringBuilder(longestLine_chars * linesIn.Length + Math.Max(longestLine_chars, linesIn.Length) * Environment.NewLine.Length);

            for (int column = 0; column < longestLine_elems; column++)
            {
                builder.Clear();
                System.Diagnostics.Debug.Assert(builder.Length == 0);
                int cutoff = 0;

                for (int l = 0; l < linesIn.Length; l++)
                {
                    // Is the line long enough to reach to this column?

                    int[] lineTextElements = textElements[l];
                    int textElementsInLine = lineTextElements.Length;

                    if (textElementsInLine > column)
                    {
                        int firstCharIndex = lineTextElements[column];

                        if (column + 1 < textElementsInLine)
                        {
                            int nrOfChars = lineTextElements[column + 1] - firstCharIndex;
                            builder.Append(linesIn[l], firstCharIndex, nrOfChars);
                        }
                        else
                        {
                            builder.Append(linesIn[l], firstCharIndex, linesIn[l].Length - firstCharIndex);
                        }

                        cutoff = builder.Length;
                    }
                    else
                    {
                        builder.Append(fillChar);
                    }
                }

                // Trim the fill char off line endings (for when rotating back)
                while (cutoff > 0 && builder[cutoff - 1] == fillChar)
                {
                    cutoff--;
                }

                // Resulting column
                columns[column] = builder.ToString(0, cutoff);
            }

            // Turn the columns into lines
            builder.Clear();

            foreach (var c in columns)
            {
                builder.AppendLine(c);
            }

            if (!endsWithNewline && builder.Length > 0)
            {
                builder.Length -= Environment.NewLine.Length;
            }

            return builder.ToString();
        }

        public static string ReverseLineOrder(string s)
        {
            // A line feed at the end of the text is not seen as content.
            // However, if the text ends in a line feed, we make sure the output ends in one, too.

            bool endsWithNewline = s.EndsWith(Environment.NewLine);

            string[] linesIn = s.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);

            var builder = new StringBuilder(s.Length);

            for (int l = linesIn.Length - (endsWithNewline ? 2 : 1); l >= 0; l--)
            {
                builder.AppendLine(linesIn[l]);
            }

            if (!endsWithNewline && builder.Length > 0)
            {
                builder.Length -= Environment.NewLine.Length;
            }

            return builder.ToString();
        }
    }
}

【讨论】:

  • 您可能希望StringInfo.GetTextElementEnumerator() 或类似的东西来处理不仅由基本+组合字符组成的用户感知字符,例如表情符号
  • 这对于应该需要大约 10 行的东西来说看起来非常长
  • 弗兰克,写下你的代码,让别人和你自己在未来的任何时候都能理解。
  • @Tavian Barnes 这是一个考虑因素,是的。但我认为你提到的方法并没有做到这一点(如果你的意思是文字表情,比如:)
  • 不,我的意思是 ?? 或 ?‍?‍?‍? 之类的东西,它们由多个非组合字符组成(emojipedia.org/emoji-flag-sequenceemojipedia.org/emoji-zwj-sequence
猜你喜欢
  • 2018-07-18
  • 1970-01-01
  • 2016-11-13
  • 1970-01-01
  • 2011-03-22
  • 1970-01-01
  • 2013-05-17
  • 2015-03-22
  • 2022-01-13
相关资源
最近更新 更多