LINQ using .ToDictionary without .Select - c#

I have the following dictionary:
Dictionary<string, string> clauses = new Dictionary<string, string>();
where the clauses are like this:
"A|B" - "some text"
"A|D|E" - "some text"
"G" - "some text"
"E|A" - "some text"
...
and I want to populate the dictionary below:
Dictionary<string, int> columnsBitMap = new Dictionary<string, int>();
where the string values are the unique letters of the first dictionary strings and int values are calculated by math formula.
I have the following which is working perfectly:
columnsBitMap = String.Join("|", clauses.Select(clause => clause).Select(clause => clause.Key)).Split('|')
.Distinct().OrderBy(column => column)
-- can I remove the next Select ?
.Select((column, index) => new KeyValuePair<string, int>(column, index))
.ToDictionary(column => column.Key, column => Convert.ToInt32(Math.Pow(2, column.Value)));
but I am wondering if this could be simplified removing the .Select part?
The output should be like this:
A 1
B 2
D 4
E 8
G 16

This bit is completely superfluous:
.Select(clause => clause)
Just remove it and the rest should work fine.
I dont see much reason to get rid of the part
.Select((column, index) => new KeyValuePair<string, int>(column, index))
But if you're against using a KeyValuePair<TKey,TValue> you could just make it an anonymous object
.Select((column, index) => new{ Key = column, Value = index })
But there's not a great amount of difference.
I approached your requirement in a slightly different way:
var result = clauses.SelectMany(clause => clause.Key.Split('|'))
.Distinct().OrderBy(column => column)
.Select((column, index) => new {Key=column,Value=index})
.ToDictionary(column => column.Key, column => Convert.ToInt32(Math.Pow(2, column.Value)));
Working example with your test case: http://rextester.com/PWC41147

Related

Order Dictionary by the result of other dictionary

Looking for someone to help me with C#, LINQ.
I have a Dictionary<int,int> that I am ordering like so: .OrderBy(_ => _.Value).ThenBy(_ => ThisMethodReturnsAnotherIReadOnlyDict<int,int>()).ThenByDescending(_ => _.Key).
What I want is to order the first dictionary by its value and then if there are still equal values I want that tie to be broke by the ThisMethodReturnsAnotherIReadOnlyDict<int,int>(). This first key/value of this ThisMethodReturnsAnotherIReadOnlyDict to break the tie and be on top. And finally, if everything fails, then order by it's key descending.
Some data for this like:
(First Dictionary)
[1,400]
[2,550]
[3,200]
[4,200]
(Second dictionary)
[3,50]
[4,140]
[2,600]
[1,700]
For this scenario I want my ordering to return: [3,50]
Can anyone help please?
Thanks!
It looks like you're looking for something like this:
var firstDict = new Dictionary<int, int>() {
{1,400},
{2,550},
{3,200},
{4,200}
};
var secondDict = new Dictionary<int, int>() {
{3,50},
{4,140},
{2,600},
{1,700}
};
var result = (from kvp in firstDict
join tieBreaker in secondDict on kvp.Key equals tieBreaker.Key
select new { kvp.Key, V1 = kvp.Value, V2 = tieBreaker.Value })
.OrderBy(x => x.V1)
.ThenBy(x => x.V2)
.ThenByDescending(x => x.Key)
.First();
This will join the first and second dictionaries together by its keys and will, respectively, order by the value of the first dictionary, the value of the second dictionary and then descending by the key itself.
How about:
var ans = firstDict
.OrderBy(kv => kv.Value)
.ThenBy(kv => ThisMethodReturnsAnotherIReadOnlyDict<int,int>().TryGetValue(kv.Key, out var val2) ? val2 : kv.Key)
.ToList();
Unless ThisMethodReturnsAnotherIReadOnlyDict<int,int> may change, you may want to cache the return value in a variable before sorting.

How to combine duplicate values in dictionary into one value

I have dictionary with duplicate values. How to join this duplicate values into one value
Example:
Accord - first
Accord.s - first
I want to see something like:
Accord, Accord.s - first
If I've understood you right, you have a dictionary
Dictionary<string, string> source = new Dictionary<string, string>() {
{"Accord", "first"},
{"Accord.s", "first"},
{"Gamma", "second"},
};
and you want to group by Value, which you can do with a help of Linq:
using System.Linq;
...
// If you want to create a dictionary:
Dictionary<string, string> result = source
.GroupBy(pair => pair.Value)
.ToDictionary(
chunk => string.Join(", ", chunk.Select(pair => pair.Key)),
chunk => chunk.Key);
string report = string.Join(Environment.NewLine, result
.Select(pair => $"{pair.Key} : {pair.Value}"));
Console.Write(report);
Outcome:
Accord, Accord.s : first
Gamma : second
In case you want just a query (not dictionary)
var result = source
.GroupBy(pair => pair.Value)
.Select(chunk => new {
Key = string.Join(", ", chunk.Select(pair => pair.Key)),
Value = chunk.Key});
// and then
string report = string.Join(Environment.NewLine, result
.Select(pair => $"{pair.Key} : {pair.Value}"));

How to use ToDictionary with <string, object> dictionary

I am trying to create a dictionary that can hold multiple values per key, and I have created an class called Pair that consists of two strings. I have defined idDictionary to contain a string as the key, and Pair as the value, but I am unsure how to write the ToDictionary statement as this concept is new to me, and I couldn't find any examples of this.
Dictionary<string, Pair<string, string>> idDictionary = new Dictionary<string, Pair<string, string>>();
I know with a regular generic Dictionary of I would simply use something like this:
idDictionary = resultData.Rows.Select(row => row.Split(','))
.ToDictionary(id => id[0], id => id[1]);
I am not sure how I would implement something similar for the object called Pair. Maybe I'm missing something really simple, but many thanks to those with answers.
EDIT to include full code block and more thorough explanation
The original code block is here (with a generic dictionary). The reason I am changing this is due to the fact that if there is more than 1 value per key, the application errors out due to duplicate keys.
private List<ImportItem<T>> ProcessReportResult(CSVTable resultData, ICollection<ImportItem<T>> data, Func<T, string> keyFilter)
{
WriteLog("{1}{0} records found.{1}", resultData.Rows.Length, Environment.NewLine);
//key = Order Number; value = Order ID
var idDictionary = resultData.Rows.Select((row => row.Split(','))).ToDictionary(id => id[0], id => id[1]);
idDictionary.ForEach(id => WriteLog("Input Id = {0} - Matching record Id = {1}", id.Key, id.Value));
var processList = data.Where(item => idDictionary.ContainsKey(keyFilter(item.DataItem))).ToList();
processList.ForEach(item => item.id = idDictionary[keyFilter(item.DataItem)]);
return processList;
}
A genral solution to get the one to many key value store can be achieved via grouping but that would require to have value as List of items. If I try to explain it via your given sample then the query to convert the rows to per key multi value store can be created by:
idDictionary =
resultData.Rows
.GroupBy(row => row.Id, row => row.Split(','))
.ToDictionary(g => g.Key, g => g.ToList());
Update:
Specific solution to your problem. Assuming that data would have structure something like:
List<Row> rows = new List<Row>{
new Row{
values = "1,A"
},
new Row{
values = "2,C,D,E"
},
new DataRow{
values = "3,E,X,CV,B"
},
};
You can use the Group here as well to get the Key, Value(List). Note, Here I have skipped the first value which is already captured as key at index 0.
var idDictionary =
rows.GroupBy(row => row.values.Split(',')[0],
row => row.values.Split(',').Skip(1))
.ToDictionary(g => g.Key, g => g.ToList());
This will give you the result like:
/* output -
|1, (A)|
|2, (C,D,E)|
|3, (E,X,CV,B)|
*/
Though you have to change the implementation for fetching the values via List.
But this solution will prevent the Program if there are more than one values found per key.
Not sure exactly what you need maybe this simple example will help?
idDictionary = resultData.Rows
.Select((row => row.Split(',')))
.ToDictionary<string, Pair<string, string>>
(id => id[0],id => new Pair(id[1],id[1]));
This version of ToDictionary takes two functions, one that returns the key and one that returns the value for each item in the enumeration.
You'll have to decide whether you want a tuple-based approach (or pair even) if you know how many items are in each row or if you need to consider that each row may have a different number of items.
// Setup sample data
var resultData = new
{
Rows = new string[] { "1,A,B,C", "2,A,B", "3,A,B,C,D" }
};
// If same length for each row, tuple would work easily
// Dictionary<string, Tuple<string, string>>
var tuples = resultData.Rows
.Select(r => r.Split(','))
.ToDictionary(
r => r[0],
r => Tuple.Create(r[1], r[2])
);
// If length is variable, then some type of collection could be better
// Dictionary<string, List<string>>
var lists = resultData.Rows
.Select(r => r.Split(','))
.ToDictionary(
r => r[0],
r => r.Skip(1).ToList() // Skip adding id element
);
Here is the output for the 1st item to compare each:
?lists["1"]
Count = 3
[0]: "A"
[1]: "B"
[2]: "C"
?tuples["1"]
{(A, B)}
Item1: "A"
Item2: "B"
The original code block is here (with a generic dictionary). The reason I am changing this is due to the fact that if there is more than 1 value per key, the application errors out due to duplicate keys.
Seems like what you are looking for is ToLookup
"Lookup<TKey, TElement>
represents a collection of keys each mapped to one or more values."
.
var idDictionary = resultData.Rows.Select((row => row.Split(',')))
.ToLookup(id => id[0], id => id[1]);
EDIT
A short sample:
var lines = new string[] { "a,b", "a,c", "d,e" };
var dict = lines.Select(line => line.Split(','))
.ToLookup(x => x[0], x => x[1]);
result:
Key: a Value: [b,c]
Key: e Value: [e]
Sample usage:
Console.WriteLine(string.Join(",", dict["a"]));

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 Unique values from List<Dictionary<string, string>>

I have List<Dictionary<string, string>> object with some datas in it.
/* Values in the list will be like
[0] -
aaa - aaaValue1 (Key, Value)
bbb - bbbValue1
ccc - cccValue1
ddd - dddValue1
[1] -
aaa - aaaValue2 (Key, Value)
bbb - bbbValue2
ccc - cccValue2
ddd - dddValue2
and so on */
I want to get the distinct values( List<string> ) in the dictionary where the key is equal to "ccc" and the value of the key "bbb" is equal to "bbbValue1".
Expected Result:
Return a string list contains the dictionary value where key is equal to "ccc" and the value of the key "bbb" is equal to "bbbValue1" in the List<Dictionary<string, string>>.
I think you want:
var result = testData.Where(dict => dict.ContainsKey("EmpNo"))
.Select(dict => dict["EmpNo"])
.Distinct()
.ToList();
or if you want the result as a set:
var result = new HashSet<string>(from dict in testData
where dict.ContainsKey("EmpNo")
select dict["EmpNo"]);
EDIT:
You've changed your question completely, which isn't a nice thing to do (ask a new one instead), but to answer it in its current state:
var result = testData.Where(dict => dict.ContainsKey("ccc")
&& dict.ContainsKey("bbb")
&& dict["bbb"] == "bbbValue1")
.Select(dict => dict["ccc"])
.Distinct()
.ToList()
Think it will be better to flatten list like this:
testData.SelectMany(x => x)
.Where(x => x.Key == "EmpNo")
.Select(x => x.Value)
.Distinct()
.ToList();
I think this will give you the correct result:
var result = testData.SelectMany(dict => dict)
.Where(dict => dict.Key.Equals("ccc") || (d.Key.Equals("bbb") && d.Value.Equals("bbbValue1")))
.Select(d => d.Value).Distinct().ToList();

Categories

Resources