2009-05-06 56 views
8

Tôi đang thực hiện một số công việc với chuỗi và tôi có một kịch bản cần xác định xem một chuỗi (thường là một chuỗi nhỏ < 10 ký tự) có chứa các ký tự lặp lại hay không.Kiểm tra các ký tự lặp lại trong một chuỗi

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

tôi có thể lặp qua string.ToCharArray() và kiểm tra mỗi nhân vật chống lại mọi nhân vật khác trong char [], nhưng tôi cảm thấy như tôi đang thiếu một cái gì đó rõ ràng .... có lẽ tôi chỉ cần cà phê. Có ai giúp được không?

EDIT:

Chuỗi sẽ được sắp xếp, vì vậy để không phải là quan trọng để ABCDA => AABCD

Tần số lặp đi lặp lại cũng rất quan trọng, vì vậy tôi cần phải biết nếu lặp lại là cặp hoặc triplet vv

+0

"ABCDA" có được coi là lặp lại không? I E. bạn có quan tâm đến bất kỳ lặp lại hoặc chỉ là ký tự liên tiếp? – Richard

+0

Phiên bản nào của khung công tác? – BenAlabaster

+0

Phiên bản khung là 3.5 – inspite

Trả lời

9

Nếu chuỗi ngắn, thì chỉ lặp và thử nghiệm có thể là cách đơn giản và hiệu quả nhất. Ý tôi là bạn có thể tạo bộ băm (trong bất kỳ nền tảng nào bạn đang sử dụng) và lặp qua các ký tự, không thành công nếu ký tự đã có trong bộ và thêm nó vào bộ khác - nhưng đó chỉ có khả năng cung cấp bất kỳ lợi ích nào khi các chuỗi dài hơn.

EDIT: Bây giờ chúng ta đã biết nó được sắp xếp, mquander's answer là IMO tốt nhất. Dưới đây là một thực hiện:

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; 
} 

Một lựa chọn ngắn hơn nếu bạn không nhớ lặp đi lặp lại việc sử dụng indexer:

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: Được rồi, với "tần số" phụ, tôi sẽ biến các vấn đề vòng một chút. Tôi vẫn sẽ giả định rằng chuỗi được sắp xếp, vì vậy những gì chúng ta muốn biết là độ dài của chuỗi dài nhất. Khi không có lặp lại, độ dài chạy dài nhất sẽ là 0 (cho một chuỗi trống) hoặc 1 (đối với một chuỗi không trống). Nếu không, nó sẽ là 2 hoặc nhiều hơn.

Lần đầu tiên một chuỗi phiên bản cụ thể:

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); 
} 

Bây giờ chúng ta cũng có thể làm điều này như một phương pháp khuyến nông chung trên 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); 
} 

Sau đó, bạn có thể gọi "AABCD".LongestRun() ví dụ.

+0

Đây chính xác là cách tôi thực hiện. +1 –

+0

Và tôi nghĩ bạn là một người truyền bá LINQ: P – BobTheBuilder

+0

Tôi là người hâm mộ LINQ nơi thích hợp. Trong trường hợp này, tôi không nghĩ là vậy. –

3

Cập nhật Bây giờ, bạn cần một loạt các bộ đếm để duy trì số lượng.

Giữ một mảng bit, với một bit đại diện cho một ký tự duy nhất. Bật bit khi bạn gặp phải một nhân vật và chạy qua chuỗi một lần. Một ánh xạ của chỉ số mảng bit và bộ ký tự là tùy thuộc vào bạn để quyết định. Phá vỡ nếu bạn thấy rằng một bit cụ thể đã được bật.

+0

+1. HashSet cũng hợp lệ, nhưng vì vấn đề này được giới hạn ở 26 mục, một mảng bit/bool sẽ nhanh hơn. –

+0

Nếu không quá nhiều để hỏi, ai đó có thể vui lòng cung cấp việc thực hiện điều này không? –

+0

Câu hỏi hiện đã được chỉnh sửa và câu trả lời này không còn hoạt động nữa vì không thể nhận được tần suất trùng lặp theo cách này. –

16

Nếu chuỗi được sắp xếp, bạn có thể chỉ cần nhớ từng ký tự và kiểm tra để đảm bảo ký tự tiếp theo không bao giờ giống với ký tự cuối cùng.

Ngoài ra, đối với chuỗi dưới mười ký tự, chỉ cần thử nghiệm từng ký tự đối với tất cả các ký tự còn lại có thể nhanh hoặc nhanh hơn hầu hết các thứ khác. Một bit bit, như được đề xuất bởi một người nhận xét khác, có thể nhanh hơn (giúp nếu bạn có một bộ ký tự hợp pháp nhỏ.)

Bonus: đây là một giải pháp LINQ trơn để thực hiện chức năng của Jon:

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

Vì vậy, OK, nó không phải là rất nhanh! Bạn có một vấn đề với điều đó?!

:-)

+0

Không phải là rất thanh lịch mặc dù ... một tuyên bố LINQ nhỏ đẹp sẽ làm điều đó rất ngắn gọn. – BobTheBuilder

+1

Đó là sự thật, nhưng nếu anh ấy thậm chí còn hỏi câu hỏi này, tôi cho rằng hiệu suất là quan trọng. – mquander

6

Tôi nghĩ rằng cách dễ nhất để đạt được điều đó là sử dụng regex đơn giản này

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

Nếu bạn cần biết thêm thông tin về trận đấu (bắt đầu, chiều dài vv)

 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, đánh tôi đi. –

2
/(.).*\1/ 

(hoặc bất cứ điều gì tương đương là trong cú pháp thư viện regex của bạn)

01.

Không hiệu quả nhất, vì nó có thể sẽ quay trở lại mọi ký tự trong chuỗi và sau đó quét lại. Và tôi thường không ủng hộ các biểu thức thông thường. Nhưng nếu bạn muốn ngắn gọn ...

7

Vì bạn đang sử dụng 3.5, bạn có thể làm điều này trong truy vấn một LINQ:

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()}) 
; 

Đối với mỗi nhân vật xuất hiện nhiều hơn một lần trong đầu vào, điều này sẽ cung cấp cho bạn là nhân vật và số lần xuất hiện.

+0

Bạn có thể ngưng tụ điều này nhiều hơn nữa bằng cách chỉ kiểm tra phân biệt ... nếu có một số khác biệt của sự khác biệt so với thực tế, sau đó bạn đã có một bản sao. – BobTheBuilder

+1

OP muốn biết chữ nào được lặp lại, cũng như số lần xuất hiện, do đó giải pháp của tôi ở trên. –

+1

@Bob như đã lưu ý trong bản chỉnh sửa OP, điều này đảm bảo tần suất mà một giải pháp ngưng tụ hơn có thể sẽ không xảy ra. – BenAlabaster

8

này sẽ cho bạn rất nhanh chóng nếu một chuỗi chứa bản sao:

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

Nó chỉ kiểm tra số ký tự khác biệt so với chiều dài ban đầu. Nếu chúng khác nhau, bạn đã có các bản sao ...

Chỉnh sửa: Tôi đoán điều này không quan tâm đến tần suất dups bạn đã ghi trong chỉnh sửa của mình ... nhưng một số gợi ý khác ở đây đã chăm sóc đó, vì vậy tôi sẽ không đăng các mã như tôi lưu ý một số trong số họ đã cung cấp cho bạn một giải pháp hợp lý thanh lịch. Tôi đặc biệt thích việc triển khai của Joe bằng cách sử dụng phần mở rộng LINQ.

+1

Bạn có thể xóa .ToCharArray(), nó sẽ hoạt động tốt chỉ với s.Distinct(). Count() ... – BobTheBuilder

+0

Cảm ơn, tôi đã cập nhật mã của mình cho phù hợp – BenAlabaster

2

Làm thế nào về một cái gì đó như:

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

giống như của Joe, nhưng +1 hiển thị khác nhau cú pháp. btw Chuỗi triển khai IEnumerable , không cần ToCharArray() – Lucas

0

Khi không có trật tự để làm việc trên bạn có thể sử dụng một từ điển để giữ đếm:

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

Giải pháp băm Jon đã được mô tả có lẽ là tốt. Bạn có thể sử dụng HybridDictionary vì nó hoạt động tốt với các tập dữ liệu nhỏ và lớn. Trong đó chữ cái là chìa khóa và giá trị là tần số. (Cập nhật tần suất mỗi lần thêm không thành công hoặc HybridDictionary trả về true cho .Contains (key))

1

Tôi bắt đầu tìm kiếm một số thông tin trên mạng và tôi đã nhận được giải pháp sau.

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"); 
      } 

     } 

Tôi hy vọng điều này sẽ giúp tôi có cuộc phỏng vấn xin phỏng vấn và tôi hiểu đó là một câu hỏi phổ biến.

Các vấn đề liên quan