2015-04-24 4 views
19

Hier ist was ich versuche, insgesamt zu tun. Nur um klar zu sein, das ist keine Hausaufgabe oder für einen Wettbewerb oder irgendetwas. Hoffentlich habe ich den Wortlaut klar genug gemacht:Gibt es eine elegante LINQ-Lösung für SomeButNotAll()?

Problem

ein Satz von Zeichenketten im gleichen Format gegeben, aber wo einige Ende in einem Kleinbuchstaben und einige nicht, geben einen Satz von einem jeder Zeichenfolge, die nicht in einem Kleinbuchstaben endet, aber mindestens eine identische Zeichenfolge hat, die in einem Kleinbuchstaben endet.

Beispiel

es einfach zu halten, lassen Sie uns das String-Format \d+[a-z]? ist sagen, wo der gemeinsame Teil der Zahl ist. Gegeben {1, 4, 3a, 1b, 3, 6c}, sollte ich eine Permutation von {1, 3} erhalten, weil 1 und 3 am Ende beide ein Element mit und ohne einen Kleinbuchstaben haben.

Alternative Lösung

Sie können view this solution here.

Ein Weg, ich dachte, dies zu tun, die Menge in Elemente mit und ohne Kleinbuchstaben Suffix ({1, 4, 3} und {3a, 1b, 6c}) und kehrt dann withoutSuffix.Where(x => withSuffix.Any(y => y.StartsWith(x))) zu partitionieren war.

Ich habe zwei Probleme:

  1. ich keinen guten Weg, sehe in die beiden Sätze zu partitionieren, mit dem Prädikat Regex.IsMatch(input, "[a-z]$"). Die beiden, an die ich dachte, waren zwei ähnlich definierte Variablen, die jeweils eine Where-Klausel verwenden und die Regex-Übereinstimmung zweimal pro Element ausführen oder die Menge transformieren, um die Regex-Übereinstimmungsergebnisse zu speichern und daraus die beiden Variablen zu bilden. group...by scheint nicht gut zu spielen, wenn Sie auf beide Sets zugreifen müssen, aber ich könnte mich da irren.

  2. Obwohl die Größe klein genug ist, um sich nicht um die Leistung zu kümmern, scheint withSuffix einmal pro withoutSuffix Element unelegant.

Relevante Lösung

Sie können view this solution here.

Die andere Art und Weise, die in den Sinn kam, war das gemeinsame Präfix und das optionale Suffix zu greifen: {1 => {"", b}, 3 => {a, ""}, 4 => {""}, 6 => {c}}. Dies wird einfach erreicht, indem das Präfix und das Suffix mit einer Regex ((\d+)([a-z])?) erfasst und das Suffix durch das Präfix in grouped gruppiert wird.

Von hier aus wäre es toll, zu tun:

where grouped.SomeButNotAll(x => x == string.Empty) 
select grouped.Key 

Oder auch:

where grouped.ContainsSomeButNotAll(string.Empty) 
select grouped.Key 

ich eine dieser sicherlich schaffen könnte, aber leider das Beste, was ich in LINQ zu sehen ist :

where grouped.Contains(string.Empty) && grouped.Any(x => x != string.Empty) 
select grouped.Key 

Es fühlt sich einfach super redundant an. Gibt es in LINQ schon etwas Besseres?

P.S. Ich bin offen für bessere Ansätze zur Lösung des Gesamtproblems, anstatt dies zu einem XY-Problem zu machen. Eleganz ist viel mehr als Leistung erwünscht, aber (vielleicht ist es nur ich), einfach verschwenderisch zu sein scheint immer noch unelegant.

Antwort

4

Ich glaube nicht, dass Sie wirklich dafür regexen müssen. Hier ist, wie ich es tun würde:

var withEndings = new HashSet<string>(); 
var withoutEndings = new HashSet<string>(); 

foreach (var s in input) 
    if(char.IsLower(s[s.Length - 1])) 
     withEndings.Add(s.Substring(0, s.Length - 1)); 
    else 
     withoutEndings.Add(s); 

var result = withEndings.Intersect(withoutEndings); 
+0

Gute Antwort. Eventuell möchten Sie das Ergebnis jedoch am Ende deduplizieren. – moarboilerplate

+1

@moarboilerplate Ich bin mir nicht sicher, was du meinst. Sets haben einzigartige Elemente. –

+0

Das lässt mich fast wünschen, dass es eine Art "PartitionSelect" gibt. 'var Partitionen = input.PartitionSelect (s => char.IsLower (s.Last()), s => s.Substring (0, s.Length - 1), s => s);', mit 'Partitionen. TrueValues.Intersect (partitions.FalseValues); '(obwohl ich davon keine Hash-Sets erwarten würde). – chris

3

Sie könnten eine andere Gruppierung von string.IsNullOrEmpty hinzufügen und überprüfen, dass es zwei Gruppen hat (eine für false und eine für true):

return 
    from str in strs 
    let match = Regex.Match(str, STR_FORMAT) 
    group match.Groups[2].Value by match.Groups[1].Value into parts 
    where (parts.GroupBy(string.IsNullOrEmpty).Count() == 2) 
    select parts.Key; 
-1

Sie können .Any()-!All() ändern.

Ich würde lieber die Überladung Count mit einem Prädikat verwenden und mit der Gesamtanzahl vergleichen. Das wird wahrscheinlich am saubersten sein und Sie müssen sich nicht um Ausnahmen kümmern, die aus leeren Sammlungen stammen.

+0

'! All' wird in diesem Szenario nicht funktionieren. – MarcinJuraszek

+0

Komisch, Sie sollten die Zählmethode erwähnen, da ich 'grouping.Count (x => x == string.Empty) chris

+0

@MarcinJuraszek 'gruppiert.Konturen (string.Empty) &&! Gruppiert.Alle (x => x == string.Empty)' – moarboilerplate

1

In diesem Fall denke ich, die performante Lösung nicht unbedingt sehr elegant in LINQ ist. Ich denke, das sollte tun, was Sie wollen und es O (N) Laufzeit machen.

values 
.Aggregate(
new { HashSet1 = new HashSet<string>(), HashSet2 = new HashSet<string>() }, 
(a, x) => 
{ 
    // If the last character is a lowercase letter then put the string 
    // (minus the last character) in HashSet1, otherwise, put the string 
    // in HashSet2 
    if(Char.IsLower(x, x.Length - 1)) 
    { 
     a.HashSet1.Add(x.Substring(0, x.Length - 1)); 
    } 
    else 
    { 
     a.HashSet2.Add(x); 
    } 
    return a; 
}, 
a => 
{ 
    // Return all the strings that are present in both hash sets. 
    return 
    a 
    .HashSet1 
    .Where(x => a.HashSet2.Contains(x)); 
}); 
+0

Ähnlich wie Asads Antwort, aber danke für die neue Sichtweise. "Ich wusste nicht, dass es so einfach ist," Aggregate "zu verwenden. – chris

+0

Ha! Ja, ich habe seine Antwort nicht gesehen. Ich überlegte, eine LINQ Lösung zu geben weil es viel sauberer ist, aber die Frage speziell erwähnt LINQ. –

1

.Where() für jedes Element, und für jeden, .Where() auf jedem Element wieder, sicherstellen, dass mindestens ein passt das RegexMuster der ursprünglichen Element und einem Kleinbuchstaben.

var input = new List<string>() { "1", "4", "3a", "1b", "3", "6c" }; 
var output = input.Where(
    x => input.Where(
     y => Regex.Match(y, "^" + Regex.Escape(x) + "[a-z]$").Success 
    ).Any() 
); 

output enthält { "1", "3" }.

+1

Sie können '.Any()' anstelle von '.ToArray(). Length! = 0'. –

+0

Aagh, ich * wusste * Ich habe etwas falsch gemacht Ruf an, ich werde das bearbeiten. Danke! – Diosjenin

Verwandte Themen