Подтвердить что ты не робот

Как разбить строку, сохраняя целые слова?

Мне нужно разделить длинное предложение на части, сохраняющие целые слова. Каждая часть должна иметь максимальное количество символов (включая пробел, точки и т.д.). Например:

int partLenght = 35;
string sentence = "Silver badges are awarded for longer term goals. Silver badges are uncommon."

Вывод:

1 part: "Silver badges are awarded for"
2 part: "longer term goals. Silver badges are"
3 part: "uncommon."
4b9b3361

Ответ 1

Попробуйте следующее:

    static void Main(string[] args)
    {
        int partLength = 35;
        string sentence = "Silver badges are awarded for longer term goals. Silver badges are uncommon.";
        string[] words = sentence.Split(' ');
        var parts = new Dictionary<int, string>();
        string part = string.Empty;
        int partCounter = 0;
        foreach (var word in words)
        {
            if (part.Length + word.Length < partLength)
            {
                part += string.IsNullOrEmpty(part) ? word : " " + word;
            }
            else
            {
                parts.Add(partCounter, part);
                part = word;
                partCounter++;
            }
        }
        parts.Add(partCounter, part);
        foreach (var item in parts)
        {
            Console.WriteLine("Part {0} (length = {2}): {1}", item.Key, item.Value, item.Value.Length);
        }
        Console.ReadLine();
    }

Ответ 2

Я знал, что должен быть хороший способ LINQ-y сделать это, поэтому здесь для удовольствия:

var input = "The quick brown fox jumps over the lazy dog.";
var charCount = 0;
var maxLineLength = 11;

var lines = input.Split(' ', StringSplitOptions.RemoveEmptyEntries)
    .GroupBy(w => (charCount += w.Length + 1) / maxLineLength)
    .Select(g => string.Join(" ", g));

// That all :)

foreach (var line in lines) {
    Console.WriteLine(line);
}

Очевидно, что этот код работает только до тех пор, пока запрос не является параллельным, так как он зависит от charCount для увеличения в порядке слов.

Ответ 3

Я тестировал ответы Jon и Lessan, но они не работают должным образом, если ваша максимальная длина должна быть абсолютной, а не приблизительной. Поскольку их счетчики увеличиваются, он не учитывает пустое пространство, оставшееся в конце строки.

Запустив свой код против примера OP, вы получите:

1 part: "Silver badges are awarded for " - 29 Characters
2 part: "longer term goals. Silver badges are" - 36 Characters
3 part: "uncommon. " - 13 Characters

"находятся" на второй строке, должны быть в строке три. Это происходит потому, что счетчик не включает 6 символов из конца строки.

Я придумал следующую модификацию ответа Lessan для учетной записи для этого:

public static class ExtensionMethods
{
    public static string[] Wrap(this string text, int max)
    {
        var charCount = 0;
        var lines = text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
        return lines.GroupBy(w => (charCount += (((charCount % max) + w.Length + 1 >= max) 
                        ? max - (charCount % max) : 0) + w.Length + 1) / max)
                    .Select(g => string.Join(" ", g.ToArray()))
                    .ToArray();
    }
}

Ответ 4

Разделите строку с помощью (пробел), которые создают новые строки из результирующего массива, останавливаясь перед вашим лимитом для каждого нового сегмента.

Неподтвержденный псевдокод:

string[] words = sentence.Split(new char[] {' '});
IList<string> sentenceParts = new List<string>();
sentenceParts.Add(string.Empty);

int partCounter = 0;    

foreach (var word in words)
{
  if(sentenceParts[partCounter].Length + word.Length > myLimit)
  {
     partCounter++;
     sentenceParts.Add(string.Empty);
  }

  sentenceParts[partCounter] += word + " ";
}

Ответ 5

Сначала я думал, что это может быть что-то вроде Regex, но здесь мой выстрел в него:

List<string> parts = new List<string>();
int partLength = 35;
string sentence = "Silver badges are awarded for longer term goals. Silver badges are uncommon.";

string[] pieces = sentence.Split(' ');
StringBuilder tempString = new StringBuilder("");

foreach(var piece in pieces)
{
    if(piece.Length + tempString.Length + 1 > partLength) 
    {
        parts.Add(tempString.ToString());
        tempString.Clear();        
    }
    tempString.Append(" " + piece); 
}

Ответ 6

Расширение ответа на jon выше; Мне нужно было переключить g с помощью g.toArray(), а также изменить max на (max + 2), чтобы получить точную привязку к символу max'th.

public static class ExtensionMethods
{
    public static string[] Wrap(this string text, int max)
    {
        var charCount = 0;
        var lines = text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
        return lines.GroupBy(w => (charCount += w.Length + 1) / (max + 2))
                    .Select(g => string.Join(" ", g.ToArray()))
                    .ToArray();
    }
}

И вот пример использования в качестве тестов NUnit:

[Test]
public void TestWrap()
{
    Assert.AreEqual(2, "A B C".Wrap(4).Length);
    Assert.AreEqual(1, "A B C".Wrap(5).Length);

    Assert.AreEqual(2, "AA BB CC".Wrap(7).Length);
    Assert.AreEqual(1, "AA BB CC".Wrap(8).Length);

    Assert.AreEqual(2, "TEST TEST TEST TEST".Wrap(10).Length);
    Assert.AreEqual(2, "  TEST TEST TEST TEST  ".Wrap(10).Length);
    Assert.AreEqual("TEST TEST", "  TEST TEST TEST TEST  ".Wrap(10)[0]);
}

Ответ 7

Joel в вашем коде есть небольшая ошибка, которую я исправил здесь:

public static string[] StringSplitWrap(string sentence, int MaxLength)
{
        List<string> parts = new List<string>();
        string sentence = "Silver badges are awarded for longer term goals. Silver badges are uncommon.";

        string[] pieces = sentence.Split(' ');
        StringBuilder tempString = new StringBuilder("");

        foreach (var piece in pieces)
        {
            if (piece.Length + tempString.Length + 1 > MaxLength)
            {
                parts.Add(tempString.ToString());
                tempString.Clear();
            }
            tempString.Append((tempString.Length == 0 ? "" : " ") + piece);
        }

        if (tempString.Length>0)
            parts.Add(tempString.ToString());

        return parts.ToArray();
}

Ответ 8

Это работает:

int partLength = 35;
string sentence = "Silver badges are awarded for longer term goals. Silver badges are uncommon.";
List<string> lines =
    sentence
        .Split(' ')
        .Aggregate(new [] { "" }.ToList(), (a, x) =>
        {
            var last = a[a.Count - 1];
            if ((last + " " + x).Length > partLength)
            {
                a.Add(x);
            }
            else
            {
                a[a.Count - 1] = (last + " " + x).Trim();
            }
            return a;
        });

Это дает мне:

Silver badges are awarded for 
longer term goals. Silver badges 
are uncommon. 

Ответ 9

В то время как CsConsoleFormat † был предназначен, в первую очередь, для форматирования текста для консоли, он также поддерживает создание простого текста.

var doc = new Document().AddChildren(
  new Div("Silver badges are awarded for longer term goals. Silver badges are uncommon.") {
    TextWrap = TextWrapping.WordWrap
  }
);
var bounds = new Rect(0, 0, 35, Size.Infinity);
string text = ConsoleRenderer.RenderDocumentToText(doc, new TextRenderTarget(), bounds);

И если вам действительно нужны обрезанные строки, как в вашем вопросе:

List<string> lines = text.Trim()
  .Split(new[] { Environment.NewLine }, StringSplitOptions.None)
  .Select(s => s.Trim())
  .ToList();

В дополнение к переносу слов в пространствах вы получаете правильную обработку дескрипторов, пространств нулевой ширины, пробелов без пробелов и т.д.

† CsConsoleFormat был разработан мной.

Ответ 10

Кажется, что все используют какую-то форму "Split затем перестройте предложение"...

Я думал, что попробую это так, как мой мозг будет логически думать о том, чтобы сделать это вручную, а именно:

  • Разделить по длине
  • Вернитесь назад к ближайшему месту и используйте этот кусок
  • Удалите использованный кусок и начните сначала

Код оказался немного сложнее, чем я надеялся, однако я считаю, что он обрабатывает большинство (все?) Крайних случаев - включая слова длиннее maxLength, когда слова заканчиваются точно на maxLength и т.д.

Вот моя функция:

private static List<string> SplitWordsByLength(string str, int maxLength)
{
    List<string> chunks = new List<string>();
    while (str.Length > 0)
    {
        if (str.Length <= maxLength)                    //if remaining string is less than length, add to list and break out of loop
        {
            chunks.Add(str);
            break;
        }

        string chunk = str.Substring(0, maxLength);     //Get maxLength chunk from string.

        if (char.IsWhiteSpace(str[maxLength]))          //if next char is a space, we can use the whole chunk and remove the space for the next line
        {
            chunks.Add(chunk);
            str = str.Substring(chunk.Length + 1);      //Remove chunk plus space from original string
        }
        else
        {
            int splitIndex = chunk.LastIndexOf(' ');    //Find last space in chunk.
            if (splitIndex != -1)                       //If space exists in string,
                chunk = chunk.Substring(0, splitIndex); //  remove chars after space.
            str = str.Substring(chunk.Length + (splitIndex == -1 ? 0 : 1));      //Remove chunk plus space (if found) from original string
            chunks.Add(chunk);                          //Add to list
        }
    }
    return chunks;
}

Тестовое использование:

string testString = "Silver badges are awarded for longer term goals. Silver badges are uncommon.";
int length = 35;

List<string> test = SplitWordsByLength(testString, length);

foreach (string chunk in test)
{
    Console.WriteLine(chunk);  
}

Console.ReadLine();