I have a dynamic form which I cannot use MVC's binding with. When I post this FormCollection to my controller, simplified, I have the following data in the form collection:
public ActionResult Foo(FormCollection coll)
...
coll["Data0Key"] contains "category"
coll["Data0Value"] contains "123"
coll["Data1Key"] contains "location"
coll["Data1Value"] contains "21"
coll["Data7Key"] contains "area"
coll["Data7Value"] contains "test"
coll["SomethingElse"] contains "irrelevent"
.. I have an unknown number of these and would like to create key-value pairs from the seperate key and value objects in the collection
I have been attempting along the lines of;
var settings = coll.AllKeys
.Where(k => k.StartsWith("Data"))
.ToDictionary(k => k, k => coll[k]);
which gives me a dictionary of:
Data0Key, category
Data0Value, 123
Data1Key, location
Data1Value, 21
Data7Key, area
Data7Value, test
What I would really like to have is a collection of key value pairs structured like;
category, 123
location, 21
area, test
Is what I am trying to achieve possible at all, or do I need to find a different approach?
I think you want to only iterate over the "DataxKey" parts, then look up the values. Something like:
.Where(k => k.StartsWith("Data") && k.EndsWith("Key"))
.ToDictionary(k => coll[k], k => coll[k.Replace("Key", "Value")]);
This assumes that every "Data0Key" also has a "Data0Value" matching pair, otherwise it's going to dereference a key that doesn't exist from coll.
This can be done with a relatively straightforward LINQ query:
var data = new Dictionary<string,object> {
["Data0Key"] = "category"
, ["Data0Value"] = "123"
, ["Data1Key"] = "location"
, ["Data1Value"] = "21"
, ["Data7Key"] = "area"
, ["Data7Value"] = "test"
, ["SomethingElse"] = "irrelevent"
};
var kvp = data
.Where(p => p.Key.StartsWith("Data") && (p.Key.EndsWith("Key") || p.Key.EndsWith("Value")))
.Select(p => new {
Key = p.Key.Substring(0, p.Key.Length - (p.Key.EndsWith("Key") ? 3 : 5))
, IsKey = p.Key.EndsWith("Key")
, p.Value
})
.GroupBy(p => p.Key)
.Where(g => g.Count(p => p.IsKey) == 1 && g.Count(p => !p.IsKey) == 1)
.ToDictionary(g => (string)g.Single(p => p.IsKey).Value, g => g.Single(p => !p.IsKey).Value);
foreach (var p in kvp) {
Console.WriteLine("{0} - {1}", p.Key, p.Value);
}
Here is a line-by-line explanation of what is done:
First, irrelevant items are filtered out by ensuring that only "Data" prefixes are kept, and that the suffixes are "Key" or "Value"
Next, group key is extracted by removing "Key" or "Value" suffix; the Value is added to the list, along with IsKey flag indicating if an item was a key or a value
Items are grouped by the group key ("Data0", "Data7", etc.)
Each group is checked to contain exactly one key and one value; incomplete groups are discarded
Finally, groups are converted to a dictionary of key-value pairs.
Related
I am working on project which is asp.net mvc core. I want to replace string list of duplicate values to one with comma separated,
List<string> stringList = surveylist.Split('&').ToList();
I have string list
This generate following output:
7=55
6=33
5=MCC
4=GHI
3=ABC
1003=DEF
1003=ABC
1=JKL
And I want to change output like this
7=55
6=33
5=MCC
4=GHI
3=ABC
1003=DEF,ABC
1=JKL
Duplicate items values should be comma separated.
There are probably 20 ways to do this. One simple one would be:
List<string> newStringList = stringList
.Select(a => new { KeyValue = a.Split("=") })
.GroupBy(a => a.KeyValue[0])
.Select(a => $"{a.Select(x => x.KeyValue[0]).First()}={string.Join(",", a.Select(x => x.KeyValue[1]))}")
.ToList();
Take a look at your output. Notice that an equal sign separates each string into a key-value pair. Think about how you want to approach this problem. Is a list of strings really the structure you want to build on? You could take a different approach and use a list of KeyValuePairs or a Dictionary instead.
If you really need to do it with a List, then look at the methods LINQ's Enumerable has to offer. Namely Select and GroupBy.
You can use Select to split once more on the equal sign: .Select(s => s.Split('=')).
You can use GroupBy to group values by a key: .GroupBy(pair => pair[0]).
To join it back to a string, you can use a Select again.
An end result could look something like this:
List<string> stringList = values.Split('&')
.Select(s => {
string[] pair = s.Split('=');
return new { Key = pair[0], Value = pair[1] };
})
.GroupBy(pair => pair.Key)
.Select(g => string.Concat(
g.Key,
'=',
string.Join(
", ",
g.Select(pair => pair.Value)
)
))
.ToList();
The group contains pairs so you need to select the value of each pair and join them into a string.
I have the following table structure. I want to retrieve value corresponding to key for each group name and insert it store it sequentially in a model class
the table data has been read using a ExecuteQuery and stored in a list of class object, in the below example I will have 4 rows returned. I need to convert it into 2 rows, the column values coming in as rows.
I have the following code written now, But is there any other way to verify it without explicitly checking for say Key == "GroupSpecificProperty1"?
If there is a 3rd category added later, I shouldn't have to modify this code
Result = results.GroupBy(p => p.GroupName )
.Select(g => new FinalModel()
{
GroupName = g.Select(p => p.GroupName ).FirstOrDefault(),
GroupSpecificProperty1 = g.Where(q => q.Key == "GroupSpecificProperty1").Select(v => v.Value).Cast<string>().FirstOrDefault(),
GroupSpecificProperty2= g.Where(q => q.Key == "GroupSpecificProperty2").Select(v => v.Value).Cast<string>().FirstOrDefault(),
}).ToList();
results.GroupBy(p => p.GroupName)
.Select(g => new FinalModel
{
GroupName = g.Key,
Properties = g.ToDictionary(item => item.Key, item=> item.Value)
});
And in the case that for a given GroupName the keys are not unique and you'd want to avoid a "key already exists" exception then you can:
results.GroupBy(p => p.GroupName)
.Select(g => new FinalModel
{
GroupName = g.Key,
Properties = g.GroupBy(item => item.key)
.ToDictionary(innerGroup => innerGroup.Key,
innerGroup => innerGroup.Select(innerItem => innerItem.Value))
});
Then of course you can also replace the outer/inner dictionary with a LookUp if it fits your needs better.
I've checked many solutions on different sites but couldn't find what I was looking for. I'm working on a dictionary object with different Values against Keys. The structure is as follows:
Key Value
6 4
3 4
2 2
1 1
If they dictionary contains elements like this, the output should be 6 and 3, if Key (6) has the highest value, it should print only 6. However, if all the values are same against each key, it should print all the keys.
Trying to use the following but it only prints the highest Value.
var Keys_ = dicCommon.GroupBy(x => x.Value).Max(p => p.Key);
Any ideas
Instead of using Max(x=>x.Key) use .OrderByDescending(x=>x.Key) and .FirstOrDefault() that will give you the group that has the max value. You then can itterate over the group and display whatever you need.
var dicCommon = new Dictionary<int, int>();
dicCommon.Add(6, 4);
dicCommon.Add(3, 4);
dicCommon.Add(2, 2);
dicCommon.Add(1, 1);
var maxGroup = dicCommon.GroupBy(x => x.Value).OrderByDescending(x => x.Key).FirstOrDefault();
foreach (var keyValuePair in maxGroup)
{
Console.WriteLine("Key: {0}, Value {1}", keyValuePair.Key, keyValuePair.Value);
}
Run Code
First off a query can't return one and more than one result at the same time.So you need to pick one.
In this case if you want all Keys that has the highest corresponding Value, you can sort the groups based on Value then just get the first group which has the highest Value:
var Keys_ = dicCommon.GroupBy(x => x.Value)
.OrderByDescending(g => g.Key)
.First()
.Select(x => x.Key)
.ToList();
var keys = String.Join(",", dicCommon
.OrderByDescending(x=>x.Value)
.GroupBy(x => x.Value)
.First()
.Select(x=>x.Key));
You’re almost there:
dicCommon.GroupBy(x => x.Value)
.OrderByDescending(pair => pair.First().Value)
.First().Select(pair => pair.Key).ToList()
GroupBy returns an enumerable of IGrouping. So sort these descending by value, then get the first, and select the key of each containing element.
Since this requires sorting, the runtime complexity is not linear, although we can easily do that. One way would be figuring out the maximum value first and then getting all the keys where the value is equal to that:
int maxValue = dicCommon.Max(x => x.Value);
List<int> maxKeys = dicCommon.Where(x => x.Value == maxValue).Select(x => x.Key).ToList();
Here is my code:
IEnumerable<ServiceTicket> troubletickets = db.ServiceTickets.Include(t => t.Company).Include(t => t.UserProfile);
var ticketGroups = new Dictionary<string, List<ServiceTicket>>();
ticketGroups = troubletickets
.GroupBy(o => o.DueDate).ToDictionary(
group => {
var firstOrDefault = #group.FirstOrDefault();
return firstOrDefault != null
? firstOrDefault.DueDate.HasValue
? firstOrDefault.DueDate.Value.ToShortDateString()
: ""
: "";
},
group => group.ToList()
).OrderBy(g => g.Key).ToDictionary(g => g.Key, g => g.Value);
The error that I am getting is: 'An item with the same key has already been added.' This is because the DueDate value is occasionally repeated. My question is how can I keep the key from being added if it already exists in the dictionary?
It seems that you are grouping by one value (the DueDate value), but using a different value as the dictionary key.
Can you not just use the custom code for grouping instead?
ticketGroups = troubletickets
.GroupBy(o => o.DueDate.HasValue
? o.DueDate.Value.ToShortDateString()
: "")
.ToDictionary(g => g.Key, g => g.ToList());
Note that I took our the superfluous OrderBy and second ToDictionary call - I assumed you were trying to "order" the dictionary which won't work as a plain dictionary is not ordered.
You get duplicate keys because there are two ways to get an empty string as key, either an empty group, or an empty date. The duplicate will always be the empty string. I wonder if you really intended to get an empty string as key when the group is empty. Anyway, it's not necessary, you can always filter empty groups later.
It's easier to group by date (including null) first through the database engine and then apply string formatting in memory:
IQueryable<ServiceTicket> troubletickets = db.ServiceTickets
.Include(t => t.Company)
.Include(t => t.UserProfile);
Dictionary<string, List<ServiceTicket>> ticketGroups =
troubletickets
.GroupBy(ticket => ticket.DueDate)
.AsEnumerable() // Continue in memory
.ToDictionary(g => g.Key.HasValue
? g.Key.Value.ToShortDateString()
: string.Empty,
g => g.Select(ticket => ticket));
Now the grouping is by the Key value, not by the First element in the group. The Key is never null, it's always a Nullable<DateTime>, with or without a value.
Side note: you'll notice that EF will not generate a SQL group by statement, that's because the SQL statement is "destructive": it only returns grouped columns and aggregate data, not the individual records that a LINQ GroupBy does return. For this reason, the generated SQL is pretty bloated and it may enhance performance if you place the AsEnumerable before the .GroupBy.
// Below is an array which contains master data
string[] masterData = { "324", "233", "32", "423", "23435" };
// Below string will always contain a comma separated subset of above master data
string abc = "233,423,99";
What I need is to have a dictionary having string key of all masterData array values & have the value set to 1 only for those keys which are in the string abc. All other keys need value of 0. In this case, dictionary should be of type string & value should be of type uint. In this case, dictionary should be as shown below:
String uint
"324" 0
"233" 1
"32" 0
"423" 1
"23435" 0
Note that 99 from string is ignored as only all master data should be present in dictionary.
Is there a way to do it using linq? If not, what other shorted way is it possible with?
Also, how do I perform the above operation in the reverse way using linq or any other shorted approach i.e. convert dictionary<string, uint> back to comma separated string? And this string should contain only those keys from the dictionary whose value is 1.
Note: dictionary value can be either 1 or 0 only.
Any help is much appreciated.
var abcArray = abc.Split(',');
masterData.ToDictionary(p => p, p => (uint)(abcArray.Contains(p) ? 1 : 0));
You can use Concat to add together the first array and the split string, then use GroupBy to group the same numbers, then just create the Dictionary from that, the duplicated items will have a Count greater than 1.
var dict = masterData
.Concat(abc.Split(','))
.GroupBy(x => x)
.ToDictionary(k => k.Key, v => v.Count() > 1 ? 1 : 0);
To reverse, select all the strings where the Value is 1 and Join the sting
string result = string.Join(",", dict.Where(x => x.Value > 0)
.Select(k => k.Key)
.ToArray());
string abc = "233,423,99";
var subset = abc.Split(',');
var result = masterData.ToDictionary(k => k, k => Convert.ToUInt32(subset.Contains(k)));
var reversedResult = result.Where(p => p.Value == 1).Select(p => p.Key).ToArray();
https://dotnetfiddle.net/5eU5B0