How to split List but keep groupBy - c#

I have a list of key/value pairs in the following form:
[{John:6},{Alex:100},{Peter:4},{Peter,John:5},{Alex,Kati:1}]
I wonder if there is a simple linq expression I can use to translate the list into
[{John:11},{Alex:101},{Peter:9},{Kati:1}]
ie split string by comma and adjust counts.
the list above is coming from following LINQ
var list = people.Where(a => !string.IsNullOrWhiteSpace(a.Name))
.GroupBy(a => a.Name.Trim()).Select(a => new User { Name = a.Key, Items= a.Count() });

With
var list = new[]
{
new KeyValuePair<string, int>("John", 6),
new KeyValuePair<string, int>("Alex", 100),
new KeyValuePair<string, int>("Peter", 4),
new KeyValuePair<string, int>("Peter,John", 5),
new KeyValuePair<string, int>("Alex,Kati", 1)
};
this grouping
var modifiedList = list.SelectMany(p => p.Key.Split(',').Select(n => new {Name = n, Number = p.Value}))
.GroupBy(p => p.Name).Select(g => new KeyValuePair<string, int>(g.Key, g.Sum(r => r.Number)));
gives you the output
{[John, 11]}
{[Alex, 101]}
{[Peter, 9]}
{[Kati, 1]}

Try this:
var list = new List<KeyValuePair<string, int>>
{
new KeyValuePair<string,int>("John", 6),
new KeyValuePair<string,int>("Alex", 100),
new KeyValuePair<string,int>("Peter", 4),
new KeyValuePair<string,int>("Peter,John", 5),
new KeyValuePair<string,int>("Alex,Kati", 1)
};
var result = list.SelectMany(x => x.Key.Split(','),
(x, y) => new KeyValuePair<string, int>(y, x.Value))
.GroupBy(x => x.Key)
.ToDictionary(key => key.Key, value => value.Sum(x => x.Value));

Dictionary<string, int> result = keyVals
.SelectMany(kv => kv.Key.Split(',').Select(name => new{ name, kv.Value }))
.GroupBy(x => x.name)
.ToDictionary(xg => xg.Key, xg => xg.Sum(x => x.Value));
Result:
{[John, 11]}
{[Alex, 101]}
{[Peter, 9]}
{[Kati, 1]}
Demo

So you have classes like this
public class Person
{
public string Name { get; set; }
}
public class User
{
public string Name { get; set; }
public int Items { get; set; }
}
If I understand correctly you want to count how many times each name occurs
var people = new[]
{
new Person { Name = "John" },
new Person { Name = "John,Alex" },
new Person { Name = "Alex" },
new Person { Name ="Peter,John" }
};
var list = people.SelectMany(p => p.Name.Split(','))
.GroupBy(n => n)
.Select(g => new User { Name = g.Key, Items = g.Count() });

Related

Linq GroupBy Max within Max

var list = new[]
{
new { maker="Volvo", type=1, model=15},
new { maker="Volvo", type=8, model=10},
new { maker="Volvo", type=8, model=100},
new { maker="Volvo", type=8, model=40},
new { maker="Volvo", type=6, model=5},
new { maker="Volvo", type=2, model=0},
new { maker="Volvo", type=1, model=2},
new { maker="GM", type=1, model=0},
new { maker="GM", type=0, model=20},
new { maker="GM", type=9, model=5},
new { maker="GM", type=9, model=50},
new { maker="GM", type=9, model=25},
};
var results = list
.GroupBy(x => x.maker, (key, g) => g.OrderByDescending(e => e.type).First())
.ToList();
Returns
{ maker = Volvo, type = 8, model = 10 }
{ maker = GM, type = 9, model = 5 }
Which is closed to what I want and if I keep extending it to
var results = list
.GroupBy(x => x.maker, (key, g) => g.OrderByDescending(e => e.type).GroupBy(z => z.type, (key1, y) => y.OrderByDescending(u => u.model).First()))
.ToList();
Returns nothing
vs. Expected result
{ maker = Volvo, type = 8, model = 100 }
{ maker = GM, type = 9, model = 50 }
So how do I fix this?
You just need a ThenByDescending instead of another GroupBy:
var results = list
.GroupBy(x => x.maker,
(key, g) => g.OrderByDescending(e => e.type)
.ThenByDescending(e => e.model)
.First())
.ToList();

Agregate grouping using c#

I have a grouped object that looks like
Dictionary<(ushort id, ushort sc), Timepoint[]> timepoints
and it would look like (1, 2) => [some string timepoints]
But I want to convert it to
Dictionary<ushort id, Timepoint[]>
and I want to aggregate that sc and have only the id. I tried:
test = timepoints.GroupBy(group => group.Key.id).ToDictionary(key => key.Key, value => value);
But I had no luck all.
Dictionary<ushort, IGrouping<ushort, KeyValuePair<(ushort id, ushort sc), Timepoint[]>>>
I think I am missing something.
You need to use a SelectMany to flatten out the arrays you are grouping together and turn them into one array first.
var test = timepoints
.GroupBy(kvp => kvp.Key.id)
.Select(grp => new { grp.Key, Values = grp.SelectMany(x => x.Value).ToArray() })
.ToDictionary(x => x.Key, x => x.Values);
Extended and tested version of #juharr's answer;
timepoints
.GroupBy(kvp => kvp.Key.id)
.Select(grp => new { grp.Key, Values = grp.SelectMany(x => x.Value).ToArray() })
.ToDictionary(x => x.Key, x => x.Values);
Here is the whole test console app you can try out:
static void Main(string[] args)
{
Dictionary<(ushort id, ushort sc), Timepoint[]> timepoints = new Dictionary<(ushort id, ushort sc), Timepoint[]>();
timepoints.Add((1, 1), new Timepoint[] { new Timepoint(1, "1,1,1"), new Timepoint(1, "1,1,2"), new Timepoint(1, "1,1,3") });
timepoints.Add((1, 2), new Timepoint[] { new Timepoint(1, "1,2,1"), new Timepoint(1, "1,2,2"), new Timepoint(1, "1,2,3") });
timepoints.Add((2, 1), new Timepoint[] { new Timepoint(1, "2,1,1"), new Timepoint(1, "2,1,2"), new Timepoint(1, "2,1,3") });
timepoints.Add((2, 2), new Timepoint[] { new Timepoint(1, "2,2,1"), new Timepoint(1, "2,2,2"), new Timepoint(1, "2,2,3") });
var test = timepoints
.GroupBy(kvp => kvp.Key.id)
.Select(grp => new { grp.Key, Values = grp.SelectMany(x => x.Value).ToArray() })
.ToDictionary(x => x.Key, x => x.Values);
}
 
class Timepoint
{
public Timepoint(int id, string name)
{
this.Id = id;
this.Name = name;
}
public int Id { get; set; }
public string Name { get; set; }
}

LinqToExcel to load Dictionary

I have the following worked out but quite less than elegant. I'd like to work this out with ToDictionary if possible. Thank you for any help as I'm pretty new.
var excel = new ExcelQueryFactory(#"E:\MAHipotCepaStationProgram.xlsx");
//get list of program names
List<string> testNames = new List<string>();
testNames.AddRange(excel.Worksheet().ToList()
.Where(s => s["Program #"].Value.ToString() == "Program Title")
.Select(s => s[1].Value.ToString()));
// get list of program numbers
List<int> testNumbers = new List<int>();
testNumbers.AddRange(excel.Worksheet().ToList()
.Where(s => s["Program #"].Value.ToString() == "Program #")
.Select(s => Convert.ToInt32(s[1].Value)));
// combine them
Dictionary<int, string> programs = new Dictionary<int, string>();
for (int x = 0; x < testNames.Count-1; x++)
{
if (!programs.ContainsKey(Convert.ToInt32(testNumbers[x])))
{
programs.Add(Convert.ToInt32(testNumbers[x]), testNames[x]);
}
else
{
testNumbers[x].Dump("Duplicate Found");
}
}
programs.Dump("Dict");
This is as close as I've gotten, but not right. Error:
Requires a receiver of type IEnumerable string
which isn't computing with me:
var excel = new ExcelQueryFactory(#"E:\MAHipotCepaStationProgram.xlsx");
Dictionary<string, string> programsDict = excel.Worksheet().ToDictionary<string, string>(
e => e["Program #"].Value.ToString() == "Program Title")
.Select(s => s[1].Value.ToString()),
f => f.Where(d => d.Value.ToString() == "Program #").ToString());
You can filter the values using a sigle LINQ query.This will return the name and number columns in the excel:
var sampleExcel = new ExcelQueryFactory(#"I:\Book1.xlsx");
var sampleWorksheet = from workSheet in sampleExcel.Worksheet("Sheet1") select workSheet;
var selectedValues = from excelRow in sampleExcel.Worksheet()
select new { name = excelRow[0], number =Convert.ToInt32(excelRow[1]) };
foreach (var item in selectedValues)
{
Console.WriteLine(string.Format("Name is {0} ,number is {1}",item.name,item.number));
}
Dictionary<int, string> dict = new Dictionary<int, string>();
foreach (var item in selectedValues)
{
dict.Add(item.number, item.name);
Console.WriteLine(string.Format("Name is {0} ,number is {1}", item.name, item.number));
}
Equivalent lambda expression for the above LINQ query:
var selectedValues1 = sampleExcel.Worksheet().Select(x => new { name = x[0], number = x[1] });
Have a go at this:
Dictionary<int, string> programsDict =
excel
.Worksheet()
.Select(x => new { A = x[0].ToString(), B = x[1].ToString() })
.ToArray()
.Where(x => new [] { "Program #", "Program Title" }.Contains(x.A))
.Buffer(2)
.Select(x => new { title = x[0].B, number = int.Parse(x[1].B) })
.ToDictionary(x => x.number, x => x.title);
You just need to NuGet "System.Interactive" to get the .Buffer(int) operator.
Or use this implementation:
public static IEnumerable<T[]> Buffer<T>(this IEnumerable<T> source, int count)
=>
source
.Select((t, i) => new { t, i })
.GroupBy(x => x.i / count)
.Select(x => x.Select(y => y.t).ToArray());

Turning a Dictionary<Guid,IList<String>> into Dictionary<string,IList<Guid>> With LINQ?

I have a Dictionary<Guid,IList<string>> which shows all the names an entity can have.
I want to convert this to see all the names mapped to all the entities.
so:
[["FFF" => "a", "b"],
["EEE" => "a", "c"]]
Becomes
[["a" => "FFF", "EEE"],
["b" => "FFF"],
["c" => "EEE"]]
I know this is easy to do with foreaches but I'm wondering if there is a way with LINQ / ToDictionary?
private static void Main(string[] args)
{
var source = new Dictionary<Guid, IList<string>>
{
{ Guid.NewGuid(), new List<string> { "a", "b" } },
{ Guid.NewGuid(), new List<string> { "b", "c" } },
};
var result = source
.SelectMany(x => x.Value, (x, y) => new { Key = y, Value = x.Key })
.GroupBy(x => x.Key)
.ToDictionary(x => x.Key, x => x.Select(y => y.Value).ToList());
foreach (var item in result)
{
Console.WriteLine($"Key: {item.Key}, Values: {string.Join(", ", item.Value)}");
}
}
var dic = new Dictionary<string, List<string>>()
{
{"FFF", new List<string>(){"a", "b"}},
{"EEE", new List<string>(){"a", "c"}}
};
var res = dic.SelectMany(x => x.Value, (x,y) => new{Key = y, Value = x.Key})
.ToLookup(x => x.Key, x => x.Value);
Dictionary<int,IList<string>> d = new Dictionary<int ,IList<string>>(){
{1,new string[]{"a","b"}},
{2,new string[]{"a","d"}},
{3,new string[]{"b","c"}},
{4,new string[]{"x","y"}}};
d.SelectMany(kvp => kvp.Value.Select(element => new { kvp.Key, element}))
.GroupBy(g => g.element, g => g.Key)
.ToDictionary(g => g.Key, g => g.ToList());

Aggregating Lists of Users (Strings)

I have a Dictionary that looks like the following, with the key being an Integer and the value being a List of strings:
var x = new Dictionary<int, List<string>>;
I would like to see if any of those Lists match each other (without being in order) so that I can group them together in a role.
The final solution will look like
var y = new Dictionary<string, List<int>>
Where the List<int> is the keys from var x. The string key will be a machine generated string such as a guid, etc.
You can map all values to their keys and then group them by value and then apply ToDictionary, for expected result.
var data = new Dictionary<int, List<string>>
{
{ 1, new List<string> { "Adam", "Lucie" } },
{ 2, new List<string> { "Adam", "Hannah" } },
{ 3, new List<string> { "John", "Rachel" } },
{ 4, new List<string> { "Bill", "Hannah" } },
};
var result = data.SelectMany(p => p.Value.Select(v => new {Key = p.Key, Value = v}))
.GroupBy(o => o.Value)
.ToDictionary(g => g.Key, g => g.Select(v => v.Key));
foreach (var keyValues in result)
{
Console.WriteLine(keyValues.Key + ": " + string.Join(", ", keyValues.Value));
}

Categories

Resources