11
\$\begingroup\$

I have a class like this one:

class EmailClass 
{
    public string MailAdresse { get; set; }
    public string MailAdresseCC { get; set; }
}

Through a JSON deserialization I obtain a List<EmailClass> and two other strings anotherMailAddress and justAnotherMailAddress.

I need to join all those adresses in one big string (the separator is ";"). For this purpose I wrote this code:

// this one is populated in another section but it 
// is put there for the sake of knowing the  variable name
List<EmailClass> splittedList

List<string> listOfAdresses = new List<string>();
string joinedAdresses = String.Empty;

listOfAdresses.AddRange(splittedList.Select(x => x.MailAdresse));
listOfAdresses.AddRange(splittedList.Select(x => x.MailAdresseCC));
listOfAdresses.Add(anotherMailAddress);
listOfAdresses.Add(justAnotherMailAdresses);

joinedAdresses = String.Join(";", listOfAdresses.Distinct().ToArray());

Now, is there a more polished way to obtain this result? How much will Distinct() affect the performance on medium result set (~100.000 adresses)? I've read that the complexity of the function should be O(n), is this true?

\$\endgroup\$

2 Answers 2

14
\$\begingroup\$

Use a HashSet then it will only contain unique values, duplicates will be discarded when calling Add based upon the hashcode of the string. This should be far more efficient than calling Distinct.

List<EmailClass> splittedList

HashSet<string> listOfAdresses = new HashSet<string>();

// HashSet does not contain an AddRange method.
foreach (var emailClass in splittedList)
{
    listOfAdresses.Add(emailClass.MailAdresse);
    listOfAdresses.Add(emailClass.MailAdresseCC);
}

listOfAdresses.Add(anotherMailAddress);
listOfAdresses.Add(justAnotherMailAdresses);

string joinedAdresses = String.Join(";", listOfAdresses.ToArray());

--

Following the "feedback" from Almaz, here's a basic benchmark to show the performance difference (using unique values for each address):

private static void Main(string[] args)
{
    List<EmailClass> splittedList = Enumerable.Range(1, 100000).Select(i => new EmailClass
    {
        MailAdresse = i.ToString() + "@email.com",
        MailAdresseCC = i.ToString() + "[email protected]"
    }).ToList();

    OriginalMethod(splittedList);
    HashSetMethod(splittedList);
    LinqMethod(splittedList);

    Console.ReadLine();
}

private static void OriginalMethod(List<EmailClass> splittedList)
{
    var sw = new Stopwatch();
    sw.Start();
    List<string> listOfAdresses = new List<string>();

    listOfAdresses.AddRange(splittedList.Select(x => x.MailAdresse));
    listOfAdresses.AddRange(splittedList.Select(x => x.MailAdresseCC));
    listOfAdresses.Add("[email protected]");
    listOfAdresses.Add("[email protected]");
    var joinedAdresses = String.Join(";", listOfAdresses.Distinct().ToArray());
    sw.Stop();
    Console.WriteLine("OriginalMethod");
    Console.WriteLine(sw.Elapsed);
}

private static void HashSetMethod(List<EmailClass> splittedList)
{
    var sw = new Stopwatch();
    sw.Start();
    HashSet<string> listOfAdresses = new HashSet<string>();

    foreach (var emailClass in splittedList)
    {
        listOfAdresses.Add(emailClass.MailAdresse);
        listOfAdresses.Add(emailClass.MailAdresseCC);
    }

    listOfAdresses.Add("[email protected]");
    listOfAdresses.Add("[email protected]");

    string joinedAdresses = String.Join(";", listOfAdresses.ToArray());
    sw.Stop();
    Console.WriteLine("HashSetMethod");
    Console.WriteLine(sw.Elapsed);
}

private static void LinqMethod(List<EmailClass> splittedList)
{
    var sw = new Stopwatch();
    sw.Start();
    var emails = splittedList.SelectMany(emailClass => new[] { emailClass.MailAdresse, emailClass.MailAdresseCC })
        .Concat(new[] { "[email protected]", "[email protected]" })
        .Distinct();

    var joinedAdresses = String.Join(";", emails);
    sw.Stop();
    Console.WriteLine("LinqMethod");
    Console.WriteLine(sw.Elapsed);
}

Results run in release mode without debugger attached:

OriginalMethod: 00:00:00.0789540
HashSetMethod:  00:00:00.0488568
LinqMethod:     00:00:00.0668056

Ramping up to 1,000,000 items

OriginalMethod: 00:00:00.8189667
HashSetMethod:  00:00:00.6891028
LinqMethod:     00:00:01.0157357

As you can see, the HashSet approach is substantially faster than the OP and significantly faster than the Linq approach.

\$\endgroup\$
2
  • \$\begingroup\$ Actually Distinct does almost the same, so your solution won't be faster then pure iteration over ".Distinct". See my answer. \$\endgroup\$
    – almaz
    Nov 21, 2012 at 17:26
  • \$\begingroup\$ Won't the hash set mess up the order of the strings? \$\endgroup\$ Nov 25, 2012 at 11:24
3
\$\begingroup\$

Solution can be optimized even further in terms of readability, with the same level of performance as "manually create a HashSet and populate it with items":

var emails = splittedList.SelectMany(emailClass => new[] { emailClass.MailAdresse, emailClass.MailAdresseCC})
    .Concat(new[] {anotherMailAddress, justAnotherMailAdress})
    .Distinct();
var joinedAdresses = String.Join(";", emails);
\$\endgroup\$
4
  • 2
    \$\begingroup\$ You can argue readability either way, fewer lines of code does not necessarily mean more readable. Also, this method will result in creating an array for every item in the source list which is completely unnecessary! \$\endgroup\$ Nov 21, 2012 at 18:54
  • \$\begingroup\$ you can always do .Select(emailClass=>emailList.MailAdresse).Concat(splittedList.Select(emailClass=>emailList.MailAdresseCC)) to avoid creating arrays... \$\endgroup\$
    – almaz
    Nov 21, 2012 at 20:13
  • \$\begingroup\$ You could, but then you have to enumerate splittedList twice. Once to select the EmailAddresse and a second time to select the EmailAddresseCC which isn't an optimisation either as you're doing twice as much work! Linq isn't the answer to everything, just because you can use it, doesn't mean you should :) \$\endgroup\$ Nov 21, 2012 at 20:40
  • \$\begingroup\$ I suggested another approach that (on my opinion) is more readable, expressive and compact. It declares what you want to do instead of how you want it to be done. Optimizing microseconds here is just a premature optimization, no-one will ever build a string containing a million of emails. \$\endgroup\$
    – almaz
    Nov 22, 2012 at 9:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.