How to partition an array into multiple arrays with LINQ? - c#

In order to explain the problem I've created a simplified example. In real life the data class is somewhat more complicated. Consider the following data class:
public class Data
{
public Data(string source, string path, string information)
{
this.Source = source;
this.Path = path;
this.Information = information;
}
public string Source { get; set; }
public string Path { get; set; }
public string Information { get; set; }
}
Now consider the following array:
var array = new Data[] {
new Data("MS", #"c:\temp\img1.jpg", "{a}"),
new Data("IBM", #"c:\temp\img3.jpg", "{b}"),
new Data("Google", #"c:\temp\img1.jpg", "{c}"),
new Data("MS", #"c:\temp\img2.jpg", "{d}"),
new Data("MS", #"c:\temp\img3.jpg", "{e}"),
new Data("Google", #"c:\temp\img1.jpg", "{f}"),
new Data("IBM", #"c:\temp\img2.jpg", "{g}")
};
I would like to process the data by partitioning it on the Path and sorting each partition on Source. The output needs to be like:
c:\temp\img1.jpg
"Google": "{c}"
"IBM": "{f}"
"MS": "{a}"
c:\temp\img2.jpg
"IBM": "{g}"
"MS": "{d}"
c:\temp\img3.jpg
"IBM": "{b}"
"MS": "{e}
How can I create these partitions with LINQ?
Here you can play with the code: https://dotnetfiddle.net/EbKluE

You can use LINQ's OrderBy and GroupBy to sort your items by Source and group your ordered items by Path:
var partitioned = array
.OrderBy(data => data.Source)
.GroupBy(data => data.Path);
See this fiddle for a demo.

You can use GroupBy and OrderBy like this:
Dictionary<string, Data[]> result =
array.GroupBy(d => d.Path)
.ToDictionary(g => g.Key, g => g.OrderBy(d => d.Source).ToArray());
This gives you a dictionary with Path as keys. Each value is an array of Data that have this Path and are sorted by their Source.

I would recommend the Group-by function of lync.
For your case:
var queryImageNames =
from image in array // <-- Array is your name for the datasource
group image by image.Path into newGroup
orderby newGroup.Key
select newGroup;
foreach (var ImageGroup in queryImageNames)
{
Console.WriteLine("Key: {0}", nameGroup.Key);
foreach (var image in ImageGroup )
{
Console.WriteLine("\t{0}, {1}", image.Source, image.Information);
}
}

You could use GroupBy and do this.
var results = array
.GroupBy(x=>x.Path)
.Select(x=>
new
{
Path =x.Key,
values=x.Select(s=> string.Format("{0,-8}:{1}", s.Source, s.Information))
.OrderBy(o=>o)
})
.ToList();
Output:
c:\temp\img1.jpg
Google :{c}
Google :{f}
MS :{a}
c:\temp\img3.jpg
IBM :{b}
MS :{e}
c:\temp\img2.jpg
IBM :{g}
MS :{d}
Check this fiddle

You can use Enumerable.GroupBy to group by the Path property:
var pathPartitions = array.GroupBy(x => x.Path);
foreach(var grp in pathPartitions)
{
Console.WriteLine(grp.Key);
var orderedPartition = grp.OrderBy(x => x.Source);
foreach(var x in orderedPartition )
Console.WriteLine($"\"{x.Source}\": \"{x.Information}\"");
}
If you want to create a collection you could create a Tuple<string, Data[]>[]:
Tuple<string, Data[]>[] pathPartitions = array
.GroupBy(x => x.Path)
.Select(g => Tuple.Create(g.Key, g.OrderBy(x => x.Source).ToArray()))
.ToArray();
or a Dictionary<string, Data[]>:
Dictionary<string, Data[]> pathPartitions = array
.GroupBy(x => x.Path)
.ToDictionary(g => g.Key, g => g.OrderBy(x => x.Source).ToArray());

Related

Select list elements contained in another list in linq

I have a string with "|" seperators:
string s = "item1|item2|item3|item4";
a list of objects that each have a name and value:
//object
List<ItemObject> itemList = new List<ItemObject>();
itemList.Add(new ItemObject{Name="item0",Value=0});
itemList.Add(new ItemObject{Name="item1",Value=1});
//class
public class ItemObject(){
public string Name {get;set;}
public int Value {get;set;}
}
How could the following code be done in one line in linq?
var newList = new List<object>();
foreach (var item in s.Split("|"))
{
newList.Add(itemList.FirstOrDefault(x => x.Name == item));
}
// Result: newList
// {Name="item1",Value=1}
I would suggest to start from splitting the string in the beginning. By doing so we won't split it during each iteration:
List<ItemObject> newList = s
.Split("|")
.SelectMany(x => itemList.Where(i => i.Name == x))
.ToList();
Or even better:
List<ItemObject> newList = s
.Split("|") // we can also pass second argument: StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
.Distinct() // remove possible duplicates, we can also specify comparer f.e. StringComparer.CurrentCulture
.SelectMany(x => itemList
.Where(i => string.Equals(i.Name, x))) // it is better to use string.Equals, we can pass comparison as third argument f.e. StringComparison.CurrentCulture
.ToList();
Try this:
var newList = itemList.Where(item => s.Split('|').Contains(item.Name));
The proposed solution also prevents from populating newList with nulls from nonpresent items. You may also consider a more strict string equality check.
string s = "item1|item2|item3|item4";
I don't see a need for splitting this string s. So you could simply do
var newList = itemList.Where(i => s.Contains(i.Name));
For different buggy input you can also do
s = "|" + s + "|";
var newList = itemList.Where(o => s.Contains("|" + o.Name + '|')).ToList();
List<object> newList = itemList.Where(item => s.Split("|").Contains(item.Name)).ToList<object>();

Inverting a Hierarchy with Linq

Given the class
public class Article
{
public string Title { get; set; }
public List<string> Tags { get; set; }
}
and
List<Article> articles;
How can I create a "map" from individual tags (that may be associated with 1 or more articles) with Linq?
Dictionary<string, List<Article>> articlesPerTag;
I know that I can select all of the tags like this
var allTags = articlesPerTag.SelectMany(a => a.Tags);
However, I'm not sure how to associate back from each selected tag to the article it originated from.
I know I can write this conventionally along the lines of
Dictionary<string, List<Article>> map = new Dictionary<string, List<Article>>();
foreach (var a in articles)
{
foreach (var t in a.Tags)
{
List<Article> articlesForTag;
bool found = map.TryGetValue(t, out articlesForTag);
if (found)
articlesForTag.Add(a);
else
map.Add(t, new List<Article>() { a });
}
}
but I would like to understand how to accomplish this with Linq.
If you specifically need it as a dictionary from tags to articles, you could use something like this.
var map = articles.SelectMany(a => a.Tags.Select(t => new { t, a }))
.GroupBy(x => x.t, x => x.a)
.ToDictionary(g => g.Key, g => g.ToList());
Though it would be more efficient to use a lookup instead, it's precisely what you are trying to build up.
var lookup = articles.SelectMany(a => a.Tags.Select(t => new { t, a }))
.ToLookup(x => x.t, x => x.a);
One more way using GroupBy. A bit complicated though.
articles.SelectMany(article => article.Tags)
.Distinct()
.GroupBy(tag => tag, tag => articles.Where(a => a.Tags.Contains(tag)))
.ToDictionary(group => group.Key,
group => group.ToList().Aggregate((x, y) => x.Concat(y).Distinct()));

Improving conversion from List to List<Dictionary<string,string>> with Linq

I've a Key/Value table in my DB and I would return a List of Dictionary.
The following code works fine for me but with a lot of data is not performing.
note: r.name doesn't contains unique value
List<Dictionary<string, string>> listOutput = null;
using (ExampleDB db = new ExampleDB())
{
var result = (from r in db.FormField
where r.Form_id == 1
select new { r.ResponseId, r.name, r.value}).toList();
listOutput = new List<Dictionary<string, string>>();
foreach (var element in result)
{
listOutput.Add((from x in listOutput
where x.ResponseId == element.ResponseId
select x).ToDictionary(x => x.name, x => x.value));
}
}
return listOutput;
Do you have suggestions on how to improve this code ?
I suspect you want something like:
List<Dictionary<string, string>> result;
using (var db = new ExampleDB())
{
result = db.FormField
.Where(r => r.Form_id == 1)
.GroupBy(r => r.ResponseId, r => new { r.name, r.value })
.AsEnumerable()
.Select(g => g.ToDictionary(p => p.name, p => p.value))
.ToList();
}
In other words, we're filtering so that r.Form_id == 1, then grouping by ResponseId... taking all the name/value pairs associated with each ID and creating a dictionary from those name/value pairs.
Note that you're losing the ResponseId in the list of dictionaries - you can't tell which dictionary corresponds to which response ID.
The AsEnumerable part is to make sure that the last Select is performed using LINQ to Objects, rather than trying to convert it into SQL. It's possible that it would work without the AsEnumerable, but it will depend on your provider at the very least.
From what I gather you're trying to create a list of Key/Value pairs based on each ResponseId. Try GroupBy:
var output = result.GroupBy(r => r.ResponseId)
.Select(r => r.ToDictionary(s => s.Name, s => s.Value));
This will return an IEnumerable<Dictionary<string,string>>, which you can ToList if you actually need a list.

Select single item from each group in multiple groups

I have a list (specifically IEnumerable) of items of a specific class:
internal class MyItem
{
public MyItem(DateTime timestamp, string code)
{
Timestamp= timestamp;
Code = code;
}
public DateTime Timestamp { get; private set; }
public string Code { get; private set; }
}
Within this list, there will be multiple items with the same code. Each will have a timestamp, which may or may not be unique.
I'm attempting to retrieve a dictionary of MyItem's (Dictionary<string, MyItem>) where the key is the code associated with the item.
public Dictionary<string, MyItem> GetLatestCodes(IEnumerable<MyItem> items, DateTime latestAllowableTimestamp)
Given this signature, how would I retrieve the MyItem with a timestamp closest to, but not after latestAllowableTimestamp for each code?
For example, given the following for input:
IEnumerable<MyItem> items = new List<MyItem>{
new MyItem(DateTime.Parse("1/1/2014"), "1"),
new MyItem(DateTime.Parse("1/2/2014"), "2"),
new MyItem(DateTime.Parse("1/3/2014"), "1"),
new MyItem(DateTime.Parse("1/4/2014"), "1"),
new MyItem(DateTime.Parse("1/4/2014"), "2")};
If the latestAllowableTimestamp is 1/3/2014, the result would contain only the following items:
Timestamp | Code
----------------
1/3/2014 | 1
1/2/2014 | 2
I can manage to filter the list down to only those timestamps prior to latestAllowableTimestamp, but I don't know linq well enough to pick the most recent for each code and insert it into a dictionary.
var output = items.Where(t => (t.Timestamp <= latestAllowableTimestamp)).GroupBy(t => t.Code);
At this point, I've ended up with two groups, but don't know how to select a single item across each group.
Here is the actual method you are trying to write. It even returns a dictionary and everything:
static Dictionary<string, MyItem> GetLatestCodes(
IEnumerable<MyItem> items, DateTime latestAllowableTimestamp)
{
return items
.Where(item => item.TimeStamp <= latestAllowableTimestamp)
.GroupBy(item => item.Code)
.Select(group => group
.OrderByDescending(item => item.TimeStamp)
.First())
.ToDictionary(item => item.Code);
}
See Enumerable.ToDictionary
This is the your part you should have posted in your question (as LB pointed out)
var list = new List<MyItem>()
{
new MyItem(){ code = "1" , timestamp = new DateTime(2014,1,1)},
new MyItem(){ code = "2" , timestamp = new DateTime(2014,1,2)},
new MyItem(){ code = "1" , timestamp = new DateTime(2014,1,3)},
new MyItem(){ code = "1" , timestamp = new DateTime(2014,1,4)},
new MyItem(){ code = "2" , timestamp = new DateTime(2014,1,4)}
};
DateTime latestAllowableTimestamp = new DateTime(2014, 1, 3);
This is my answer
var result = list.GroupBy(x => x.code)
.Select(x => x.OrderByDescending(y => y.timestamp)
.FirstOrDefault(z => z.timestamp <= latestAllowableTimestamp))
.ToList();
To create your Dictionary, could construct your query like so:
var newDict = items.Where(a => a.Timestamp <= latestAllowableTimestamp)
.GroupBy(b => b.Timestamp)
.ToDictionary(c => c.First().Timestamp, c => c.First());
This should create a Dictionary from your data, with no duplicate days. Note that without the GroupBy query, you'll raise an exception, because ToDictionary doesn't filter out keys it's already seen.
And then if you wanted to get only one MyItem for any given code number, you could use this query:
newDict.Select(a => a.Value)
.OrderByDescending(b => b.Timestamp)
.GroupBy(c => c.Code)
.Select(d => d.First());
The FirstOrDefault query will return only one element from each group. This will give you the MyItem closest to the latest date for any given code.

Using LINQ to build a Dictionary from a List of delimited strings

I have a list of strings that look like this:
abc|key1|486997
def|key1|488979
ghi|key2|998788
gkl|key2|998778
olz|key1|045669
How can I use LINQ and ToDictionary to produce a Dictionary<string, List<string>> that looks like
key1 : { abc|key1|486997, def|key1|488979, olz|key1|045669 }
key2 : { ghi|key2|998788, gkl|key2|998778 }
Basically I want to be able to extract the second element as the key use ToDictionary() to create the dictionary in one go-round.
I'm currently doing this ..
var d = new Dictionary<string, List<string>>();
foreach(var l in values)
{
var b = l.Split('|');
var k = b.ElementAtOrDefault(1);
if (!d.ContainsKey(k))
d.Add(k, new List<string>());
d[k].Add(l);
}
I've seen the questions on building dictionaries from a single string of delimited values, but I'm
wondering if there's an elegant way to do this when starting with a list of delimited strings instead.
var list = new []
{
"abc|key1|486997",
"def|key1|488979",
"ghi|key2|998788",
"gkl|key2|998778",
"olz|key1|045669"
};
var dict = list.GroupBy(x => x.Split('|')[1])
.ToDictionary(x => x.Key, x => x.ToList());
You can also transform it to a lookup (that is very similary to a Dictionary<K,IEnumerable<V>>) in one shot:
var lookup = list.ToLookup(x => x.Split('|')[1]);
var data = new[]
{
"abc|key1|486997",
"def|key1|488979",
"ghi|key2|998788",
"gkl|key2|998778",
"olz|key1|045669"
};
var dictionary = data.Select(row => row.Split('|'))
.GroupBy(row => row[1])
.ToDictionary(group => group.Key, group => group);
If your data is guaranteed to be consistent like that, you could do something like this:
var data = new[]
{
"abc|key1|486997",
"def|key1|488979",
"ghi|key2|998788",
"gkl|key2|998778",
"olz|key1|045669"
};
var items = data
.GroupBy(k => k.Split('|')[1])
.ToDictionary(k => k.Key, v => v.ToList());

Categories