2013-05-23 14 views
5

Mam string[], który zawiera kod. Każda linia zawiera kilka początkowych spacji. Muszę "cofnąć" kod w miarę możliwości bez zmiany istniejącego formatowania.Efektywny sposób na cofanie linii kodu przechowywanego w ciągu znaków

Na przykład zawartość mojego string[] może być

           public class MyClass 
             { 
              private bool MyMethod(string s) 
              { 
               return s == ""; 
              } 
             } 

Chciałbym znaleźć dość elegancki i wydajny sposób (LINQ?), Aby przekształcić go do

public class MyClass 
{ 
    private bool MyMethod(string s) 
    { 
     return s == ""; 
    } 
}

Aby być jasne Szukam

IEnumerable<string> UnindentAsMuchAsPossible(string[] content) 
{ 
    return ???; 
} 
+0

Czy jesteś pewien, że wszystkie przestrzenie, i nie ma żadnych zakładek? – Servy

+2

Nie chcesz, aby 'UnindentAsMuchAsPossible' mógł" zwracać "' void', prawda? –

+0

Masz rację @Tim. Naprawiony. – shamp00

Odpowiedz

3

Opierając się na Tima Schmelter za odpowiedź:

static IEnumerable<string> UnindentAsMuchAsPossible(IEnumerable<string> input) 
{ 
    const int TabWidth = 4; 

    if (!input.Any()) 
    { 
     return Enumerable.Empty<string>(); 
    } 

    int minDistance = input 
     .Where(line => line.Length > 0) 
     .Min(line => line 
      .TakeWhile(Char.IsWhiteSpace) 
      .Sum(c => c == '\t' ? TabWidth : 1)); 

    return input 
     .Select(line => line.Replace("\t", new string(' ', TabWidth))) 
     .Select(line => line.Substring(Math.Min(l.Length, minDistance)); 
} 

ta obsługuje:

  • znaki Tab
  • kod
  • źródło, które zawiera pustych wierszy
+0

Okazało się, że mój przypadek użycia ma zarówno tabulatory, jak i puste linie. Ta odpowiedź idzie o krok dalej. – shamp00

1

Najpierw znajdę minimalną ident, a następnie usunę t liczba spacji dla każdej linii.

var code = new [] { " foo", " bar" }; 

var minIndent = code.Select(line => line.TakeWhile(ch => ch == ' ').Count()).Min(); 
var formatted = code.Select(line => line.Remove(0, minIndent)); 

To byłoby możliwe, aby napisać wszystko w jednej wypowiedzi, ale jednocześnie jest to bardziej funkcjonalnie elegancki myślę, że zmienna minIndent sprawia, że ​​kod jest bardziej czytelny.

3

Wystarczy policzyć czołowych miejsc na pierwszej linii, a następnie „usuń”, że wiele znaków od początku każdej linii:

IEnumerable<string> UnindentAsMuchAsPossible(string[] content) 
{ 
    int spacesOnFirstLine = content[0].TakeWhile(c => c == ' ').Count(); 
    return content.Select(line => line.Substring(spacesOnFirstLine)); 
} 
+1

Zakłada to, że pierwszy wiersz ma najmniej wcięty (co może nie być prawdą). –

+0

@MattHouser Jeśli program jest poprawnie sformatowany, aby zacząć od tego, w jaki sposób może nie być? Czy masz przykład takiego programu? – Servy

+0

Gdzie w pierwotnym pytaniu jest napisane, że kod źródłowy jest odpowiednio sformatowany? Pytanie tylko stwierdza, że ​​istniejące formatowanie ma zostać zachowane. –

2

użyć trochę LINQ i regex znaleźć najkrótszą wcięcia, następnie usuń tę liczbę znaków ze wszystkich linii.

string[] l_lines = { 
         "           public class MyClass", 
         "           {", 
         "            private bool MyMethod(string s)", 
         "            {", 
         "             return s == \"\";", 
         "            }", 
         "           }" 
        }; 

int l_smallestIndentation = 
    l_lines.Min(s => Regex.Match(s, "^\\s*").Value.Length); 

string[] l_result = 
    l_lines.Select(s => s.Substring(l_smallestIndentation)) 
      .ToArray(); 

foreach (string l_line in l_result) 
    Console.WriteLine(l_line); 

Wydruki:

public class MyClass 
{ 
    private bool MyMethod(string s) 
    { 
     return s == ""; 
    } 
} 

Program ten skanuje wszystkie sznurki w tablicy. Jeśli można założyć, że pierwsza linia jest najmniej wcięty, a następnie można zwiększyć wydajność poprzez skanowanie tylko pierwszy wiersz:

int l_smallestIndentation = 
    Regex.Match(l_lines[0], "^\\s*").Value.Length; 

Należy również pamiętać, że to będzie obsługiwać znak tabulatora ("\t") jako pojedynczego znaku. Jeśli występuje mieszanka zakładek i spacji, cofnięcie wcięcia może być trudne. Najprostszym sposobem na obsłużenie tego byłoby zastąpienie wszystkich instancji kart z odpowiednią liczbą spacji (często 4, chociaż poszczególne aplikacje mogą się znacznie różnić) przed uruchomieniem powyższego kodu.

Byłoby również możliwe zmodyfikowanie powyższego kodu w celu nadania dodatkowej wagi zakładkom. W tym momencie wyrażenie regularne nie jest już zbyt użyteczne.

string[] l_lines = { 
     "\t\t\tpublic class MyClass", 
     "      {", 
     "        private bool MyMethod(string s)", 
     "        {", 
     "  \t  \t\treturn s == \"\";", 
     "        }", 
     "\t\t\t}" 
    }; 

int l_tabWeight = 8; 
int l_smallestIndentation = 
    l_lines.Min 
    (
     s => s.ToCharArray() 
       .TakeWhile(c => Char.IsWhiteSpace(c)) 
       .Select(c => c == '\t' ? l_tabWeight : 1) 
       .Sum() 
    ); 

string[] l_result = 
    l_lines.Select 
    (
     s => 
     { 
      int l_whitespaceToRemove = l_smallestIndentation; 
      while (l_whitespaceToRemove > 0) 
      { 
       l_whitespaceToRemove -= s[0] == '\t' ? l_tabWeight : 1; 
       s = s.Substring(1); 
      } 
      return s; 
     } 
    ).ToArray(); 

nadrukami (zakładając swoją konsolę ma szerokość zakładki z 8 jak ja):

public class MyClass 
{ 
     private bool MyMethod(string s) 
     { 
       return s == ""; 
     } 
} 

może trzeba zmodyfikować ten kod do pracy ze scenariuszami krawędzi sprawy, takich jak zero- linie długości lub linie zawierające tylko białe spacje.

3

To powinno działać:

static IEnumerable<string> UnindentAsMuchAsPossible(IEnumerable<string> input) 
{ 
    int minDistance = input.Min(l => l.TakeWhile(Char.IsWhiteSpace).Count()); 
    return input.Select(l => l.Substring(minDistance)); 
} 

Porusza kod w lewo, wszystkie linie z tej samej ilości przestrzeni.

Na przykład:

string testString = @"  
        public class MyClass 
        { 
         private bool MyMethod(string s) 
         { 
          return s == ""; 
         } 
        }"; 


string[] lines = testString.Split(new[] { Environment.NewLine }, StringSplitOptions.None); 
string[] unindentedArray = UnindentAsMuchAsPossible(lines).ToArray(); 
+2

Dokładnie to, co myślałem. W zależności od tego, co OP chce, możesz również poprawić to poprzez zmianę tego: 'l.TakeWhile (Char.IsWhiteSpace) .Count()' na to: 'l.TakeWhile (Char.IsWhiteSpace) .Sum (c => c == '\ t'? TabWidth: 1) 'gdzie' TabWidth' jest podobne do 4. –

+1

Kolejnym ulepszeniem będzie obsługa pustych linii. Zazwyczaj, jeśli ktoś pisze kod, który ma pustą linię, pusty wiersz zawiera tylko "\ r \ n". Ten kod sobie z tym nie poradzi. –

1

Aby dopasować żądaną interfejs Metoda:

IEnumerable<string> UnindentAsMuchAsPossible(string[] content) 
{ 
    int minIndent = content.Select(s => s.TakeWhile(c => c == ' ').Count()).Min(); 
    return content.Select(s => s.Substring(minIndent)).AsEnumerable(); 
} 

Pobiera minimalną wcięcie wszystkich linii (przyjęto, że przestrzeń, tylko nie karty), a następnie Strips minIndent przestrzenie z początek każdej linii i zwraca ją jako IEnumerable.

Powiązane problemy