2009-05-06 23 views
8

Sto lavorando con le stringhe e ho uno scenario in cui ho bisogno di determinare se una stringa (solitamente una piccola < 10 caratteri) contiene caratteri ripetuti.Test per caratteri ripetuti in una stringa

`ABCDE` // does not contain repeats 
`AABCD` // does contain repeats, ie A is repeated 

posso scorrere lo string.ToCharArray() e testare ogni personaggio contro ogni altro personaggio del char [], ma mi sento come mi manca qualcosa ovvio .... forse ho solo bisogno di caffè. Qualcuno può aiutare?

EDIT:

La stringa verrà ordinato, in modo da ordine non è importante in modo ABCDA => AABCD

La frequenza di ripetizioni è anche importante, quindi ho bisogno di sapere se la ripetizione è coppia o tripletta ecc.

+0

"ABCDA" dovrebbe essere considerato come ripetuto? Cioè sei interessato a eventuali ripetizioni o solo a caratteri consecutivi? – Richard

+0

Quale versione del framework? – BenAlabaster

+0

La versione del framework è 3.5 – inspite

risposta

9

Se la stringa è breve, solo il looping e il test potrebbero essere il modo più semplice ed efficace. Voglio dire che tu potresti creare un set di hash (in qualunque piattaforma tu stia usando) e scorrere tra i personaggi, fallendo se il personaggio è già nel set e aggiungendolo al set in altro modo - ma è probabile che fornisca qualche beneficio quando le corde sono più lunghe

EDIT: Ora che sappiamo che è ordinato, mquander's answer è il migliore IMO. Ecco un'implementazione:

public static bool IsSortedNoRepeats(string text) 
{ 
    if (text.Length == 0) 
    { 
     return true; 
    } 
    char current = text[0]; 
    for (int i=1; i < text.Length; i++) 
    { 
     char next = text[i]; 
     if (next <= current) 
     { 
      return false; 
     } 
     current = next; 
    } 
    return true; 
} 

A breve alternativa, se non ti dispiace ripetere l'uso indicizzatore:

public static bool IsSortedNoRepeats(string text) 
{ 
    for (int i=1; i < text.Length; i++) 
    { 
     if (text[i] <= text[i-1]) 
     { 
      return false; 
     } 
    } 
    return true; 
} 

EDIT: Okay, con il lato "frequenza", mi trasformerò il problema rotonda un po. Devo ancora supporre che la stringa sia ordinata, quindi quello che vogliamo sapere è la lunghezza della corsa più lunga. Quando non ci sono ripetizioni, la lunghezza della corsa più lunga sarà 0 (per una stringa vuota) o 1 (per una stringa non vuota). Altrimenti, saranno 2 o più.

Prima una versione specifica stringa:

public static int LongestRun(string text) 
{ 
    if (text.Length == 0) 
    { 
     return 0; 
    } 
    char current = text[0]; 
    int currentRun = 1; 
    int bestRun = 0; 

    for (int i=1; i < text.Length; i++) 
    { 
     if (current != text[i]) 
     { 
      bestRun = Math.Max(currentRun, bestRun); 
      currentRun = 0; 
      current = text[i]; 
     } 
     currentRun++; 
    } 
    // It's possible that the final run is the best one 
    return Math.Max(currentRun, bestRun); 
} 

Ora possiamo anche fare questo come un metodo di estensione generale sulla IEnumerable<T>:

public static int LongestRun(this IEnumerable<T> source) 
{ 
    bool first = true; 
    T current = default(T); 
    int currentRun = 0; 
    int bestRun = 0; 

    foreach (T element in source) 
    { 
     if (first || !EqualityComparer<T>.Default(element, current)) 
     { 
      first = false; 
      bestRun = Math.Max(currentRun, bestRun); 
      currentRun = 0; 
      current = element; 
     } 
    } 
    // It's possible that the final run is the best one 
    return Math.Max(currentRun, bestRun); 
} 

Quindi è possibile chiamare "AABCD".LongestRun() per esempio.

+0

Questo è esattamente come lo farei. +1 –

+0

E pensavo fossi un evangelista LINQ: P – BobTheBuilder

+0

Sono un fan di LINQ dove è appropriato. In questo caso, non penso che lo sia. –

3

Aggiornamento Ora, è necessario disporre di una serie di contatori per mantenere un conteggio.

Mantieni un array di bit, con un bit che rappresenta un carattere univoco. Attiva il bit quando incontri un personaggio ed esegui una volta la stringa. Una mappatura dell'indice di array di bit e del set di caratteri spetta a te decidere. Break se vedi che un particolare bit è già attivo.

+0

+1. Anche HashSet è valido, ma poiché questo problema è limitato a 26 elementi, un array bit/bool sarà molto più veloce. –

+0

Se non c'è molto da chiedere, qualcuno potrebbe fornire una implementazione di questo? –

+0

La domanda è stata modificata e questa risposta non funziona più, poiché non è possibile ottenere la frequenza dei duplicati in questo modo. –

16

Se la stringa è ordinata, è sufficiente ricordare ogni carattere a turno e verificare che il prossimo carattere non sia mai identico all'ultimo carattere.

Oltre a ciò, per le stringhe con meno di dieci caratteri, testare ogni personaggio contro tutto il resto è probabilmente più veloce o più veloce della maggior parte delle altre cose. Un vettore bit, come suggerito da un altro commentatore, potrebbe essere più veloce (aiuta se hai un piccolo set di caratteri legali.)

Bonus: ecco una soluzione LINQ chiazza di petrolio per implementare la funzionalità di Jon:

int longestRun = 
    s.Select((c, i) => s.Substring(i).TakeWhile(x => x == c).Count()).Max(); 

Quindi, OK, non è molto veloce! Hai un problema con quello ?!

:-)

+0

Non molto elegante però ... una bella dichiarazione LINQ lo farebbe molto succintamente. – BobTheBuilder

+1

È vero, ma se mi sta anche facendo questa domanda, presumo che le prestazioni siano importanti. – mquander

6

Penso che il modo più semplice per raggiungere questo obiettivo è quello di utilizzare questa semplice espressione regolare

bool foundMatch = false; 
foundMatch = Regex.IsMatch(yourString, @"(\w)\1"); 

Se avete bisogno di ulteriori informazioni circa la partita (inizio, durata, ecc)

 Match match = null; 
    string testString = "ABCDE AABCD"; 
    match = Regex.Match(testString, @"(\w)\1+?"); 
    if (match.Success) 
    { 
     string matchText = match.Value; // AA 
     int matchIndnex = match.Index; // 6 
     int matchLength = match.Length; // 2 
    } 
+0

Gah, bastonatemi. –

2
/(.).*\1/ 

(o qualunque sia l'equivalente nella sintassi della libreria regex)

Non è il più efficiente, poiché probabilmente tornerà indietro a ogni carattere nella stringa e quindi eseguirà nuovamente la scansione in avanti. E di solito non sostengo espressioni regolari. Ma se si vuole brevità ...

7

Dal momento che si sta utilizzando 3.5, si potrebbe fare questo nella query LINQ uno:

var results = stringInput 
    .ToCharArray() // not actually needed, I've left it here to show what's actually happening 
    .GroupBy(c=>c) 
    .Where(g=>g.Count()>1) 
    .Select(g=>new {Letter=g.First(),Count=g.Count()}) 
; 

Per ogni personaggio che appare più di una volta l'ingresso, questo darà tu il personaggio e il numero di occorrenze.

+0

Si potrebbe condensare molto di più semplicemente controllando le distinzioni ... se c'è un diverso numero di distinzioni rispetto al reale, allora hai un duplicato. – BobTheBuilder

+1

L'OP voleva sapere quali lettere sono state ripetute, così come il numero di occorrenze, quindi la mia soluzione sopra. –

+1

@Bob come indicato nella modifica OPs, questo si occupa della frequenza che probabilmente una soluzione più condensata non avrebbe. – BenAlabaster

8

Questo vi dirà molto rapidamente se una stringa contiene duplicati:

bool containsDups = "ABCDEA".Length != s.Distinct().Count(); 

E 'solo controlla il numero di caratteri distinti contro la lunghezza originale. Se sono diversi, hai duplicati ...

Modifica: Immagino che questo non si preoccupi della frequenza dei duplicati che hai notato nella tua modifica però ... ma alcuni altri suggerimenti qui già abbi cura di questo, quindi non posterò il codice perché noto che alcuni di essi ti offrono già una soluzione ragionevolmente elegante. Mi piace particolarmente l'implementazione di Joe con le estensioni LINQ.

+1

Puoi rimuovere .ToCharArray(), funzionerà perfettamente solo con s.Distinct(). Count() ... – BobTheBuilder

+0

Grazie, ho aggiornato di conseguenza il mio codice – BenAlabaster

2

ne dite qualcosa di simile:

string strString = "AA BRA KA DABRA"; 

var grp = from c in strString.ToCharArray() 
     group c by c into m 
     select new { Key = m.Key, Count = m.Count() }; 

foreach (var item in grp) 
{ 
    Console.WriteLine(
     string.Format("Character:{0} Appears {1} times", 
     item.Key.ToString(), item.Count)); 
} 
+0

uguale a quello di Joe, ma +1 mostra diversi sintassi. btw String implementa IEnumerable , nessuna necessità di ToCharArray() – Lucas

0

Quando non c'è fine di lavorare su di voi potrebbe usare un dizionario per mantenere i conteggi:

String input = "AABCD"; 
var result = new Dictionary<Char, int>(26); 
var chars = input.ToCharArray(); 
foreach (var c in chars) 
{ 
    if (!result.ContainsKey(c)) 
    { 
     result[c] = 0; // initialize the counter in the result 
    } 
    result[c]++; 
} 

foreach (var charCombo in result) 
{ 
    Console.WriteLine("{0}: {1}",charCombo.Key, charCombo.Value); 
} 
0

La soluzione hash Jon stava descrivendo è probabilmente il migliore. È possibile utilizzare un HybridDictionary poiché funziona bene con insiemi di dati piccoli e grandi. Dove la lettera è la chiave e il valore è la frequenza. (Aggiorna la frequenza ogni volta che l'aggiunta fallisce o HybridDictionary restituisce true per .Contains (chiave))

1

Ho iniziato a cercare alcune informazioni sulla rete e sono arrivato alla seguente soluzione.

string input = "aaaaabbcbbbcccddefgg"; 
     char[] chars = input.ToCharArray(); 
     Dictionary<char, int> dictionary = new Dictionary<char,int>(); 

     foreach (char c in chars) 
     { 
      if (!dictionary.ContainsKey(c)) 
      { 
       dictionary[c] = 1; // 
      } 
      else 
      { 
       dictionary[c]++; 
      } 
     } 

     foreach (KeyValuePair<char, int> combo in dictionary) 
     { 
      if (combo.Value > 1) //If the vale of the key is greater than 1 it means the letter is repeated 
      { 
       Console.WriteLine("Letter " + combo.Key + " " + "is repeated " + combo.Value.ToString() + " times"); 
      } 

     } 

Spero che aiuta, ho avuto un colloquio di lavoro in cui l'intervistatore mi ha chiesto di risolvere questo e capisco che è una domanda comune.

Problemi correlati