1

Is there a way to rewrite the GetTransformedCollection method below so that it uses a Linq statement and not an expression? I'm currently trying to get around the “A lambda expression with a statement body cannot be converted to an expression tree” error.

public class Obj1
{
    public int Id { get; set; }
    public string[] Names { get; set; }
    public string[] Tags { get; set; }
}

public class EntCollections
{
    private List<Obj1> _results;

    [SetUp]
    public void SetUp()
    {
        _results = new List<Obj1>
        {
            new Obj1 {Id = 1, Names = new[] {"n1"}, Tags = new[] {"abc", "def"}},
            new Obj1 {Id = 2, Names = new[] {"n2", "n3"}, Tags = new[] {"ghi"}},
            new Obj1 {Id = 3, Names = new[] {"n1", "n3"}, Tags = new[] {"def", "xyz"}}
        };
    }

    private static Dictionary<string, List<string>>
        GetTransformedCollection(IEnumerable<Obj1> results)
    {
        var list = new Dictionary<string, List<string>>();

        foreach (var result in results)
        {
            foreach (var id in result.Names)
            {
                if (list.ContainsKey(id))
                {
                    list[id].AddRange(result.Tags);
                }
                else
                {
                    list.Add(id, result.Tags.ToList());
                }
            }
        }

        return list;
    }

    [Test]
    public void Test()
    {
        var list = GetTransformedCollection(_results);

        Assert.That(list["n1"], Is.EquivalentTo(new [] { "abc", "def", "def", "xyz" }));
        Assert.That(list["n2"], Is.EquivalentTo(new [] { "ghi" }));
        Assert.That(list["n3"], Is.EquivalentTo(new [] { "ghi", "def", "xyz" }));
    }

P.s I'm not too worried about the result type being a Dictionary, that was just the simplist way to express it as a return type.

Nick
  • 6,366
  • 5
  • 43
  • 62

2 Answers2

7

I would personally use an ILookup, which is a good bet whenever you have a Dictionary<T1, List<T2>>, and is built with ToLookup():

// Flatten the objects (lazily) to create a sequence of valid name/tag pairs
var pairs = from result in results
            from name in result.Names
            from tag in result.Tags
            select new { name, tag };

// Build a lookup from name to all tags with that name
var lookup = pairs.ToLookup(pair => pair.name, pair => pair.tag);
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • +1. Much cleaner solution, than using extension method syntax – Ilya Ivanov Jan 15 '13 at 17:28
  • I'm coming around to this approach as it only enumerates the list once. Is there a way to perform an 'outer join' on the list? By this I mean if an element exists in the original list with _n_ Names but _0_ Tags, then the individual names will still appear in the result set as a pair with null/empty string? I've appended such an element the original list and added an extra assertion. – Nick Jan 16 '13 at 10:09
  • That sounds like you want `GroupJoin`, basically. It would probably be a significantly different answer - I suggest you ask it as a separate question, given how different it will be. – Jon Skeet Jan 16 '13 at 10:17
  • The saga continues... http://stackoverflow.com/questions/14356327/use-a-linq-statement-to-perfrom-outer-join – Nick Jan 16 '13 at 10:33
  • Also removed the amendment to this question so the answer remains valid. – Nick Jan 16 '13 at 10:56
  • Your answer inspired me flattening a `Dictionarty>` to a `List` where MyClass is `{T1 t1, T2 t2}` var l = (from kvp in dProblematics from value in dProblematics[kvp.Key] select new { FileName = kvp.Key, Tag = value }).ToList(); – Achilles Apr 18 '13 at 15:22
2

Idea is to find all keys for resulting dictionary and then find corresponding values from original sequence of Obj1

var distinctNames = results.SelectMany(val => val.Names).Distinct();

return distinctNames
      .ToDictionary(name => name, 
                    name => results
                            .Where(res => res.Names.Contains(name))
                            .SelectMany(res => res.Tags)
                            .ToList());
Ilya Ivanov
  • 23,148
  • 4
  • 64
  • 90