0

we are doing the following programming exercise: Who's Online.

We have written the following code:

using System;
using System.Collections.Generic;

public static class Kata
{
  public static Dictionary<UserStatus, IEnumerable<string>> WhosOnline /*❓❓*/ (User[] friends)
  {
    var users = new Dictionary<UserStatus, IEnumerable<string>>(); //We will save each friends' status and name
    for(int i=0; i<friends.Length; i++)
    {
      Console.WriteLine(friends[i].Username+","+friends[i].Status+","+friends[i].LastActivity);
      if(friends[i].Status==UserStatus.Online && friends[i].LastActivity>10){  //If it was active more than 10 minutes ago, it is away
        friends[i].Status=UserStatus.Away;  
      }

      users.Add(friends[i].Status, new []{friends[i].Username});  //We need to store the userStatus and its name

      //users[friends[i].Status] = new []{friends[i].Username};
    }

    return users;
  }
}

Being the tests:

namespace Solution 
{
  using NUnit.Framework;
  using System;
  using System.Collections.Generic;

  [TestFixture]
  public class SampleTest
  {
    [Test, Description("Example test two of each")]
    public void SoloTest()
    {
      User[] friends = new User[] 
      {
        new User("David", UserStatus.Online, 10),
        new User("Anna", UserStatus.Online, 1),
        new User("Lucy", UserStatus.Offline, 22),
        new User("Viviana", UserStatus.Offline, 44),
        new User("Bob", UserStatus.Online, 104),
        new User("Fina", UserStatus.Online, 300),
      };
      var expected = new Dictionary<UserStatus, IEnumerable<string>>
      {
        {UserStatus.Online, new[] {"David"}},
        {UserStatus.Online, new[] {"Anna"}},
        {UserStatus.Offline, new[] {"Lucy"}},
        {UserStatus.Offline, new[] {"Viviana"}},
        {UserStatus.Away, new[] {"Bob"}},
        {UserStatus.Away, new[] {"Fina"}}
      };
      Assert.That(Kata.WhosOnline(friends), Is.EqualTo(expected));
    }

    [Test, Description("Example test no one online")]
    public void NoOnlineTest()
    {
      User[] friends = new User[] 
      {
        new User("Lucy", UserStatus.Offline, 22),
        new User("Bob", UserStatus.Online, 104)
      };
      var expected = new Dictionary<UserStatus, IEnumerable<string>>
      {
        {UserStatus.Offline, new[] {"Lucy"}},
        {UserStatus.Away, new[] {"Bob"}}
      };
      Assert.That(Kata.WhosOnline(friends), Is.EqualTo(expected));
    }
  }
}

We found that it outputs:

System.ArgumentException : An item with the same key has already been added. Key: Online

We have identified that it is produced because of in the line 16:

  users.Add(friends[i].Status, new []{friends[i].Username});  //We need to store the userStatus and its name

We are trying to assign a Dictionary entry, which key has been already inserted.

We would need to do something like the following:

users.Add(i, {friends[i].Status, new []{friends[i].Username}});  //We need to store the userStatus and its name

Adding to the dictionary each entry with a unique key. However if we execute the code with the previous line we get:

src/Solution.cs(17,20): error CS1525: Invalid expression term '{'

And if we execute it as follows:

  users.Add(i, friends[i].Status, new []{friends[i].Username});  //We need to store the userStatus and its name

We observe:

src/Solution.cs(17,13): error CS1501: No overload for method 'Add' takes 3 arguments

In addition we also have tried to store entries like:

users[friends[i].Status] = new []{friends[i].Username};

However, it produces the same output:

System.ArgumentException : An item with the same key has already been added. Key: Online

Besides, we have adapted it as:

  users[i] = (friends[i].Status,new []{friends[i].Username});

After that, we get:

src/Solution.cs(20,13): error CS1503: Argument 1: cannot convert from 'int' to 'UserStatus'

How could we insert new entries of UserStatus, IEnumerable, with unique keys?

We have read:

EDIT: Thanks to @Guru Stron we have written the following code:

using System;
using System.Collections.Generic;

public static class Kata
{
  public static Dictionary<UserStatus, IEnumerable<string>> WhosOnline /*❓❓*/ (User[] friends)
  {
    var users = new Dictionary<UserStatus, List<string>>(); //We will save each friends' status and name
    for(int i=0; i<friends.Length; i++)
    {
      Console.WriteLine(friends[i].Username+","+friends[i].Status+","+friends[i].LastActivity);
      if(friends[i].Status==UserStatus.Online && friends[i].LastActivity>10){  //If it was active more than 10 minutes ago, it is away
        friends[i].Status=UserStatus.Away;  
      }

      if(!users.ContainsKey(friends[i].Status)){  //-> We should check if this status has already been saved
        users.Add(friends[i].Status, new List<string>{friends[i].Username});
      }else{
        users[friends[i].Status].Add(friends[i].Username);
      }

    }


    return users;
  }
}

However console writes:

src/Solution.cs(25,12): error CS0029: Cannot implicitly convert type 'System.Collections.Generic.Dictionary<UserStatus, System.Collections.Generic.List<string>>' to 'System.Collections.Generic.Dictionary<UserStatus, System.Collections.Generic.IEnumerable<string>>'

Because of we are trying to save a List inside a dictionary which has been declared as IEnumerable, and we can not change its type.

Then we tried, to convert it back to IEnumerable

using System;
using System.Collections.Generic;

public static class Kata
{
  public static Dictionary<UserStatus, IEnumerable<string>> WhosOnline /*❓❓*/ (User[] friends)
  {
    var users = new Dictionary<UserStatus, List<string>>(); //We will save each friends' status and name
    for(int i=0; i<friends.Length; i++)
    {
      Console.WriteLine(friends[i].Username+","+friends[i].Status+","+friends[i].LastActivity);
      if(friends[i].Status==UserStatus.Online && friends[i].LastActivity>10){  //If it was active more than 10 minutes ago, it is away
        friends[i].Status=UserStatus.Away;  
      }

      if(!users.ContainsKey(friends[i].Status)){
        users.Add(friends[i].Status, new List<string>{friends[i].Username});
      }else{
        users[friends[i].Status].Add(friends[i].Username);
      }

    }

    var output = new Dictionary<UserStatus, IEnumerable<string>>();  //We try to convert List<string> back to IEnumerable<string>
    foreach(KeyValuePair<UserStatus, List<string>> entry in users){
      if(!output.ContainsKey(entry.Key)){
        output.Add(entry.Key, entry.Value.ToArray());
      }else{
        users[entry.Key].Add(entry.Value.ToArray());
      }    
    }

    return output;
  }
}

And we observe:

src/Solution.cs(29,30): error CS1503: Argument 1: cannot convert from 'string[]' to 'string'

After that, we followed the suggestion made by @Guru Stron and we started to use Linq as follows:

using System;
using System.Collections.Generic;
using System.Linq;

public static class Kata
{
  public static Dictionary<UserStatus, IEnumerable<string>> WhosOnline /*❓❓*/ (User[] friends)
  {
    /*How could we express: if(f.Status==Online && f.LastActivity > 10) f.Status=Away ❗❓*/
    var users = friends.GroupBy(f => f.Status).ToDictionary(g => g.Key,
        g => (IEnumerable<string>)(g.Select(f => f.Username).ToList()));

    foreach(KeyValuePair<UserStatus, IEnumerable<string>> entry in users)
    {
      Console.WriteLine("Status: "+entry.Key+" Username: ");
      Console.WriteLine("[{0}]",string.Join(", ",entry.Value));
    }

    return users;
  }
}

However, the tests fail because of we would need to express:

if(f.Status==Online && f.LastActivity > 10) f.Status=Away

How could do it with Linq?

Yone
  • 2,064
  • 5
  • 25
  • 56
  • Your test case is wrong too, it's adding the same key to `expected` – Cid May 10 '20 at 08:27
  • Shouldn't it be, in example for `Online`, this : `{ UserStatus.Online, new[] { "David", "Anna" } }` ? – Cid May 10 '20 at 08:30

1 Answers1

1

Dictionary in C# is collection of unique keys and values associated with them.

So first of all you will want Dictionary<UserStatus, List<string>>() not Dictionary<UserStatus, IEnumerable<string>>()(for convenience). Secondary when adding a user to status group(key) you need to check if such group(key) exists and if yes - add to it, if not - add it:

if(!users.ContainsKey(friends[i].Status))
{
    users.Add(friends[i].Status, new List<string>{friends[i].Username})
}
else
{
    users[friends[i].Status].Add(friends[i].Username);
}

and lastly you can use LINQ:

var users = friends.GroupBy(f => f.Status)
    .ToDictionary(g => g.Key, g => g.Select(f => f.UserName).ToList())

If you can't change signature of the your function LINQ would be more convenient cause you can do

var users = friends.GroupBy(f => f.Status)
    .ToDictionary(g => g.Key,
        g => (IEnumerable<string>)(g.Select(f => f.UserName).ToList()))
Guru Stron
  • 102,774
  • 10
  • 95
  • 132