Trying to merge two string array based on comparision - c#

Below is my class :
public class Regions
{
public int Id { get; set; }
public string[] ParentName { get; set; }
}
Now I have 2 list of above regions class like below containing some data:
var region1 = new Regions();
var region2 = new Regions();
Now ParentName contains data like below for region1 :
[0] : Abc.mp3,Pqr.mp3
[1] : Xxx.mp3
[2] : kkk.mp3
[3] : ppp.mp3,zzz.mp3,rrr.mp3,ddd.mp3
Now ParentName contains data like below for region2 :
[0] : Abc.mp3,Pqr.mp3,lmn.mp3
[1] : rrr.mp3,ggg.mp3,yyy.mp3
Now I am trying to merge ParentName of region2 in to region1 if any part of region1 is matching with region2 after splitting records by comma like below :
[0] : Abc.mp3,Pqr.mp3,lmn.mp3
[1] : Xxx.mp3
[2] : kkk.mp3
[3] : ppp.mp3,zzz.mp3,rrr.mp3,ddd.mp3,ggg.mp3,yyy.mp3
Now in above expected output, Abc.mp3 and Pqr.Mp3(Region1 and Region2) is matching only Lmn.mp3 is not matching so it will be appended at the end of Region1.
For the last record from region1 and region2, rrr.mp3 is matching(single match is also enough) so non matching record from region2 i.e ggg.mp3,yyy.mp3 will be appended at the end of region1.
Output I am getting in Region1:
[0] : Abc.mp3,Pqr.mp3
[1] : Xxx.mp3
[2] : kkk.mp3
[3] : ppp.mp3,zzz.mp3,rrr.mp3,ddd.mp3
[4] : Abc.mp3,Pqr.mp3,lmn.mp3
[3] : rrr.mp3,ggg.mp3,yyy.mp3
Code :
region1.ParentName = region1.ParentName.Concat(region2.ParentName).Distinct().ToArray();
public static T[] Concat<T>(this T[] x, T[] y)
{
if (x == null) throw new ArgumentNullException("x");
if (y == null) throw new ArgumentNullException("y");
int oldLen = x.Length;
Array.Resize<T>(ref x, x.Length + y.Length);
Array.Copy(y, 0, x, oldLen, y.Length);
return x;
}

It's unclear if your names contain duplicates and how they should be handled, but here is the LINQ solution which produces the desired result with the specified inputs:
var e2Sets = region2.ParentName.Select(e2 => e2.Split(',')).ToList();
var result =
from e1 in region1.ParentName
let e1Set = e1.Split(',')
let e2AppendSet = (
from e2Set in e2Sets
where e1Set.Intersect(e2Set).Any()
from e2New in e2Set.Except(e1Set)
select e2New
).Distinct()
select string.Join(",", e1Set.Concat(e2AppendSet));
result.ToArray() will give you the desired new region1.ParentName.
How it works:
Since we basically need Cartesian product of the two input sequences, we start by preparing a list of the arrays of split strings of the second sequence, in order to avoid multiple string.Split inside the inner loop.
The for each element of the first sequence, we split it to array of strings, the for each split array in the second sequence which has a match (determined with Intersect method) we select the unmatched strings using the Except method. Then we flatten all the unmatched strings, apply Distinct to remove the potential duplicates, concatenate the two sets and use string.Join to produce the new comma delimited string.

You could do the following:
public static void Merge(Regions first, Regions second)
{
if (ReferenceEquals(first, null))
throw new ArgumentNullException(nameof(first));
if (ReferenceEquals(second, null))
throw new ArgumentNullException(nameof(second));
first.ParentName = first.ParentName.Merge(second.ParentName).ToArray();
}
private static IEnumerable<string> Merge(this IEnumerable<string> first, IEnumerable<string> second)
{
if (ReferenceEquals(first, null))
throw new ArgumentNullException(nameof(first));
if (ReferenceEquals(second, null))
throw new ArgumentNullException(nameof(second));
foreach (var f in first)
{
yield return f.Merge(second, ',');
}
}
private static string Merge(this string first, IEnumerable<string> second, char separator)
{
Debug.Assert(first != null);
Debug.Assert(second != null);
var firstSplitted = first.Split(separator);
foreach (var s in second)
{
var sSplitted = s.Split(separator);
if (firstSplitted.Intersect(sSplitted).Any())
return string.Join(separator.ToString(), firstSplitted.Union(sSplitted));
}
return first;
}
Note that this will merge on the first match it finds; if duplicate values exist, it will only merge the first time the match is encountered.
The secret here is divide and conquer. If you are having trouble implementing a certain logic, then break it down into simpler steps and implement a method for each baby step. Once its working, if you really need to, you can refactor your code to make it more concise or performant.
If you run this:
var first = new Regions();
var second = new Regions();
first.ParentName = new[] { "Abc.mp3,Pqr.mp3", "Xxx.mp3", "kkk.mp3", "ppp.mp3,zzz.mp3,rrr.mp3,ddd.mp3" };
second.ParentName = new[] { "Abc.mp3,Pqr.mp3,lmn.mp3", "rrr.mp3,ggg.mp3,yyy.mp3" };
Merge(first, second);
You will get the expected result. first.ParentName will be:
[0]: "Abc.mp3,Pqr.mp3,lmn.mp3"
[1]: "Xxx.mp3"
[2]: "kkk.mp3"
[3]: "ppp.mp3,zzz.mp3,rrr.mp3,ddd.mp3,ggg.mp3,yyy.mp3"

You can use Split() method to get parts of string and find matches and Join() method to get final string:
private static void Merge(Regions region, Regions region2)
{
List<List<string>> splittedLists = region.ParentName.Select(p => p.Split(new char[] { ',' }, StringSplitOptions.None).ToList()).ToList();
List<List<string>> splittedLists2 = region2.ParentName.Select(p => p.Split(new char[] { ',' }, StringSplitOptions.None).ToList()).ToList();
List<string> res = new List<string>();
foreach (var item in splittedLists)
{
bool wasMatch = false;
foreach (var s in item)
{
bool contains = false;
foreach (var s2 in splittedLists2.Where(s2 => s2.Contains(s)))
{
wasMatch = true;
contains = true;
res.Add(string.Join(",", item.Concat(s2).Distinct()));
}
if (contains)
{
contains = false;
break;
}
}
if (!wasMatch)
{
res.Add(string.Join(",", item));
}
}
region.ParentName = res.ToArray();
}

Related

How to parse a text file with alternating lines of names and lists of integers?

I need to read a file and put that data inside to different arrays.
My .txt file looks like:
w1;
1 2 3
w2;
3 4 5
w3;
4 5 6
I tried something like the following:
int[] w1 = new int [3];
int[] w2 = new int [3];
int[] w3 = new int [3];
string v = "w1:|w2:|w3:";
foreach (string line in File.ReadAllLines(#"D:\\Data.txt"))
{
string[] parts = Regex.Split(line, v);
I got that string but I have no idea how to cut every element of it to arrays showed above.
Rather than parsing the file and putting the arrays into three hardcoded variables corresponding to hardcoded names w1, w2 and w3, I would remove the hardcoding and parse the file into a Dictionary<string, int[]> like so:
public static class DataFileExtensions
{
public static Dictionary<string, int[]> ParseDataFile(string fileName)
{
var separators = new [] { ' ' };
var query = from pair in File.ReadLines(fileName).Chunk(2)
let key = pair[0].TrimEnd(';')
let value = (pair.Count < 2 ? "" : pair[1]).Split(separators, StringSplitOptions.RemoveEmptyEntries).Select(s => int.Parse(s, NumberFormatInfo.InvariantInfo)).ToArray()
select new { key, value };
return query.ToDictionary(p => p.key, p => p.value);
}
}
public static class EnumerableExtensions
{
// Adapted from the answer to "Split List into Sublists with LINQ" by casperOne
// https://stackoverflow.com/questions/419019/split-list-into-sublists-with-linq/
// https://stackoverflow.com/a/419058
// https://stackoverflow.com/users/50776/casperone
public static IEnumerable<List<T>> Chunk<T>(this IEnumerable<T> enumerable, int groupSize)
{
// The list to return.
List<T> list = new List<T>(groupSize);
// Cycle through all of the items.
foreach (T item in enumerable)
{
// Add the item.
list.Add(item);
// If the list has the number of elements, return that.
if (list.Count == groupSize)
{
// Return the list.
yield return list;
// Set the list to a new list.
list = new List<T>(groupSize);
}
}
// Return the remainder if there is any,
if (list.Count != 0)
{
// Return the list.
yield return list;
}
}
}
And you would use it as follows:
var dictionary = DataFileExtensions.ParseDataFile(fileName);
Console.WriteLine("Result of parsing {0}, encountered {1} data arrays:", fileName, dictionary.Count);
foreach (var pair in dictionary)
{
var name = pair.Key;
var data = pair.Value;
Console.WriteLine(" Data row name = {0}, values = [{1}]", name, string.Join(",", data));
}
Which outputs:
Result of parsing Question49341548.txt, encountered 3 data arrays:
Data row name = w1, values = [1,2,3]
Data row name = w2, values = [3,4,5]
Data row name = w3, values = [4,5,6]
Notes:
I parse the integer values using NumberFormatInfo.InvariantInfo to ensure consistency of parsing in all locales.
I break the lines of the file into chunks of two by using a lightly modified version of the method from this answer to Split List into Sublists with LINQ by casperOne.
After breaking the file into chunks of pairs of lines, I trim the ; from the first line in each pair and use that as the dictionary key. The second line in each pair gets parsed into an array of integer values.
If the names w1, w2 and so on are not unique, you could deserialize instead into a Lookup<string, int []> by replacing ToDictionary() with ToLookup().
Rather than loading the entire file into memory upfront using File.ReadAllLines(), I enumerate though it sequentially using File.ReadLines(). This should reduce memory usage without any additional complexity.
Sample working .Net fiddle.
Your RegEx doesn't actually do anything, you already have an array with each line separated. What you want to do is just ignore the lines that aren't data:
var lines = File.ReadAllLines(#"D:\\Data.txt");
for (int i = 1; i < lines.Length; i += 2) // i.e indexes 1, 3 and 5
{
string[] numbers = lines[i].Split(' ');
}
Or, you could just assign given that you know the order:
w1 = lines[1].Split(' ');
w2 = lines[3].Split(' ');
w3 = lines[5].Split(' ');

In C#, how can I force iteration over IEnumerable within nested foreach loops?

I have two IEnumerables:
IEnumerable<string> first = ...
IEnumerable<string> second = ...
I want to create a second IEnumerable<string> that is the concatenation of each element of each IEnumerable.
For example:
IEnumerable<string> first = new [] {"a", "b"};
IEnumerable<string> second = new [] {"c", "d"};
foreach (string one in first)
{
foreach (string two in second)
{
yield return string.Format("{0} {1}", one, two);
}
}
This would produce:
"a c"; "a d"; "b c"; "b d";
The problem is, sometimes one of the two IEnumerables is empty:
IEnumerable<string> first = new string[0];
IEnumerable<string> second = new [] {"c", "d"};
In this case, the nested foreach construct never reaches the yield return statement. When either IEnumerable is empty, I would like the result to just be the list of the non-empty IEnumerable.
How can I produce the combinations I am looking for?
EDIT:
In reality, I have three different IEnumerables I am trying to combine, so adding if conditions for every possible permutation of empty IEnumerable seems bad. If that's the only way, then I guess I'll have to do it that way.
You can simply check that first enumerable is not empty:
IEnumerable<string> first = new [] {"a", "b"};
IEnumerable<string> second = new [] {"c", "d"};
var firstList = first.ToList();
if (!firstList.Any()) {
return second;
}
foreach (string one in firstList)
{
foreach (string two in second)
{
yield return string.Format("{0} {1}", one, two);
}
}
To eliminate double IEnumerable evaluation in positive cases just convert first enumerable to list
Simply use Enumerable.DefaultIfEmpty() to enumerate collection even if there is no items.
IEnumerable<string> first = new string[0];
IEnumerable<string> second = new[] { "a", "b" };
IEnumerable<string> third = new[] { "c", null, "d" };
var permutations =
from one in first.DefaultIfEmpty()
from two in second.DefaultIfEmpty()
from three in third.DefaultIfEmpty()
select String.Join(" ", NotEmpty(one, two, three));
Note: I have used String.Join to join items which are not null or empty and method to select non-empty items to be joined (you can inline this code if you don't want to have a separate method):
private static IEnumerable<string> NotEmpty(params string[] items)
{
return items.Where(s => !String.IsNullOrEmpty(s));
}
Output for sample above is
[ "a c", "a", "a d", "b c", "b", "b d" ]
For two collections and foreach loops (though I would prefere LINQ as above):
IEnumerable<string> first = new[] { "a", "b" };
IEnumerable<string> second = new string[0];
foreach(var one in first.DefaultIfEmpty())
{
foreach(var two in second.DefaultIfEmpty())
yield return $"{one} {two}".Trim(); // with two items simple Trim() can be used
}
Output:
[ "a", "b" ]
Your current approach should work until any of the collections is empty. If this is the case you need some check in front:
if(!first.Any())
foreach(var e in second) yield return e;
else if(!second.Any())
foreach(var e in first) yield return e;
foreach (string one in first)
{
foreach (string two in second)
{
yield return string.Format("{0} {1}", one, two);
}
}
However you should consider making an immediate execution using ToList in front to avoid multiple iterations of the same collection.
Assuming you're output for case :
IEnumerable<string> first = new string[0];
IEnumerable<string> second = new [] {"c", "d"};
would be :
c
d
This would work :
var query = from x in first.Any() ? first : new [] { "" }
from y in second.Any() ? second : new[] { "" }
select x + y;
Less code , easier to maintain and debug !
Edit : If you have any other IEnumerable is just 1 extra line per IEnumerable ( includes the check )
var query = from x in first.Any() ? first : new [] { "" }
from y in second.Any() ? second : new[] { "" }
from z in third.Any() ? third : new[] { "" }
select x + y + z;
Edit 2 : you can just add the spaces at the end :
select (x + y + z).Aggregate(string.Empty, (c, i) => c + i + ' ');
If you have more than a couple lists, you can setup a recursive iterator. You'll want to be mindful of the stack, and I think the string concatenation is less than ideal, and passing lists of lists is rather clunky, but this should get you started.
using System;
using System.Collections.Generic;
using System.Linq;
namespace en
{
class Program
{
static void Main(string[] args)
{
// three sample lists, for demonstration purposes.
var a = new List<string>() { "a", "b", "c" };
var b = new List<string>() { "1", "2", "3" };
var c = new List<string>() { "i", "ii", "iii" };
// the function needs everything in one argument, so create a list of the lists.
var lists = new List<List<string>>() { a, b, c };
var en = DoStuff(lists).GetEnumerator();
while (en.MoveNext())
{
Console.WriteLine(en.Current);
}
}
// This is the internal function. I only made it private because the "prefix" variable
// is mostly for internal use, but there might be a use case for exposing that ...
private static IEnumerable<String> DoStuffRecursive(IEnumerable<String> prefix, IEnumerable<IEnumerable<String>> lists)
{
// start with a sanity check
if (object.ReferenceEquals(null, lists) || lists.Count() == 0)
{
yield return String.Empty;
}
// Figure out how far along iteration is
var len = lists.Count();
// down to one list. This is the exit point of the recursive function.
if (len == 1)
{
// Grab the final list from the parameter and iterate over the values.
// Create the final string to be returned here.
var currentList = lists.First();
foreach (var item in currentList)
{
var result = prefix.ToList();
result.Add(item);
yield return String.Join(" ", result);
}
}
else
{
// Split the parameter. Take the first list from the parameter and
// separate it from the remaining lists. Those will be handled
// in deeper calls.
var currentList = lists.First();
var remainingLists = lists.Skip(1);
foreach (var item in currentList)
{
var iterationPrefix = prefix.ToList();
iterationPrefix.Add(item);
// here's where the magic happens. You can't return a recursive function
// call, but you can return the results from a recursive function call.
// http://stackoverflow.com/a/2055944/1462295
foreach (var x in DoStuffRecursive(iterationPrefix, remainingLists))
{
yield return x;
}
}
}
}
// public function. Only difference from the private function is the prefix is implied.
public static IEnumerable<String> DoStuff(IEnumerable<IEnumerable<String>> lists)
{
return DoStuffRecursive(new List<String>(), lists);
}
}
}
console output:
a 1 i
a 1 ii
a 1 iii
a 2 i
a 2 ii
a 2 iii
a 3 i
a 3 ii
a 3 iii
b 1 i
b 1 ii
b 1 iii
b 2 i
b 2 ii
b 2 iii
b 3 i
b 3 ii
b 3 iii
c 1 i
c 1 ii
c 1 iii
c 2 i
c 2 ii
c 2 iii
c 3 i
c 3 ii
c 3 iii

Get string from another string array if value matches

String Array 1: (In this format: <MENU>|<Not Served?>|<Alternate item served>)
Burger|True|Sandwich
Pizza|True|Hot Dog
String Array 2: (Contains Menu)
Burger
Pizza
Grill Chicken
Pasta
I need the menu is served or any alternate item served for that particular item.
Code:
for(int i = 0; i < strArr2.Length; i++)
{
if(strArr2.Any(_r => _r.Split('|').Any(_rS => _rS.Contains(strArr1[i]))))
{
var menu = strArr2[i];
var alternate = ? // need to get alternate item
}
}
As I commented in the code, how to get the alternate item in that string array? Please help, thanks in advance.
P.S: Any help to trim if condition is also gladly welcome.
Instead of any, you may use Where to get the value matching.
#Markus is having the detailed answer, I am just using your code to find a quick fix for you.
for(int i = 0; i < strArr2.Length; i++)
{
if(strArr2.Any(_r => _r.Split('|').Any(_rS => _rS.Contains(strArr1[i]))))
{
var menu = strArr2[i];
var alternate = strArr2.Where(_rs => _rs.Split('|').Any(_rS => _rS.Contains(strArr1[i]))).First().Split('|').Last();
}
}
In order to simplify your code, it is a good idea to better separate the tasks. For instance, it will be much easier to handle the contents of string array 1 after you have converted the contents into objects, e.g.
class NotServedMenu
{
public string Menu { get; set; }
public bool NotServed { get; set; }
public string AlternateMenu { get; set; }
}
Instead of having an array of strings, you can read the strings to a list first:
private IEnumerable<NotServedMenu> NotServedMenusFromStrings(IEnumerable<string> strings)
{
return (from x in strings select ParseNotServedMenuFromString(x)).ToArray();
}
private NotServedMenu ParseNotServedMenuFromString(string str)
{
var parts = str.Split('|');
// Validate
if (parts.Length != 3)
throw new ArgumentException(string.Format("Unable to parse \"{0}\" to an object of type {1}", str, typeof(NotServedMenu).FullName));
bool notServedVal;
if (!bool.TryParse(parts[1], out notServedVal))
throw new ArgumentException(string.Format("Unable to read bool value from \"{0}\" in string \"{1}\".", parts[1], str));
// Create object
return new NotServedMenu() { Menu = parts[0],
NotServed = notServedVal,
AlternateMenu = parts[2] };
}
Once you can use the objects, the subsequent code will be much cleaner to read:
var notServedMenusStr = new[]
{
"Burger|True|Sandwich",
"Pizza|True|Hot Dog"
};
var notServedMenus = NotServedMenusFromStrings(notServedMenusStr);
var menus = new[]
{
"Burger",
"Pizza",
"Grill Chicken",
"Pasta"
};
var alternateMenus = (from m in menus join n in notServedMenus on m equals n.Menu select n);
foreach(var m in alternateMenus)
Console.WriteLine("{0}, {1}, {2}", m.Menu, m.NotServed, m.AlternateMenu);
In this sample, I've used a Linq join to find the matching items.
You could do something like that
string[] strArr1 = { "Burger|True|Sandwich", "Pizza|True|Hot Dog" };
string[] strArr2 = { "Burger", "Pizza", "Grill Chicken", "Pasta" };
foreach (string str2 in strArr2)
{
string str1 = strArr1.FirstOrDefault(str => str.Contains(str2));
if (str1 != null)
{
string[] splited = str1.Split('|');
string first = splited[0];
bool condition = Convert.ToBoolean(splited[1]);
string second = splited[2];
}
}

Sort a list in C#

How to sort a List in such a way item of list matching string comes first.
Suppose if i have
"vishal pandey"
in string then item of list matching "vishal pandey" comes first then it should show result of item containing "vishal" and item containing "pandey"
It is not possible for me bring data in that order from database
Currently I am getting list like this
var matchedProjects = (from project in unitOfWork.ProjectRepository.All()
where project.IsActive
&& project is Project
&& (
queryList.Contains(project.Name)
|| project.Name.StartsWith(query)
|| project.Name.Contains(query)
|| project.Name.EndsWith(query)
|| project.ProjectAddress.City.Name.StartsWith(query)
|| project.ProjectAddress.City.Name.Contains(query)
|| project.ProjectAddress.City.Name.EndsWith(query)
|| queryList.Contains(project.ProjectAddress.City.Name)
|| queryList.Contains(project.ProjectAddress.Address1)
)
select project as Project).Distinct().AsParallel().ToList();
-Thanks
Simplest (but not very efficient) solution is just applying lots of sorts to your items:
var keywords = "vishal pandey";
var items = new[] { "pandey", "other", "vishal", "vishal pandey" };
var query = items.OrderByDescending(i => i.Contains(keywords));
foreach (var keyword in keywords.Split())
query = query.ThenByDescending(i => i.Contains(keyword));
Output:
vishal pandey
vishal
pandey
other
But if you have many keywords, or there is lot of items, custom comparer will be much better solution.
UPDATE1: If order of partial matches will not be important, you can use this simple solution suggested by Frank:
var pattern = "vishal pandey".Replace(' ', '|');
var items = new[] { "pandey", "other", "vishal", "vishal pandey" };
var query = items.OrderByDescending(i => Regex.Matches(i, pattern).Count);
UPDATE2: Custom comparer sample
public class ItemsComparer : IComparer<string>
{
private string[] keywords;
private string pattern;
public ItemsComparer(string keywords)
{
this.keywords = keywords.Split();
this.pattern = keywords.Replace(' ', '|');
}
public int Compare(string x, string y)
{
var xMatches = Regex.Matches(x, pattern).Count;
var yMatches = Regex.Matches(y, pattern).Count;
if (xMatches != yMatches)
return yMatches.CompareTo(xMatches);
if (xMatches == keywords.Length || xMatches == 0)
return 0;
foreach (var keyword in keywords)
{
var result = y.Contains(keyword).CompareTo(x.Contains(keyword));
if (result == 0)
continue;
return result;
}
return 0;
}
}
Usage:
var items = new[] { "pandey", "other", "vishal", "vishal pandey" };
var comparer = new ItemsComparer("vishal pandey");
Array.Sort(items, comparer);
What I would do is to create a custom comparer.
Then you create a class that implements this IComparer<T> interface.
In the constructor you can pass the list of expected strings.
In the compare method you can compare two instances based on how many of the expected strings the item has.

Remove duplicated elements from a List<String>

I would like to remove the duplicate elements from a List. Some elements of the list looks like this:
Book 23
Book 22
Book 19
Notebook 22
Notebook 19
Pen 23
Pen 22
Pen 19
To get rid of duplicate elements i've done this:
List<String> nodup = dup.Distinct().ToList();
I would like to keep in the list just
Book 23
Notebook 22
Pen 23
How can i do that ?
you can do someting like
string firstElement = dup.Distinct().ToList().First();
and add it to another list if you want.
It's not 100% clear what you want here - however...
If you want to keep the "largest" number in the list, you could do:
List<string> noDup = dup.Select(s => s.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)
.Select(p => new { Name=p[0], Val=int.Parse(p[1]) })
.GroupBy(p => p.Name)
.Select(g => string.Join(" ", g.Key, g.Max().ToString()))
.ToList();
This would transform the List<string> by parsing the numeric portion into a number, taking the max per item, and creating the output string as you have specified.
You can use LINQ in combination with some String operations to group all your itemy by name and MAX(Number):
var q = from str in list
let Parts = str.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
let item = Parts[ 0 ]
let num = int.Parse(Parts[ 1 ])
group new { Name = item, Number = num } by item into Grp
select new {
Name = Grp.Key,
Value = Grp.Max(i => i.Number).ToString()
};
var highestGroups = q.Select(g =>
String.Format("{0} {1}", g.Name, g.Value)).ToList();
(Same as Reed's approach but in query syntax which is better readable to my mind)
Edit: I cannot reproduce your comment that it does not work, here is sample data:
List<String> list = new List<String>();
list.Add("Book 23");
list.Add("Book 22");
list.Add("Book 19");
list.Add("Notebook 23");
list.Add("Notebook 22");
list.Add("Notebook 19");
list.Add("Pen 23");
list.Add("Pen 22");
list.Add("Pen 19");
list.Add("sheet 3");
var q = from str in list
let Parts = str.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
let item = Parts[ 0 ]
let num = int.Parse(Parts[ 1 ])
group new { Name = item, Number = num } by item into Grp
select new {
Name = Grp.Key,
Value = Grp.Max(i => i.Number).ToString()
};
var highestGroups = q.Select(g => String.Format("{0} {1}", g.Name, g.Value));
MessageBox.Show(String.Join(Environment.NewLine, highestGroups));
The result:
Book 23
Notebook 23
Pen 23
sheet 3
You may want to add a custom comparer as a parameter, as you can see in the example on MSDN.
In this example I assumed Foo is a class with two members.
class Program
{
static void Main(string[] args)
{
var list = new List<Foo>()
{
new Foo("Book", 23),
new Foo("Book", 22),
new Foo("Book", 19)
};
foreach(var element in list.Distinct(new Comparer()))
{
Console.WriteLine(element.Type + " " + element.Value);
}
}
}
public class Foo
{
public Foo(string type, int value)
{
this.Type = type;
this.Value = value;
}
public string Type { get; private set; }
public int Value { get; private set; }
}
public class Comparer : IEqualityComparer<Foo>
{
public bool Equals(Foo x, Foo y)
{
if(x == null || y == null)
return x == y;
else
return x.Type == y.Type;
}
public int GetHashCode(Foo obj)
{
return obj.Type.GetHashCode();
}
}
This works on an IList, assuming that we want the first item each, not the one with the highest number. Be careful with different collection types (like ICollection or IEnumerable), as they do not guarantee you any order. Therefore any of the Foos may remain after the Distinct.
You could also override both Equals and GetHashCode of Foo instead of using a custom IEqualityComparer. However, I would not actually recommend this for a local distinct. Consumers of your class may not recognize that two instances with same value for Type are always equal, regardless of their Value.
a bit old fashioned , but it should work ,
If I understand correctrly
Dictionary<string,int> dict=new Dictionary<string,int>();
//Split accepts 1 character ,assume each line containes key value pair seperated with spaces and not containing whitespaces
input=input.Replace("\r\n","\n");
string[] lines=input.Split('\n');
//break to categories and find largest number at each
foreach(line in lines)
{
string parts[]=line.Split(' ');
string key=parts[0].Trim();
int value=Convert.ToInt32(parts[1].Trim());
if (dict.ContainsKey(key))
{
dict.Add(key, value);
}
else
{
if (dict[key]<value)
{
dict[key]=value;
}
}
}
//do somethig with dict

Categories

Resources