2009-04-23 5 views
0

나는 사용자 목록에 사용자 및 검색 문자열 문자열을 취하는 필터 메소드가 있습니다. 현재 FindAll 술어는 공백에 용어를 나눈 다음 검색 가능한 속성 중 하나라도 용어의 일부가 포함되어 있으면 일치를 반환합니다.C#의 검색어로 필터링 한 후 일치 항목 수를 기준으로 목록을 정렬하려면 어떻게해야합니까?

public static List<User> FilterBySearchTerms(List<User> usersToFilter, string searchTerms, bool searchEmailText) 
{ 
    return usersToFilter.FindAll(user => 
    { 
     // Convert to lower case for better comparison, trim white space and then split on spaces to search for all terms 
     string[] terms = searchTerms.ToLower().Trim().Split(' '); 

     foreach (string term in terms) 
     { 
      // TODO: Is this any quicker than two separate ifs? 
      if ( 
        (searchEmailText && user.Email.ToLower().Contains(term)) 
        || (
         user.FirstName.ToLower().Contains(term) || user.Surname.ToLower().Contains(term) 
         || user.Position.ToLower().Contains(term) || user.Company.ToLower().Contains(term) 
         || user.Office.ToLower().Contains(term) 
         || user.Title.ToLower().Contains(term) 
        ) 
      ) 
       return true; 
      // Search UserID by encoded UserInviteID 
      else 
      { 
       int encodedID; 
       if (int.TryParse(term, out encodedID)) 
       { 
        User fromInvite = GetByEncodedUserInviteID(encodedID); 
        if (fromInvite != null && fromInvite.ID.HasValue && fromInvite.ID.Value == user.ID) 
         return true; 
       } 
      } 
     } 

     return false; 
    }); 
} 

이제 주문이 중요해질 수 있도록 새로운 요구 사항을 받았습니다. 예를 들어, 'Mr Smith'를 검색하면 부인 인 이브 스미스 (Eve Smith) 앞에서 미스터 아담 스미스 (Adam Smith)가 있어야합니다. 그러나 가장 중요한 것은 속성의 수/용어 매치의 일부입니다.

저는 완전한 용어 일치와 부분 일치를 추적하기위한 몇 가지 카운터를 가질 수 있다고 생각합니다. 그런 다음 그 둘로 주문하십시오. Filter 메서드를 개선 할 수있는 방법에 대한 제안도 열려 있습니다. 아마도 다른 것을 사용했을 것입니다.

답변

4

다음은 LINQ 기반 솔루션입니다. .NET 3.5를 사용하지 않는다면 조금 더 고통 스러울 것입니다. 그것은 명확성을 위해 쿼리 자체와 일치하는 세부 정보를 구분합니다.

모든 속성이 소문자 인 User 개체를 반환하는 LowerCaseUser 메서드를 만들어야합니다. 모든 검색 용어보다 한 번 더 수행하는 것이 좋습니다. 만약 당신이 그걸 넣을 수 있고 UserMatchesUser 클래스에 넣어두면 훨씬 좋습니다. 어쨌든 여기에 코드가 있습니다.

public static List<User> FilterBySearchTerms 
    (List<User> usersToFilter, 
    string searchTerms, 
    bool searchEmailText) 
{ 
    // Just split the search terms once, rather than for each user 
    string[] terms = searchTerms.ToLower().Trim().Split(' '); 

    return (from user in usersToFilter 
      let lowerUser = LowerCaseUser(user) 
      let matchCount = terms.Count(term => 
             UserMatches(lowerUser, term)) 
      where matchCount != 0 
      orderby matchCount descending 
      select user).ToList(); 
} 

private static bool UserMatches(User user, string term, 
           bool searchEmailText) 
{ 
    if ((searchEmailText && user.Email.Contains(term)) 
     || user.FirstName.Contains(term) 
     || user.Surname.Contains(term) 
     || user.Position.Contains(term) 
     || user.Company.Contains(term) 
     || user.Office.Contains(term) 
     || user.Title.Contains(term)) 
    { 
     return true; 
    } 
    int encodedID; 
    if (int.TryParse(term, out encodedID)) 
    { 
     User fromInvite = GetByEncodedUserInviteID(encodedID); 
     // Let the compiler handle the null/non-null comparison 
     if (fromInvite != null && fromInvite.ID == user.ID) 
     { 
      return true; 
     } 
    } 
    return false; 
} 
+0

영문자 우선 순위를 유지하기 위해 orderby matchCount 뒤에 사용자 이름에 대한 보조 orderby를 추가 하시겠습니까? –

+0

아마도, 그것이 원한다면. 모든 종류의 옵션이 있습니다 :) –

+0

안녕하세요 존, 답장을 보내 주셔서 감사합니다. 귀하의 코드에서 몇 가지 오류가 발생했습니다. 1. System.Linq.Enumerable.Count (System.Collections.Generic.IEnumerable , System.Func ) 메서드의 형식 인수를 사용에서 유추 할 수 없습니다. 형식 인수를 명시 적으로 지정하십시오. 2. 이름 'term'이 현재 컨텍스트에 존재하지 않습니다. Linq를 사용하기 전에 사용하지 않은 것 같습니다. 정확히 어떻게 작동하는지 모르겠습니다 ... – Rich

0

관련성이있는 부분 전체 또는 부분 일치를 구분하거나 표준 사전 식 정렬 만 구분합니까? Adam Smith 선생님과 Eve Smith 선생님을 분류하면 순서대로 배치됩니다. 그러면 표준 람다를 사용할 수 있습니다.

+0

사전 식 순서는 일치 횟수 이후로 중요합니다. – Rich

1

가장 먼저해야 할 일은 느슨한 평가 또는 조건을 별도의 조건으로 나누는 것입니다. 그렇지 않으면 실제로 얻는 일치 수를 해결할 수 없습니다. 이 후에는 검색 용어가 검색 결과와 얼마나 잘 일치 하는지를 나타내는 점수가 각 사용자에게 필요할 것입니다.

이미 람다 식을 사용하고 있으므로 여기에서 LINQ를 사용할 수 있다고 가정합니다.

class ScoredUser 
    { 
     public User User { get; set; } 
     public int Score { get; set; } 
    } 

    public static List<User> FilterBySearchTerms(List<User> usersToFilter, string searchTerms, bool searchEmailText) 
    { 
     // Convert to lower case for better comparison, trim white space and then split on spaces to search for all terms 
     string[] terms = searchTerms.ToLower().Trim().Split(' '); 

     // Run a select statement to user list which converts them to 
     // a scored object. 
     return usersToFilter.Select(user => 
     { 
      ScoredUser scoredUser = new ScoredUser() 
      { 
       User = user, 
       Score = 0 
      }; 

      foreach (string term in terms) 
      { 
       if (searchEmailText && user.Email.ToLower().Contains(term)) 
        scoredUser.Score++; 

       if (user.FirstName.ToLower().Contains(term)) 
        scoredUser.Score++; 

       if (user.Surname.ToLower().Contains(term)) 
        scoredUser.Score++; 

       // etc. 
      } 

      return scoredUser; 

      // Select all scored users with score greater than 0, order by score and select the users. 
     }).Where(su => su.Score > 0).OrderByDescending(su => su.Score).Select(su => su.User).ToList(); 
    } 

점수가 매겨진 고객을 돌려 보내는 방법으로 나중에 점수 균형을 쉽게 조정할 수 있습니다. 예를 들어 일치하는 회사보다 일치하는 이름의 문제를 더 원한다고 가정 해보십시오.

+0

.. 물론 나는 다른 용어 나 다른 필드와 완전히 혼동합니다. 따라서 게으른 평가를 위반하는 것은 필요하지 않지만 다른 경기에서 다른 점수 금액을 허용합니다. 이 경우에는 "else if"와 결합해야합니다. –