2010-03-21 3 views
5

다음 변환을 수행하는 간결한 방법이 필요합니다. 나는 노래 가사를 변형시키고 싶다. 입력은 다음과 같이 보일 것이다 :LINQ의 까다로운 문자열 변환

Verse 1 lyrics line 1 
Verse 1 lyrics line 2 
Verse 1 lyrics line 3 
Verse 1 lyrics line 4 

Verse 2 lyrics line 1 
Verse 2 lyrics line 2 
Verse 2 lyrics line 3 
Verse 2 lyrics line 4 

을 그리고 각 구절의 첫 번째 줄은 같이 그룹화되도록 그들을 변환 할 :

Verse 1 lyrics line 1 
Verse 2 lyrics line 1 

Verse 1 lyrics line 2 
Verse 2 lyrics line 2 

Verse 1 lyrics line 3 
Verse 2 lyrics line 3 

Verse 1 lyrics line 4 
Verse 2 lyrics line 4 

가사 분명히 알 수 있지만 빈 선은 입력에서 절 사이의 구분을 표시합니다.

답변

3

저는이 유형의 처리를 매우 간단하게 만드는 몇 가지 확장 방법을 항상 가지고 있습니다. 솔루션 전체가 다른 것보다 길어질 것입니다. 그러나 이것들은 가지고 다니기에 유용한 방법이며 일단 확장 메소드를 사용하면 대답은 매우 짧고 읽기 쉽습니다.

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items, 
    Predicate<T> splitCondition) 
{ 
    using (IEnumerator<T> enumerator = items.GetEnumerator()) 
    { 
     while (enumerator.MoveNext()) 
     { 
      yield return GetNextItems(enumerator, splitCondition).ToArray(); 
     } 
    } 
} 

private static IEnumerable<T> GetNextItems<T>(IEnumerator<T> enumerator, 
    Predicate<T> stopCondition) 
{ 
    do 
    { 
     T item = enumerator.Current; 
     if (stopCondition(item)) 
     { 
      yield break; 
     } 
     yield return item; 
    } while (enumerator.MoveNext()); 
} 
:

public static class EnumerableExtensions 
{ 
    public static IEnumerable<T> Zip<T>(
     this IEnumerable<IEnumerable<T>> sequences, 
     Func<IEnumerable<T>, T> aggregate) 
    { 
     var enumerators = sequences.Select(s => s.GetEnumerator()).ToArray(); 
     try 
     { 
      while (enumerators.All(e => e.MoveNext())) 
      { 

       var items = enumerators.Select(e => e.Current); 
       yield return aggregate(items); 
      } 
     } 
     finally 
     { 
      foreach (var enumerator in enumerators) 
      { 
       enumerator.Dispose(); 
      } 
     } 
    } 
} 

다음 string.Split 문자열로 수행한다는 IEnumerable<T>에 거의 같은 일을하는 분할 방법이있다 :

우선, 시퀀스의 임의의 수를 소요 우편 방법있다

이러한 확장자가 있으면 노래 가사 문제를 해결하면 케이크 한 조각이됩니다.

string lyrics = ... 
var verseGroups = lyrics 
    .Split(new[] { Environment.NewLine }, StringSplitOptions.None) 
    .Select(s => s.Trim()) // Optional, if there might be whitespace 
    .Split(s => string.IsNullOrEmpty(s)) 
    .Zip(seq => string.Join(Environment.NewLine, seq.ToArray())) 
    .Select(s => s + Environment.NewLine); // Optional, add space between groups 
+0

매우 편리한 ZIP 방식! – Larsenal

0

하나의 큰 문자열로 입력하십시오. 그런 다음 구절의 줄 수를 결정하십시오.

.Split을 사용하여 문자열 배열을 가져 오면 각 항목이 이제 한 줄로 구성됩니다. 그런 다음 줄 수를 반복하고 stringbuilder를 사용하여 SplitStrArray (i) 및 SplitStrArray (i + 줄을 한 절에 추가)를 추가합니다.

나는 이것이 최선의 방법이라고 생각합니다. 나는 LINQ가 굉장하지 않다는 것을 말하는 것이 아니라, '나는 문제가 있으며 그것을 해결하기 위해이 도구를 사용하고 싶다'라고 말하는 것은 어리석은 것처럼 보입니다.

"벽에 나사를 끼워야합니다.하지만 망치를 사용하고 싶습니다." 당신이 결정된다면 아마도 망치를 사용할 수있는 방법을 발견 할 것입니다. 하지만 IMHO, 그건 최선의 행동 방식이 아닙니다. 어쩌면 다른 누군가가 정말 멋진 LINQ 예제를 사용하여 쉽게 쉽게 만들 수 있으며 이것을 게시하는 데 어리석은 짓을하게 될 것입니다 ....

+0

예, 절차 적으로 이렇게하면 쉽습니다. 이것이 중요하지 않은 "주말 코드"이므로 LINQ 한 줄 짜기에서이 작업을 수행 할 수있는 방법이 있는지 궁금합니다. – Larsenal

+0

Linq는 이것을위한 좋은 도구가 아니라, 필요한 특정 변환이 표준 Linq 라이브러리의 일부가 아니라는 것입니다. 'Split' 메쏘드와'Zip' 메쏘드가 필요합니다. 어느 쪽도 표준은 아니지만 둘 다 쓰기 쉽습니다. – Aaronaught

+3

Zip이 .NET 4 (http://msdn.microsoft.com/en-us/library/dd267698%28VS.100%29.aspx)에 추가됩니다. –

1

이 작업을 수행하는 데 더 간결한 방법이있을 수 있지만 여기서는 주어진 해결 방법이 있습니다. 유효한 입력 :

 var output = String.Join("\r\n\r\n", // join it all in the end 
     Regex.Split(input, "\r\n\r\n") // split on blank lines 
      .Select(v => Regex.Split(v, "\r\n")) // now split lines in each verse 
      .SelectMany(vl => vl.Select((lyrics, i) => new { Line = i, Lyrics = lyrics })) // flatten things out, but attach line number 
      .GroupBy(b => b.Line).Select(c => new { Key = c.Key, Value = c }) // group by line number 
      .Select(e => String.Join("\r\n", e.Value.Select(f => f.Lyrics).ToArray())).ToArray()); 

분명히 이것은 꽤 못생긴 것입니다. 전혀 프로덕션 코드에 대한 제안이 아닙니다.

0

시도해보십시오. Regex.Split은 여분의 빈 항목을 방지하는 데 사용됩니다String.SplitArray.FindIndex 메소드의 도움으로 첫 번째 빈 줄이 나타나는 위치를 결정하는 데 사용할 수 있습니다. 이것은 각 빈 줄 사이에서 사용할 수있는 구절의 수를 나타냅니다 (형식이 일관된 경우). 다음으로, 빈 라인을 필터링하여 각 라인의 인덱스를 결정하고 앞서 언급 한 인덱스의 모듈러스로 그룹화합니다.

string input = @"Verse 1 lyrics line 1 
Verse 1 lyrics line 2 
Verse 1 lyrics line 3 
Verse 1 lyrics line 4 
Verse 1 lyrics line 5 

Verse 2 lyrics line 1 
Verse 2 lyrics line 2 
Verse 2 lyrics line 3 
Verse 2 lyrics line 4 
Verse 2 lyrics line 5 

Verse 3 lyrics line 1 
Verse 3 lyrics line 2 
Verse 3 lyrics line 3 
Verse 3 lyrics line 4 
Verse 3 lyrics line 5 
"; 

// commented original Regex.Split approach 
//var split = Regex.Split(input, Environment.NewLine); 
var split = input.Split(new[] { Environment.NewLine }, StringSplitOptions.None); 
// find first blank line to determine # of verses 
int index = Array.FindIndex(split, s => s == ""); 
var result = split.Where(s => s != "") 
        .Select((s, i) => new { Value = s, Index = i }) 
        .GroupBy(item => item.Index % index); 

foreach (var group in result) 
{ 
    foreach (var item in group) 
    { 
     Console.WriteLine(item.Value); 
    }   
    Console.WriteLine(); 
} 
+0

그들은 정말로 트리밍해야합니다. 예를 들어 모든 가사를 나열했기 때문에 트리밍해야합니다. 가장자리로 밀어 넣으면 트리밍이 더 이상 필요하지 않습니다. 입력에 따라 달라집니다. 텍스트 파일이 다시 문제가되지 않을 것입니다. 일반적으로 .Trim()을 사용하여 문자열이 "깨끗한"것을 확인합니다. –

+0

@Matthew는 피드백에 감사드립니다. 처음에는 Regex.Split 및 보통의 'Split'을 사용할 때 빈 라인을 사용하는 것처럼 보였습니다. 그걸 재현하고 무슨 일이 일어 났는지 알아 내려고 노력했습니다. –

+0

빈 라인에 공백이나 탭이있을 가능성이 있습니까? ~에 의해 사고? 그래서 내가 일반적으로 .Trim()을 사용하여 비어 있는지 확인하기 때문입니다. 당신이 볼 수없는 성가신 버그를 해결하는 데 도움이됩니다. –

1

LINQ는 너무 달콤합니다 ... 나는 그것을 좋아합니다.

static void Main(string[] args) 
{ 
    var lyrics = @"Verse 1 lyrics line 1 
        Verse 1 lyrics line 2 
        Verse 1 lyrics line 3 
        Verse 1 lyrics line 4 

        Verse 2 lyrics line 1 
        Verse 2 lyrics line 2 
        Verse 2 lyrics line 3 
        Verse 2 lyrics line 4"; 
    var x = 0; 
    var indexed = from lyric in lyrics.Split(new[] { Environment.NewLine }, 
              StringSplitOptions.None) 
        let line = lyric.Trim() 
        let indx = line == string.Empty ? x = 0: ++x 
        where line != string.Empty 
        group line by indx; 

    foreach (var trans in indexed) 
    { 
     foreach (var item in trans) 
      Console.WriteLine(item); 
     Console.WriteLine(); 
    } 
    /* 
     Verse 1 lyrics line 1 
     Verse 2 lyrics line 1 

     Verse 1 lyrics line 2 
     Verse 2 lyrics line 2 

     Verse 1 lyrics line 3 
     Verse 2 lyrics line 3 

     Verse 1 lyrics line 4 
     Verse 2 lyrics line 4 
    */ 
} 
+5

LINQ 표현 내부 mutant 상태 ('+ X를') 그것은 특정 순서로 처리하기 때문에 좋은 스타일이 아닙니다. 여기서는 작동하지만, 예를 들어 Split 뒤에'.AsParallel() '을 넣으면 작동하지 않을 수 있습니다. – Gabe

+0

"하지 말아야 할"많은 일들이 있지만, 실제로 그 일을하는 가장 쉬운 방법이기 때문에 어쨌든 끝내야합니다. 모든 예제는 알려진 처리 순서를 필요로하기 때문에 멀티 스레딩의 "마법"버전에 문제가 있습니다. 프로그래머와 엔지니어가 이해하고 기대해야 할 것이 있습니다. 때때로 희생을해야합니다. 내 문제가있는 경우 나 자신의 예를 자유롭게 작성하십시오. –