2015-04-24 23 views
19

Ecco cosa sto cercando di fare in generale. Per essere chiari, questo non è compito o per un concorso o altro. Speriamo, ho fatto la formulazione abbastanza chiaro:Esiste una soluzione LINQ elegante per SomeButNotAll()?

Problema

Dato un insieme di stringhe nello stesso formato, ma dove qualche fine in una lettera minuscola e alcuni non fai, riprendi in un insieme di uno di ogni stringa che non termina in una lettera minuscola, ma che ha almeno una stringa identica che termina in una lettera minuscola.

Esempio

Per farla semplice, diciamo che il formato stringa è \d+[a-z]?, dove la parte comune è il numero. Dato {1, 4, 3a, 1b, 3, 6c}, dovrei ricevere una permutazione di {1, 3} perché 1 e 3 hanno entrambi un elemento con e senza una lettera minuscola alla fine.

soluzione alternativa

È possibile view this solution here.

Un modo ho pensato di fare questo è stato quello di dividere il set in elementi con e senza una lettera di suffisso minuscolo ({1, 4, 3} e {3a, 1b, 6c}), e poi tornare withoutSuffix.Where(x => withSuffix.Any(y => y.StartsWith(x))).

ho due problemi con questo:

  1. non vedo un buon modo per dividere in due set, con il predicato Regex.IsMatch(input, "[a-z]$"). I due a cui pensavo erano due variabili definite allo stesso modo, ciascuna usando una clausola Where e facendo la regex corrispondente a due volte per elemento, o trasformando il set per memorizzare i risultati della corrispondenza regolare e quindi formando le due variabili da quella. group...by non sembra funzionare bene quando è necessario accedere a entrambi i set come questo, ma potrei sbagliarmi lì.

  2. Anche se la dimensione è abbastanza piccola per non preoccuparsi delle prestazioni, passare attraverso withSuffix una volta per elemento withoutSuffix sembra poco elegante.

Soluzione Rilevante

È possibile view this solution here.

L'altro modo che mi è venuto in mente era prendere il prefisso comune e il suffisso opzionale: {1 => {"", b}, 3 => {a, ""}, 4 => {""}, 6 => {c}}. Ciò è facilmente ottenibile catturando il prefisso e il suffisso con un'espressione regolare ((\d+)([a-z])?) e raggruppando il suffisso con il prefisso in grouped.

Da qui, sarebbe bello fare:

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

O anche:

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

mi potrebbe certamente creare uno di questi, ma purtroppo, il meglio che posso vedere in LINQ è :

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

Si sente super-ridondante. C'è già qualcosa di meglio in LINQ?

P.S. Sono aperto ad approcci migliori per risolvere il problema generale invece di renderlo un problema XY. L'eleganza è desiderata molto più che le prestazioni, ma (forse sono solo io) essere semplicemente dispendioso sembra ancora poco elegante.

risposta

4

Non penso che tu abbia davvero bisogno di un'espressione regolare per questo. Ecco come lo farei:

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

Buona risposta. Potresti voler deduplicare il risultato alla fine però. – moarboilerplate

+1

@moarboilerplate Non sono sicuro di cosa intendi. Gli insiemi hanno elementi unici. –

+0

Questo mi fa quasi desiderare che ci fosse un 'PartitionSelect' di sorta. 'var partitions = input.PartitionSelect (s => char.IsLower (s.Last()), s => s.Substring (0, s.Length - 1), s => s);', con 'partizioni. TrueValues.Intersect (partitions.FalseValues); '(anche se non mi aspetterei set di hash da quello). – chris

3

si potrebbe aggiungere un altro raggruppamento da string.IsNullOrEmpty e verificare che ha 2 gruppi (uno per false e uno per 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

È possibile modificare .Any()-!All().

Preferirei usare il sovraccarico Count con un predicato e confrontandolo con il conteggio generale. Questo sarà probabilmente il più pulito e non dovrai preoccuparti delle eccezioni derivanti da raccolte vuote.

+0

'! All' non funzionerà in questo scenario. – MarcinJuraszek

+0

È divertente menzionare il metodo di conteggio, poiché ho provato 'grouping.Count (x => x == string.Empty) chris

+0

@MarcinJuraszek 'grouped.Contains (string.Empty) &&! Grouped.All (x => x == string.Empty)' – moarboilerplate

1

In questo caso, penso che la soluzione più performante non sia necessariamente molto elegante in LINQ. Penso che questo dovrebbe fare ciò che vuoi e farlo funzionare O (N).

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

Simile alla risposta di Asad, ma grazie per la nuova visualizzazione su di esso. Non mi rendevo conto che era semplice usare 'Aggregate'. – chris

+0

Ha! Sì, non avevo nemmeno visto la sua risposta. perché è molto più pulito, ma la domanda specificava LINQ. –

1

.Where() su ogni elemento, e per ciascuno, .Where() su ogni elemento nuovo, assicurandosi che almeno una adatta l'espressione regolare dell'elemento originale più qualsiasi lettera minuscola.

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 contiene { "1", "3" }.

+1

Puoi usare '.Any()' invece di '.ToArray(). Length! = 0'. –

+0

Aagh, io * sapevo * Stavo facendo qualcosa di sbagliato. chiama: lo modificherò. Grazie! – Diosjenin