“ThisStringHasNoSpacesButItDoesHaveCapitals”라는 문자열이 주어지면 대문자 앞에 공백을 추가하는 가장 좋은 방법은 무엇입니까? 끝 문자열은 “이 문자열에는 공백이 없지만 대문자가 있습니다”입니다.
다음은 RegEx를 사용한 시도입니다.
System.Text.RegularExpressions.Regex.Replace(value, "[A-Z]", " $0")
답변
정규 표현식이 제대로 작동하지만 (Martin Browns 답변에 투표하기까지 했음) 비용이 많이 듭니다.
이 기능
string AddSpacesToSentence(string text, bool preserveAcronyms)
{
if (string.IsNullOrWhiteSpace(text))
return string.Empty;
StringBuilder newText = new StringBuilder(text.Length * 2);
newText.Append(text[0]);
for (int i = 1; i < text.Length; i++)
{
if (char.IsUpper(text[i]))
if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) ||
(preserveAcronyms && char.IsUpper(text[i - 1]) &&
i < text.Length - 1 && !char.IsUpper(text[i + 1])))
newText.Append(' ');
newText.Append(text[i]);
}
return newText.ToString();
}
2,968,750 틱으로 100,000 번, 정규식은 25,000,000 틱 (및 정규식이 컴파일 된 틱)이 걸립니다.
주어진 값이 더 좋을수록 (즉 더 빠름) 더 낫지 만 유지해야 할 코드는 더 많습니다. “더 나은”은 종종 경쟁 요구 사항을 타협합니다.
도움이 되었기를 바랍니다 🙂
업데이트
이것을 살펴본 후 오랜 시간이 걸렸으며 코드가 변경된 후 타이밍이 업데이트되지 않았다는 것을 깨달았습니다.
‘Abbbbbbbbb’이 100 번 반복 된 문자열 (즉, 1,000 바이트)이있는 문자열에서 100,000 회의 변환은 수작업으로 코딩 된 함수 4,517,177 틱을 취하고 아래의 정규식은 59,435,719를 수행하여 Hand coded 함수를 수행하는 시간의 7.6 %에서 실행합니다. 정규식.
업데이트 2
약어를 고려합니까? 지금입니다! if 문의 논리는 상당히 불분명합니다.
if (char.IsUpper(text[i]))
if (char.IsUpper(text[i - 1]))
if (preserveAcronyms && i < text.Length - 1 && !char.IsUpper(text[i + 1]))
newText.Append(' ');
else ;
else if (text[i - 1] != ' ')
newText.Append(' ');
… 전혀 도움이되지 않습니다!
약어에 대해 걱정하지 않는 원래 간단한 방법은 다음과 같습니다.
string AddSpacesToSentence(string text)
{
if (string.IsNullOrWhiteSpace(text))
return "";
StringBuilder newText = new StringBuilder(text.Length * 2);
newText.Append(text[0]);
for (int i = 1; i < text.Length; i++)
{
if (char.IsUpper(text[i]) && text[i - 1] != ' ')
newText.Append(' ');
newText.Append(text[i]);
}
return newText.ToString();
}
답변
솔루션에 첫 번째 문자 T 앞에 공백을 넣는 문제가 있으므로
" This String..." instead of "This String..."
이 문제를 해결하려면 앞에 소문자를 찾은 다음 중간에 공백을 삽입하십시오.
newValue = Regex.Replace(value, "([a-z])([A-Z])", "$1 $2");
편집 1 :
사용 @"(\p{Ll})(\p{Lu})"
하면 악센트 부호가있는 문자도 선택됩니다.
편집 2 :
문자열에 두문자어가 포함될 수있는 경우 다음을 사용할 수 있습니다.
newValue = Regex.Replace(value, @"((?<=\p{Ll})\p{Lu})|((?!\A)\p{Lu}(?>\p{Ll}))", " $0");
따라서 “DriveIsSCSICompatible”은 “Drive Is SCSI Compatible”이됩니다.
답변
성능을 테스트하지는 않았지만 linq와 함께 한 줄로 표시합니다.
var val = "ThisIsAStringToTest";
val = string.Concat(val.Select(x => Char.IsUpper(x) ? " " + x : x.ToString())).TrimStart(' ');
답변
나는 이것이 오래된 것임을 알고 있지만, 이것을해야 할 때 사용하는 확장입니다.
public static class Extensions
{
public static string ToSentence( this string Input )
{
return new string(Input.SelectMany((c, i) => i > 0 && char.IsUpper(c) ? new[] { ' ', c } : new[] { c }).ToArray());
}
}
이것은 당신이 사용할 수 있습니다 MyCasedString.ToSentence()
답변
이진 걱정의 코드를 기반으로 간단한 확장 방법을 사용하여 약어를 올바르게 처리하고 반복 할 수 있습니다 (이미 단어 간격이 엉망이 아닙니다). 여기 내 결과가 있습니다.
public static string UnPascalCase(this string text)
{
if (string.IsNullOrWhiteSpace(text))
return "";
var newText = new StringBuilder(text.Length * 2);
newText.Append(text[0]);
for (int i = 1; i < text.Length; i++)
{
var currentUpper = char.IsUpper(text[i]);
var prevUpper = char.IsUpper(text[i - 1]);
var nextUpper = (text.Length > i + 1) ? char.IsUpper(text[i + 1]) || char.IsWhiteSpace(text[i + 1]): prevUpper;
var spaceExists = char.IsWhiteSpace(text[i - 1]);
if (currentUpper && !spaceExists && (!nextUpper || !prevUpper))
newText.Append(' ');
newText.Append(text[i]);
}
return newText.ToString();
}
이 함수가 통과하는 단위 테스트 사례는 다음과 같습니다. tchrist가 제안한 사례의 대부분을이 목록에 추가했습니다. 통과하지 못하는 세 가지 (두 개는 로마 숫자 일뿐입니다)는 주석 처리됩니다.
Assert.AreEqual("For You And I", "ForYouAndI".UnPascalCase());
Assert.AreEqual("For You And The FBI", "ForYouAndTheFBI".UnPascalCase());
Assert.AreEqual("A Man A Plan A Canal Panama", "AManAPlanACanalPanama".UnPascalCase());
Assert.AreEqual("DNS Server", "DNSServer".UnPascalCase());
Assert.AreEqual("For You And I", "For You And I".UnPascalCase());
Assert.AreEqual("Mount Mᶜ Kinley National Park", "MountMᶜKinleyNationalPark".UnPascalCase());
Assert.AreEqual("El Álamo Tejano", "ElÁlamoTejano".UnPascalCase());
Assert.AreEqual("The Ævar Arnfjörð Bjarmason", "TheÆvarArnfjörðBjarmason".UnPascalCase());
Assert.AreEqual("Il Caffè Macchiato", "IlCaffèMacchiato".UnPascalCase());
//Assert.AreEqual("Mister Dženan Ljubović", "MisterDženanLjubović".UnPascalCase());
//Assert.AreEqual("Ole King Henry Ⅷ", "OleKingHenryⅧ".UnPascalCase());
//Assert.AreEqual("Carlos Ⅴº El Emperador", "CarlosⅤºElEmperador".UnPascalCase());
Assert.AreEqual("For You And The FBI", "For You And The FBI".UnPascalCase());
Assert.AreEqual("A Man A Plan A Canal Panama", "A Man A Plan A Canal Panama".UnPascalCase());
Assert.AreEqual("DNS Server", "DNS Server".UnPascalCase());
Assert.AreEqual("Mount Mᶜ Kinley National Park", "Mount Mᶜ Kinley National Park".UnPascalCase());
답변
유니 코드에 오신 것을 환영합니다
이 모든 솔루션은 현대 텍스트에는 본질적으로 잘못되었습니다. 대소 문자를 이해하는 것을 사용해야합니다. Bob이 다른 언어를 요청했기 때문에 Perl에게 몇 가지를 드리겠습니다.
최악에서 최고에 이르는 네 가지 솔루션을 제공합니다. 가장 좋은 것만 항상 옳습니다. 다른 사람들에게는 문제가 있습니다. 다음은 작동하는 것과 작동하지 않는 위치 및 위치를 보여주는 테스트 실행입니다. 공백을 넣은 위치를 볼 수 있도록 밑줄을 사용했으며, 잘못된 것으로 잘못 표시했습니다.
Testing TheLoneRanger
Worst: The_Lone_Ranger
Ok: The_Lone_Ranger
Better: The_Lone_Ranger
Best: The_Lone_Ranger
Testing MountMᶜKinleyNationalPark
[WRONG] Worst: Mount_MᶜKinley_National_Park
[WRONG] Ok: Mount_MᶜKinley_National_Park
[WRONG] Better: Mount_MᶜKinley_National_Park
Best: Mount_Mᶜ_Kinley_National_Park
Testing ElÁlamoTejano
[WRONG] Worst: ElÁlamo_Tejano
Ok: El_Álamo_Tejano
Better: El_Álamo_Tejano
Best: El_Álamo_Tejano
Testing TheÆvarArnfjörðBjarmason
[WRONG] Worst: TheÆvar_ArnfjörðBjarmason
Ok: The_Ævar_Arnfjörð_Bjarmason
Better: The_Ævar_Arnfjörð_Bjarmason
Best: The_Ævar_Arnfjörð_Bjarmason
Testing IlCaffèMacchiato
[WRONG] Worst: Il_CaffèMacchiato
Ok: Il_Caffè_Macchiato
Better: Il_Caffè_Macchiato
Best: Il_Caffè_Macchiato
Testing MisterDženanLjubović
[WRONG] Worst: MisterDženanLjubović
[WRONG] Ok: MisterDženanLjubović
Better: Mister_Dženan_Ljubović
Best: Mister_Dženan_Ljubović
Testing OleKingHenryⅧ
[WRONG] Worst: Ole_King_HenryⅧ
[WRONG] Ok: Ole_King_HenryⅧ
[WRONG] Better: Ole_King_HenryⅧ
Best: Ole_King_Henry_Ⅷ
Testing CarlosⅤºElEmperador
[WRONG] Worst: CarlosⅤºEl_Emperador
[WRONG] Ok: CarlosⅤº_El_Emperador
[WRONG] Better: CarlosⅤº_El_Emperador
Best: Carlos_Ⅴº_El_Emperador
BTW, 여기에있는 거의 모든 사람들이 “Worst”라고 표시된 첫 번째 방법을 선택했습니다. “OK”라고 표시된 두 번째 방법을 선택한 사람이 있습니다. 그러나 저보다 먼저 “더 나은”또는 “최상의”접근 방식을 수행하는 방법을 보여준 사람은 없습니다.
다음은 네 가지 방법으로 테스트 프로그램입니다.
#!/usr/bin/env perl
use utf8;
use strict;
use warnings;
# First I'll prove these are fine variable names:
my (
$TheLoneRanger ,
$MountMᶜKinleyNationalPark ,
$ElÁlamoTejano ,
$TheÆvarArnfjörðBjarmason ,
$IlCaffèMacchiato ,
$MisterDženanLjubović ,
$OleKingHenryⅧ ,
$CarlosⅤºElEmperador ,
);
# Now I'll load up some string with those values in them:
my @strings = qw{
TheLoneRanger
MountMᶜKinleyNationalPark
ElÁlamoTejano
TheÆvarArnfjörðBjarmason
IlCaffèMacchiato
MisterDženanLjubović
OleKingHenryⅧ
CarlosⅤºElEmperador
};
my($new, $best, $ok);
my $mask = " %10s %-8s %s\n";
for my $old (@strings) {
print "Testing $old\n";
($best = $old) =~ s/(?<=\p{Lowercase})(?=[\p{Uppercase}\p{Lt}])/_/g;
($new = $old) =~ s/(?<=[a-z])(?=[A-Z])/_/g;
$ok = ($new ne $best) && "[WRONG]";
printf $mask, $ok, "Worst:", $new;
($new = $old) =~ s/(?<=\p{Ll})(?=\p{Lu})/_/g;
$ok = ($new ne $best) && "[WRONG]";
printf $mask, $ok, "Ok:", $new;
($new = $old) =~ s/(?<=\p{Ll})(?=[\p{Lu}\p{Lt}])/_/g;
$ok = ($new ne $best) && "[WRONG]";
printf $mask, $ok, "Better:", $new;
($new = $old) =~ s/(?<=\p{Lowercase})(?=[\p{Uppercase}\p{Lt}])/_/g;
$ok = ($new ne $best) && "[WRONG]";
printf $mask, $ok, "Best:", $new;
}
이 데이터 세트에서 “최고”와 동일한 점수를 매길 수 있으면 올바르게 수행 한 것입니다. 그때까지는 그렇지 않았습니다. 여기서 다른 어느 누구도 “Ok”보다 나은 작업을 수행하지 않았으며 대부분 “Worst”를 수행했습니다. 누군가 올바른 ℂ♯ 코드를 게시하기를 기대합니다.
StackOverflow의 강조 표시 코드가 다시 비참하게 다루어졌습니다. 그들은 여기에 언급 된 나머지 가난한 접근 방식과 거의 같은 오래된 절름발이를 만들고 있습니다. ASCII를 쉬게하는 데 시간이 오래 걸리지 않습니까? 더 이상 말이되지 않으며, 당신이 가진 전부인 척하는 것은 단순히 잘못입니다. 코드가 잘못되었습니다.
답변
이진 걱정, 나는 당신의 제안 된 코드를 사용했으며, 약간 좋습니다. 단지 하나의 추가 사항이 있습니다.
public static string AddSpacesToSentence(string text)
{
if (string.IsNullOrEmpty(text))
return "";
StringBuilder newText = new StringBuilder(text.Length * 2);
newText.Append(text[0]);
for (int i = 1; i < result.Length; i++)
{
if (char.IsUpper(result[i]) && !char.IsUpper(result[i - 1]))
{
newText.Append(' ');
}
else if (i < result.Length)
{
if (char.IsUpper(result[i]) && !char.IsUpper(result[i + 1]))
newText.Append(' ');
}
newText.Append(result[i]);
}
return newText.ToString();
}
조건을 추가했습니다 !char.IsUpper(text[i - 1])
. 이로 인해 ‘AverageNOX’와 같은 것이 ‘Average NOX’로 바뀌는 버그가 수정되었습니다. ‘Average NOX’를 읽어야하므로 분명히 잘못되었습니다.
슬프게도 여전히 ‘FromAStart’라는 텍스트가 있으면 ‘From AStart’가 표시되는 버그가 있습니다.
이것을 고치는 것에 대한 생각이 있습니까?
