A Dictionary of Dictionary from IQueryable - c#

I would like a method to return a Dictionary of Dictionary from an IQueryable formula. Typically,
Dictionary<int, Dictionary<DateTime, string>>
At first, I looked at ToDictionary() of course but couldn't find a way to declare two one after the other.
Then, i loooked at ToLookup() and I have a way to carry this with a ILookup with the string being my secondary dictionary(the DateTime.Tostring() + the other string)... you follow ? :)
But I don't find the ILookup solution really confortable (parsing a date to string and when i receive the data -> string to date.. beuark)
That would give something like that
return this.ttdc.Trackers.GroupBy(t => new { TrackerTaskId = t.TrackerTaskID, DateGenerated = t.TrackerDateGenerated })
.Select(t => new { taskId = t.Key.TrackerTaskId, DateGenerated = t.Key.DateGenerated, count = t.Count() })
.ToLookup(k => k.taskId.Value, k => k.DateGenerated.Value.ToString() + "|" + k.count);
Now, i'm thinking about creating a List of self created class with the 3 informations I need as properties.
Could you help me chosing the best pattern ? This method is hosted on a windows service and I would like to limit the amount of data transfered to the client.
Thank you

Here's an example, using GroupBy and ToDictionary:
var testValues = new[]{new {ID = 1, Date = new DateTime(2010,1,1), Str = "Val1"},
new {ID = 1, Date = new DateTime(2011,2,2), Str = "Val2"},
new {ID = 2, Date = new DateTime(2010,1,1), Str = "Val3"}};
var dict = testValues.GroupBy(item => item.ID)
.ToDictionary(grp => grp.Key,
grp => grp.ToDictionary(item => item.Date,
item => item.Str));

Related

Improving O(n^2) algorithm

I have the following piece of code:
var keywordItems = adwordsService
.ParseReport(report)
.Where(e => e.Keyword.IndexOf('+') == -1);
var keywordTranslations = keywordTranslationService
.GetKeywordTranslationsByClient(id);
model.KeywordItems = keywordItems
.Where(e =>
{
int lastUnderscore = e.CampaignName.LastIndexOf('_');
var identifer = e.CampaignName.Substring(lastUnderscore + 1);
var translation = keywordTranslations
.FirstOrDefault(t => t.translation == e.Keyword &&
t.LocalCombination_id == identifer);
return translation == null;
})
.OrderBy(e => e.Keyword);
It receives an array and then filters each of these element based on whether or not they've already been seen before.
However, this runs pretty slow, as there's a lot of new elements, so I would like it, if someone can point me in the right direction regarding the best algorithm to use in this case.
Simple join will do the job - it uses hashset for matching between collections, which gives you O(1) for search operation:
from k in keywordItems
let identifer = k.CampaignName.Substring(k.CampaignName.LastIndexOf('_') + 1)
join t in keywordTranslations on
new { k.Keyword, Id = identifer } equals
new { Keyword = t.translation, Id = t.LocalCombination_id } into g
where !g.Any()
orderby k.Keyword
select k
To further improve performance you can move identifier extraction directly to the key creation. Thus you will omit introducing new range variable.
I suggest using hashing, e.g. HashSet<T> or Dictionary<T>. Providing that translation as well as LocalCombination_id are of type string:
HashSet<Tuple<string, int>> keywordTranslations =
new HashSet<Tuple<string, string>>(keywordTranslationService
.GetKeywordTranslationsByClient(id)
.Select(t => new Tuple<string, int>(t.translation, t.LocalCombination_id)));
model.KeywordItems = keywordItems
.Where(e => !keywordTranslations.Contains(new Tuple<string, string>(
e.Keyword,
e.CampaignName.Substring(e.CampaignName.LastIndexOf('_') + 1))))
.OrderBy(e => e.Keyword);

Identifying and grouping similar items in a collection of strings

I have a collection of strings like the following:
List<string> codes = new List<string>
{
"44.01", "44.02", "44.03", "44.04", "44.05", "44.06", "44.07", "44.08", "46", "47.10"
};
Each string is made up of two components separated by a full stop - a prefix code and a subcode. Some of the strings don't have sub codes.
I want to be able combine the strings whose prefixes are the same and output them as follows with the other codes also:
44(01,02,03,04,05,06,07,08),46,47.10
I'm stuck at the first hurdle of this, which is how to identify and group together the codes whose prefix values are the same, so that I can combine them into a single string as you can see above.
You can do:
var query = codes.Select(c =>
new
{
SplitArray = c.Split('.'), //to avoid multiple split
Value = c
})
.Select(c => new
{
Prefix = c.SplitArray.First(), //you can avoid multiple split if you split first and use it later
PostFix = c.SplitArray.Last(),
Value = c.Value,
})
.GroupBy(r => r.Prefix)
.Select(grp => new
{
Key = grp.Key,
Items = grp.Count() > 1 ? String.Join(",", grp.Select(t => t.PostFix)) : "",
Value = grp.First().Value,
});
This is how it works:
Split each item in the list on the delimiter and populate an anonymous type with Prefix, Postfix and original value
Later group on Prefix
after that select the values and the post fix values using string.Join
For output:
foreach (var item in query)
{
if(String.IsNullOrWhiteSpace(item.Items))
Console.WriteLine(item.Value);
else
Console.WriteLine("{0}({1})", item.Key, item.Items);
}
Output would be:
44(01,02,03,04,05,06,07,08)
46
47.10
Try this:-
var result = codes.Select(x => new { SplitArr = x.Split('.'), OriginalValue = x })
.GroupBy(x => x.SplitArr[0])
.Select(x => new
{
Prefix= x.Key,
subCode = x.Count() > 1 ?
String.Join(",", x.Select(z => z.SplitArray[1])) : "",
OriginalValue = x.First().OriginalValue
});
You can print your desired output like this:-
foreach (var item in result)
{
Console.Write("{0}({1}),",item.Prefix,item.subCode);
}
Working Fiddle.
Outlined idea:
Use Dictionary<string, List<string>> for collecting your result
in a loop over your list, use string.split() .. the first element will be your Dictionary key ... create a new List<string> there if the key doesn't exist yet
if the result of split has a second element, append that to the List
use a second loop to format that Dictionary to your output string
Of course, linq is possible too, e.g.
List<string> codes = new List<string>() {
"44.01", "44.05", "47", "42.02", "44.03" };
var result = string.Join(",",
codes.OrderBy(x => x)
.Select(x => x.Split('.'))
.GroupBy(x => x[0])
.Select((x) =>
{
if (x.Count() == 0) return x.Key;
else if (x.Count() == 1) return string.Join(".", x.First());
else return x.Key + "(" + string.Join(",", x.Select(e => e[1]).ToArray()) + ")";
}).ToArray());
Gotta love linq ... haha ... I think this is a monster.
You can do it all in one clever LINQ:
var grouped = codes.Select(x => x.Split('.'))
.Select(x => new
{
Prefix = int.Parse(x[0]),
Subcode = x.Length > 1 ? int.Parse(x[1]) : (int?)null
})
.GroupBy(k => k.Prefix)
.Select(g => new
{
Prefix = g.Key,
Subcodes = g.Where(s => s.Subcode.HasValue).Select(s => s.Subcode)
})
.Select(x =>
x.Prefix +
(x.Subcodes.Count() == 1 ? string.Format(".{0}", x.Subcodes.First()) :
x.Subcodes.Count() > 1 ? string.Format("({0})", string.Join(",", x.Subcodes))
: string.Empty)
).ToArray();
First it splits by Code and Subcode
Group by you Code, and get all Subcodes as a collection
Select it in the appropriate format
Looking at the problem, I think you should stop just before the last Select and let the data presentation be done in another part/method of your application.
The old fashioned way:
List<string> codes = new List<string>() {"44.01", "44.05", "47", "42.02", "44.03" };
string output=""
for (int i=0;i<list.count;i++)
{
string [] items= (codes[i]+"..").split('.') ;
int pos1=output.IndexOf(","+items[0]+"(") ;
if (pos1<0) output+=","+items[0]+"("+items[1]+")" ; // first occurence of code : add it
else
{ // Code already inserted : find the insert point
int pos2=output.Substring(pos1).IndexOf(')') ;
output=output.Substring(0,pos2)+","+items[1]+output.Substring(pos2) ;
}
}
if (output.Length>0) output=output.Substring(1).replace("()","") ;
This will work, including the correct formats for no subcodes, a single subcode, multiple subcodes. It also doesn't assume the prefix or subcodes are numeric, so it leaves leading zeros as is. Your question didn't show what to do in the case you have a prefix without subcode AND the same prefix with subcode, so it may not work in that edge case (44,44.01). I have it so that it ignores the prefix without subcode in that edge case.
List<string> codes = new List<string>
{
"44.01", "44.02", "44.03", "44.04", "44.05", "44.06", "44.07", "44.08", "46", "47.10"
};
var result=codes.Select(x => (x+".").Split('.'))
.Select(x => new
{
Prefix = x[0],
Subcode = x[1]
})
.GroupBy(k => k.Prefix)
.Select(g => new
{
Prefix = g.Key,
Subcodes = g.Where(s => s.Subcode!="").Select(s => s.Subcode)
})
.Select(x =>
x.Prefix +
(x.Subcodes.Count() == 0 ? string.Empty :
string.Format(x.Subcodes.Count()>1?"({0})":".{0}",
string.Join(",", x.Subcodes)))
).ToArray();
General idea, but i'm sure replacing the Substring calls with Regex would be a lot better as well
List<string> newCodes = new List<string>()
foreach (string sub1 in codes.Select(item => item.Substring(0,2)).Distinct)
{
StringBuilder code = new StringBuilder();
code.Append("sub1(");
foreach (string sub2 in codes.Where(item => item.Substring(0,2) == sub1).Select(item => item.Substring(2))
code.Append(sub2 + ",");
code.Append(")");
newCodes.Add(code.ToString());
}
You could go a couple ways... I could see you making a Dictionary<string,List<string>> so that you could have "44" map to a list of {".01", ".02", ".03", etc.} This would require you processing the codes before adding them to this list (i.e. separating out the two parts of the code and handling the case where there is only one part).
Or you could put them into a a SortedSet and provide your own Comparator which knows that these are codes and how to sort them (at least that'd be more reliable than grouping them alphabetically). Iterating over this SortedSet would still require special logic, though, so perhaps the Dictionary to List option above is still preferable.
In either case you would still need to handle a special case "46" where there is no second element in the code. In the dictionary example, would you insert a String.Empty into the list? Not sure what you'd output if you got a list {"46", "46.1"} -- would you display as "46(null,1)" or... "46(0,1)"... or "46(,1)" or "46(1)"?

Get sum of the value from list using linq?

I am trying to get the sum of the value from list of list using linq ?my data is as below code
List<List<string>> allData = new List<List<string>>();
using (StreamReader reader = new StreamReader(path))
{
while (!reader.EndOfStream)
{
List<string> dataList;
dataList = reader.ReadLine().Split('|').ToList();
allData.Add(dataList);
}
}
which gives me data in allData as below
allData-->[0]-->[0]-'name1'
[1]-'sub'
[2]-'12'
[1]-->[0]-'name2'
[1]-'sub'
[2]-'15'
[2]-->[0]-'name1'
[1]-'sub2'
[2]-'15'
//and so on ....
i have applied group by that gives me grouping by the name but i am not able to figure out how to get the sum of the marks for each name ?
var grouped = allData.GroupBy(x => x[0]);
after this i get all matching name grouped into one but now how to get sum of the marks for that group ? any help would be great ?
Output should be name1=27 and name2=15 and so on.
Not sure if you want to get the sum of every group or the total. If it's the total then this should do the trick
var sum = allData.Sum(x => Int32.Parse(x[2]));
If it's per key then try the following
var all = allData
.GroupBy(x => x[0])
.Select(x => x.Sum(y => Int32.Parse(y[2]));
var grouped = allData.GroupBy(x => x[0])
.Select(g => new
{
Name = g.Key,
Sum = g.Sum(x => int.Parse(x[2]))
});
It will return an anonymous type instance for each group, with two properties: Name with your grouping key and Sum with sum of marks.
Sticking as much as possible to the LINQ query language:
var grouped = from d in allData
group d by i[0] into g
select new
{
Name = g.Key,
Sum = g.Sum(i => int.Parse(i[2]))
};
This will give you parallel List with each name and the count of how many times each name occurs.
var names = grouped.Select(s => s.Key).ToList();
var nameCount = grouped.Select(s => s.Count()).ToList();
Also... you may want to add this when assigning alldata to grouped. I use this to get a List from greatest to least amount of occurrences.
var grouped = allData.GroupBy(x => x[0]).OrderByDescending(i => i.Count());

How do i sum a list of items by code(or any field)?

I have an object that has a list of another object in it. i.e Object1 contains List<Object2>.
Assuming this is the definition of object 2:
public class Object2
{
string code,
string name,
decimal amount
}
I want to be a able to make a list2 from the list whose value will contain what something similar to what a select name, code, sum(amount) group by code kinda statement could have given me
this is what i did but it didnt contain what i needed on passing through.
var newlist = obj2List.GroupBy(x => x.code)
.Select(g => new { Amount = g.Sum(x => x.amount) });
I want code and name in the new list just like the sql statement above.
You're almost there:
var newlist = obj2List.GroupBy(x => x.code)
.Select(g => new
{
Code = g.First().code,
Name = g.First().name,
Amount = g.Sum(x => x.amount)
});
This groups the items by code and creates an anonymous object for each group, taking the code and name of first item of the group. (I assume that all items with the same code also have the same name.)
If you are grouping by code and not by name you'd have to choose something for name from the list, perhaps with First() or Last() or something.
var newlist = obj2List.GroupBy(x => x.code).Select(g => new {
Code = g.Key,
Name = g.First().name,
Amount = g.Sum(x => x.amount)
});
var query = Object1.Obj2List
.GroupBy(obj2 => obj2.code)
.Select(g => new {
Names = string.Join(",", g.Select(obj2.name)),
Code = g.Key,
Amount = g.Sum(obj2 => obj2.Amount)
});
Since you group by code only you need to aggregate the name also in some way. I have used string.Join to create a string like "Name1,Name2,Name3" for each code-group.
Now you could consume the query for example with a foreach:
foreach(var x in query)
{
Console.WriteLine("Code: {0} Names: {1} Amount: {2}"
, x.Code, x.Names, x.Amount);
}
Instead of using the LINQ Extension Methods .GroupBy() and .Select() you could also use a pure LINQ statement which is way easier to read if you come from a SQL Background.
var ls = new List<Object2>();
var newLs = from obj in ls
group obj by obj.code into codeGroup
select new { code = codeGroup.Key, amount = codeGroup.Sum(s => s.amount) };

How to use LINQ to select into an object?

I have data that looks like so:
UserId | SongId
-------- --------
1 1
1 4
1 12
2 95
I also have the following class:
class SongsForUser
{
public int User;
public List<int> Songs;
}
What I would like to do is use LINQ to select from my data to create a collection of SongsForUser objects. Below is what I have come up with so far:
var userCombos = songs.UserSongs.Select(x => new SongsForUser() { User = x.UserId,
Songs = /*What goes here?*/ });
How would I go about populating my Songs List?
So the result should be two SongsForUser objects. For user 1 it would have 3 items in the Songs list. For user 2 it would have 1 item in the Songs list.
songs.UserSongs.GroupBy(x => x.User).Select(g => new SongsForUser()
{
User = g.Key,
Songs = g.Select(s => s.SongId).ToList()
});
I suspect you want:
var songsByUser = songs.UserSongs
.GroupBy(song => song.UserId, song => song.SongId)
.Select(g => new SongsForUser { User = g.Key,
Songs = g.ToList() });
To explain, after the GroupBy you'll have a bunch of groups, where the key of each group is the user ID, and the values within the group are the song IDs:
Key = 1, Values = 1, 4, 12
Key = 2, Value = 95
Then you're just converting that into your SongsForUser type. Note that you don't need to explicitly include the () when calling the constructor in an object initializer - it's implicit unless you need to specify constructor arguments.
You could do this all in one GroupBy call, by the way:
var songsByUser = songs.UserSongs
.GroupBy(song => song.UserId, song => song.SongId,
(user, ids) => new SongsForUser { User = user,
Songs = ids.ToList() });
Personally I usually find a separate Select call to be more readable.
You can also do all of this with a query expression:
var songsByUser = from song in songs.UserSongs
group song.SongId by song.UserId into g
select new SongsForUser { User = g.Key, Songs = g.ToList() };
EDIT: The above is "provider-neutral" but it sounds like it's not working with LINQ to Entities. You may be able to get it to work like this:
var songsByUser = songs.UserSongs
.GroupBy(song => song.UserId, song => song.SongId)
.AsEnumerable()
.Select(g => new SongsForUser { User = g.Key,
Songs = g.ToList() });
The AsEnumerable call will force the grouping to be done in the database, but the final projection (including the ToList call) to be done locally. You should check the generated SQL for efficiency though.
Lets say you have the following:
public class SongsForUser
{
public int UserId;
public List<int> Songs;
}
Then a function like this one here will do. The list is just there to have
some data to test with.
public void Group()
{
List<Tuple<int, int>> SongRelations = new List<Tuple<int, int>>();
SongRelations.Add(new Tuple<int, int>(1, 1));
SongRelations.Add(new Tuple<int, int>(1, 4));
SongRelations.Add(new Tuple<int, int>(1, 12));
SongRelations.Add(new Tuple<int, int>(2, 95));
var list = SongRelations.GroupBy(s => s.Item1)
.Select(r => new SongsForUser()
{
UserId = r.Key,
Songs = r.Select(t => t.Item2).ToList(),
});
}
list contains 2 items of type SongsForUser afterwards.
One with user 1 and a list of songs containing 1, 4 and 12
and one with user 2 and a list of songs containing 95.
In its simplest form you can just:
List<MapPoint> points = db.PropertyResearches.Where(a => a.deptId == 66).Select(x => new MapPoint { property = x.notes.Substring(0, 10), latitude = x.lat, longitude = x.#long }).ToList();

Categories

Resources