2009-04-24 28 views
5

tôi có mã như sau:Tôi có phải băm hai lần trong C# không?

class MyClass 
{ 
    string Name; 
    int NewInfo; 
} 

List<MyClass> newInfo = .... // initialize list with some values 
Dictionary<string, int> myDict = .... // initialize dictionary with some values 

foreach(var item in newInfo) 
{ 
    if(myDict.ContainsKey(item.Name)) // 'A' I hash the first time here 
     myDict[item.Name] += item.NewInfo // 'B' I hash the second (and third?) time here 
    else 
     myDict.Add(item.Name, item.NewInfo); 
} 

Có cách nào để tránh làm hai tra cứu trong từ điển - lần đầu tiên để xem nếu có một mục, và lần thứ hai để cập nhật giá trị? Thậm chí có thể có hai lần tra cứu băm trên dòng 'B' - một để có được giá trị int và giá trị khác để cập nhật nó.

Trả lời

14

Có - sử dụng Dictionary.TryGetValue. Phải có tham số out để nhận giá trị và trả về giá trị được tìm thấy hay không. Đây là mã điều chỉnh của bạn:

foreach(var item in newInfo) 
{ 
    int value; 
    if (myDict.TryGetValue(item.Name, out value)) 
    { 
     myDict[item.Name] = value + item.NewInfo; 
    } 
    else 
    { 
     myDict[item.Name] = item.NewInfo; 
    } 
} 

Tuy nhiên, chúng ta có thể làm tốt hơn rằng trong trường hợp đặc biệt này. Nếu phím-không phải là được tìm thấy, thông số out được đặt thành 0. Khi chúng tôi sẽ đặt giá trị mới thành item.NewInfo hoặc item.NewInfo + value, chúng tôi cũng thực sự làm điều tương tự. Chúng ta có thể bỏ qua các giá trị trả về của phương pháp này, và chỉ sử dụng:

foreach(var item in newInfo) 
{ 
    int value; 
    myDict.TryGetValue(item.Name, out value); 
    myDict[item.Name] = value + item.NewInfo; 
} 

Đó là khá bất thường mặc dù - thường bạn sẽ sử dụng giá trị trả về, rõ ràng.

digression

Đó là chỉ vì bạn đang thực sự làm một hoạt động GetValueOrDefault rằng nó hoạt động. Trong thực tế, đó sẽ là một cặp giá trị của phương pháp khuyến nông:

public static TValue GetValueOrDefault<TKey, TValue> 
    (this IDictionary<TKey, TValue> dictionary, TKey key) 
{ 
    TValue value; 
    dictionary.TryGetValue(key, out value); 
    return value; 
} 

public static TValue GetValueOrDefault<TKey, TValue> 
    (this IDictionary<TKey, TValue> dictionary, TKey key, 
    TValue customDefault) 
{ 
    TValue value; 
    if (dictionary.TryGetValue(key, out value)) 
    { 
     return value; 
    } 
    else 
    { 
     return customDefault; 
    } 
} 

Tại thời điểm mà bạn có thể làm cho mã của bạn rõ ràng ngắn gọn:

foreach(var item in newInfo) 
{ 
    myDict[item.Name] = myDict.GetValueOrDefault(item.Name) + item.NewInfo; 
} 

(Bạn có thể gọi GetValueOrDefault(item.Name, 0) cho rõ ràng có khả năng hơn .)

Quay lại điểm ...

Lưu ý rằng bạn vẫn đang thực hiện hai lần tra cứu - một để tìm giá trị và một để thêm/thay thế nó. Bạn thực sự không thể tránh điều đó mà không làm cho đối số loại TValue có thể thay đổi, mà bạn có thể thay đổi tại chỗ. Điều đó sẽ có thể, nhưng không phải là tuyệt vời.

Trong mã ban đầu, bạn có khả năng thực hiện ba tìm kiếm - một cho ContainsKey và sau đó hai (nếu tìm thấy khóa) để thay thế giá trị. Nó dễ dàng hơn để thấy rằng nếu chúng ta mở rộng +=:

myDict[item.Name] = myDict[item.Name] + item.NewInfo; 

(. item.Name sẽ chỉ được đánh giá một lần, nhưng khác hơn là nó giống nhau)

digression Một

Sẽ tốt để có một hoạt động trên Dictionary đã thực hiện "tra cứu và thay thế" dựa trên một hàm để lấy giá trị mới dựa trên giá trị cũ, ví dụ:

bool Update(TKey key, Func<TValue, bool, TValue> replacementFunction) 

nơi replacementFunction sẽ là một hàm lấy giá trị hiện tại (hoặc giá trị mặc định của TValue nếu phím không được tìm thấy) và một Boolean để nói hay không phím được thực sự tìm thấy, và lợi nhuận giá trị mới. Từ điển có thể tra cứu khóa, gọi hàm thay thế và cập nhật giá trị tại chỗ. (Không thể triển khai phương thức này dưới dạng phương thức mở rộng.)

+1

Trong khi chức năng này, chỉ cần biết rằng giá trị chỉ bằng 0 vì giá trị mặc định cho loại int là 0. Từ góc độ mã hóa phòng thủ, khả năng đọc này (và khả năng bảo trì) cho tính ngắn gọn. Tôi muốn gắn bó với những gì bạn có, thành thật mà nói. –

+0

Tôi sẽ đưa ra cảnh báo lớn hơn - chắc chắn đó là trường hợp đặc biệt. –

+0

Bạn vẫn không băm mỗi mục hai lần? Một lần trong TryGetValue, và một lần nữa, nơi bạn nói 'myDict [item.Name]'? –

1

Không, bạn không cần phải băm hai lần phần lớn thời gian. Bí mật là bạn lưu trữ một đối tượng trong từ điển thay vì chỉ là một int.

class Program 
{ 
    static void Main(string[] args) 
    { 
     var newInfo = new List<MyClass>(); 
     var myDict = new Dictionary<string, MyClass>(); 

     foreach (var item in newInfo) 
     { 
      MyClass temp; 
      if (!myDict.TryGetValue(item.Name, out temp)) 
      { 
       temp = new MyClass() { Name = item.Name }; 
       myDict.Add(temp.Name,temp); 
      } 

      temp.NewInfo += 1; 

     } 

    } 
} 


class MyClass 
{ 
    public string Name; 
    public int NewInfo; 
} 
+0

Ít nhất làm cho chúng thuộc tính thay vì lĩnh vực công cộng ... ick! :) –

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