Как разбить строку, сохраняя целые слова?
Мне нужно разделить длинное предложение на части, сохраняющие целые слова. Каждая часть должна иметь максимальное количество символов (включая пробел, точки и т.д.).
Например:
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."
Ответы
Ответ 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();