Subtracting Records from a Set using case-insensit

2019-07-09 04:50发布

问题:

I have a set of records:

type Person =
    {
        Name : string
        Age : int
    }


let oldPeople =
    set [ { Name = "The Doctor"; Age = 1500 };
          { Name = "Yoda";       Age = 900 } ]

Unlike the hardcoded example above, the set of data actually comes from a data source (over which I have very little control). Now I need to subtract a set of data from another data source. In general, the data in this second source matches, but occasionally there is a difference in captialization:

let peopleWhoAreConfusedAboutTheirAge =
    set [ { Name = "THE DOCTOR"; Age = 1500 } ]

When I attempt to subtract the second set from the first, it fails because the string comparison is case sensitive:

let peopleWhoKnowHowOldTheyAre =
    oldPeople - peopleWhoAreConfusedAboutTheirAge
val peopleWhoKnowHowOldTheyAre : Set<Person> =
  set [{Name = "The Doctor";
        Age = 1500;}; {Name = "Yoda";
                       Age = 900;}]

Is there a way to perform a case-insensitive comparison for the Name field of the People record?

回答1:

This is what I've implemented so far, though there may be a better way to do it.

My solution was to override the Equals function on the People record so as to perform a case-insensitive comparison. Set subtraction uses the Equals function to determine if two records match one another. By overriding Equals, I was forced (via warning and error) to override GetHashCode and implement IComparable (as well as set the CustomEquality and CustomComparison attributes):

[<CustomEquality; CustomComparison>]
type Person =
    {
        Name : string
        Age : int
    }

    member private this._internalId =
        this.Name.ToLower() + this.Age.ToString()

    interface System.IComparable with
        member this.CompareTo obj =
            let other : Person = downcast obj
            this._internalId.CompareTo( other._internalId )

    override this.Equals( other ) =
        match other with
        | :? Person as other -> 
            System.String.Compare( this._internalId, other._internalId ) = 0
        | _ -> false

    override this.GetHashCode() =
        this._internalId.GetHashCode()

This, however, seems to do the trick:

let oldPeople =
    set [ { Name = "The Doctor"; Age = 1500 };
          { Name = "Yoda";       Age = 900 } ]

let peopleWhoAreConfusedAboutTheirAge =
    set [ { Name = "THE DOCTOR"; Age = 1500 } ]

let peopleWhoKnowHowOldTheyAre =
    oldPeople - peopleWhoAreConfusedAboutTheirAge
val peopleWhoKnowHowOldTheyAre : Set<Person> = set [{Name = "Yoda";
                                                     Age = 900;}]

If you know a better solution (involving less code), please post it rather than comment on this answer. I will happily accept a less verbose, awkward solution.



回答2:

Here's another approach:

type Name(value) =
  member val Value = value
  override this.Equals(that) =
    match that with 
    | :? Name as name -> StringComparer.CurrentCultureIgnoreCase.Equals(this.Value, name.Value)
    | _ -> false
  override this.GetHashCode() =
    StringComparer.CurrentCultureIgnoreCase.GetHashCode(this.Value)

type Person =
  {
    Name: Name
    Age: int
  }

{Name=Name("John"); Age=21} = {Name=Name("john"); Age=21} //true