Parse string and order by parsed value - c#

i am trying to build linq expression to solve my problem. I have list of strings
List<string> arr = new List<string>();
arr.Add("<desc><ru>1</ru><en>3</en></desc>");
arr.Add("<desc><ru>2</ru><en>4</en></desc>");
i want to parse every item and order results
fake sample:
arr.Select(ParseItem("en")).OrderBy(x)
then we have two items in ru in order 1,2
Thanks for all and sorry for my bad English
Thanks for all response but how to convert now results to IQueryable
class Test { public string data { get; set; } }
List<Test> arr = new List<Test>();
arr.Add(new Test { data = "<desc><ru>AAA</ru><en>One</en></desc>" });
arr.Add(new Test { data = "<desc><ru>1</ru><en>Two</en></desc>" });
arr.Add(new Test { data = "<desc><ru>22</ru><en>Ab</en></desc>" });
IQueryable<Test> t = arr.AsQueryable();
// here the trouble how to convert to IQueryable<Test>
t = t.Select(s => XElement.Parse(s.data)).Select(x => x.Element("en")).
OrderBy(el => el.Value);
Thanks again

After the question update - this will return your ordered data by <en> node value:
var result = arr
.OrderBy(t=>
XElement.Parse(t.data).Element("en").Value
);
The result valiable is of IOrderedEnumerable<Test> type.

This will produce a list of the values in ru tags (assuming they are integers), ordered by the values in en tags (again, assuming integers).
List<string> items = arr.Select(s => XElement.Parse(s))
.OrderBy(xml => (int)xml.Element("en"))
.Select(xml => (int)xml.Element("ru"))
.ToList();
If you simply want to enumerate, you can omit the ToList call:
foreach (var item in arr.Select(s => XElement.Parse(s))
.OrderBy(xml => (int)xml.Element("en"))
.Select(xml => (int)xml.Element("ru")))
{
// do something with item
}

I'm not sure I've got what the excepted results are, but if you need to select values in en ordered by the value in ru then here it is:
var orderedItems = (
from item in arr
let x = XElement.Parse(item)
let ruValue = (int)x.Element("ru")
let enValue = (int)x.Element("en")
orderby ruValue
select enValue
).ToList();

I don't know if it is too late, but if you are wanting to parse the text and if it is an integer then sort by value otherwise sort by text, then this might help.
You need to define a function like this to enable parsing in LINQ expressions:
Func<string, int?> tryParseInteger = text =>
{
int? result = null;
int parsed;
if (int.TryParse(text, out parsed))
{
result = parsed;
}
return result;
};
Then you can do queries like this:
var xs = new [] { "Hello", "3ff", "4.5", "5", };
var rs =
(from x in xs
select tryParseInteger(x)).ToArray();
// rs == new int?[] { null, null, null, 5, };
In your case you possibly want something like this:
var elements = new []
{
"<desc><ru>AAA</ru></desc>",
"<desc><ru>1</ru></desc>",
"<desc><ru>42</ru></desc>",
"<desc><ru>-7</ru></desc>",
"<desc><ru>BBB</ru></desc>",
"<desc><ru>22</ru></desc>",
};
var query =
from e in elements
let xe = XElement.Parse(e)
let v = xe.Element("ru").Value
orderby v
orderby tryParseInteger(v)
select v;
Which would give you:
{ "AAA", "BBB", "-7", "1", "22", "42" }
If you want to treat non-integers (ie parsed as null) to be zero then change the query by using this line:
orderby tryParseInteger(v) ?? 0
Then you'll get this:
{ "-7", "AAA", "BBB", "1", "22", "42" }
I hope this helps.

Related

How to extract items at specified locations from list of comma separated strings using LINQ

I have a list of comma separated strings and I need to extract 1-st and 3-rd items from all strings.
List<string> list = new List<string>()
{
"1,2,3",
"4,5,6",
"7,8,9",
"10,11,12"
};
List<Tuple<string, string>> parsed = new List<Tuple<string, string>>(list.Count);
foreach (string s in list)
{
string[] items = s.Split(',');
parsed.Add(new Tuple<string, string>(items[0], items[2]));
}
Console.WriteLine(string.Join(Environment.NewLine, parsed.Select(p => p.Item1 +","+ p.Item2)));
Console.ReadLine();
That results:
1,3
4,6
7,9
10,12
But when I try to write it using LINQ, I can't get something simpler than:
IEnumerable<Tuple<string, string>> parsed = list.Select(
s =>
{
string[] items = s.Split(',');
return new Tuple<string, string>(items[0], items[2]);
});
I was wondering if it's possible to get rid of that {} block and replace it with LINQ function calls. To be clear, I am asking this question only to increase my knowledge of the features and capabilities of LINQ, so, any suggestion is welcome.
Edit:
So far, all suggested codes call the split function twice. Is there a way to get the desired result just by calling it once? Something like:
var parsed = list.Select(s => s.Split(',').Magic(...));
Also, by that code sample above, I didn't mean first and last items. I really mean items at specified locations.
If you are working with C#7 or above version, then you can write even in simpler manner,
IEnumerable<Tuple<string, string>> parsed = list.Select(
s => (s.Split(',')[0], s.Split(',')[2]));
You can do something like below
IEnumerable<Tuple<string, string>> parsed = list.Select(
s =>
{
var spl = s.Split(',');
return new Tuple<string, string>(spl[0], spl[2]);
// return new MyClass(spl[0], spl[2], ... ,spl[n]);
});
If you want the , separated list back by removing the middle number you can use the Regex to replace it.
IEnumerable<string> afterUpdate = list.Select(s => Regex.Replace(s, #",[0-9]*,", ","));
Output for this will be
{
"1,3",
"4,6",
"7,9",
"10,12"
};
May be this could help...
//----------------Linq.----------------------
//Data Source
var source = new List<string> { "1,2,3", "4,5,6", "7,8,9", "10,11,12" };
//var sourceTest = new List<string> { "11,45,6,5,", "2,3,4,5,6", "1,7,40,30", "10,20,30,40,50" };
//var sourceTest2 = new List<string> { "15,12,11,45,6,5,", "1,2,3,4,5,6", "1,7,9,40,30", "60,20,70,80,90,100" };
//Query Creation
var queryLambda = source.Select(item => new
{
FirstItem = item.Split(',').FirstOrDefault(),
ThirdItem = item.Split(',').Skip(2).FirstOrDefault()
}).ToList();
var query = (from items in source
select new
{
FirstItem = items.Split(',').FirstOrDefault(),
ThirdItem = items.Split(',').Skip(2).FirstOrDefault()
}).ToList();
//Query Execution
queryLambda.ForEach(item => { Console.WriteLine(string.Join(",", new string[] { item.FirstItem, item.ThirdItem })); });
Console.WriteLine();
query.ForEach(item => { Console.WriteLine(string.Join(",", new string[] { item.FirstItem, item.ThirdItem })); });
Console.ReadLine();

Removing strings with duplicate letters from string array

I have array of strings like
string[] A = { "abc", "cccc", "fgaeg", "def" };
I would like to obtain a list or array of strings where any letter appears only one time. I means that "cccc", "fgaeg" will be removed from input array.
I managed to do this but I feel that my way is very messy, unnecessarily complicated and not efficient.
Do you have any ideas to improve this algorythm (possibliy replacing with only one Linq query)?
My code:
var goodStrings = new List<string>();
int i = 0;
foreach (var str in A)
{
var tempArr = str.GroupBy(x => x)
.Select(x => new
{
Cnt = x.Count(),
Str = x.Key
}).ToArray();
var resultArr = tempArr.Where(g => g.Cnt > 1).Select(f => f.Str).ToArray();
if(resultArr.Length==0) goodStrings.Add(A[i]);
i++;
}
You can use Distinct method for every array item and get items with count of distinct items equals to original string length
string[] A = { "abc", "cccc", "fgaeg", "def" };
var result = A.Where(a => a.Distinct().Count() == a.Length).ToList();
You'll get list with abc and def values, as expected

Find most common element in array

I have a string array that can contains 1 or more elements with various string values. I need to find the most common string in the array.
string aPOS[] = new string[]{"11","11","18","18","11","11"};
I need to return "11" in this case.
Try something like this using LINQ.
int mode = aPOS.GroupBy(v => v)
.OrderByDescending(g => g.Count())
.First()
.Key;
If you don't like using LINQ or are using e.g. .Net 2.0 which does not have LINQ, you can use foreach loops
string[] aPOS = new string[] { "11", "11", "18", "18", "11", "11"};
var count = new Dictionary<string, int>();
foreach (string value in aPOS)
{
if (count.ContainsKey(value))
{
count[value]++;
}
else
{
count.Add(value, 1);
}
}
string mostCommonString = String.Empty;
int highestCount = 0;
foreach (KeyValuePair<string, int> pair in count)
{
if (pair.Value > highestCount)
{
mostCommonString = pair.Key;
highestCount = pair.Value;
}
}
You can do this with LINQ, the following is untested, but it should put you on the right track
var results = aPOS.GroupBy(v=>v) // group the array by value
.Select(g => new { // for each group select the value (key) and the number of items into an anonymous object
Key = g.Key,
Count = g.Count()
})
.OrderByDescending(o=>o.Count); // order the results by count
// results contains the enumerable [{Key = "11", Count = 4}, {Key="18", Count=2}]
Here's the official Group By documentation

"in" operator in linq c#?

I have a generic list which contains member details and I have a string array of memberIds..I need to filter the list and get the results which contains all the memberIds..How can I achieve this using LINQ.
I tried the following
string[] memberList = hdnSelectedMemberList.Value.Split(',');
_lstFilteredMembers = lstMainMembers.Where(p =>memberList.Contains(p.MemberId))
.ToList();
But the above query is giving me only the results that match the first member ID..so lets say if I have memberIds 1,2,3,4 in the memberList array..the result it returns after the query contains only the members with member ID 1..even though the actual list has 1,2,3,4,5 in it..
Can you please guide me what I am doing wrong.
Thanks and appreciate your feedback.
Strings make terrible primary keys. Try trimming the list:
string[] memberList = hdnSelectedMemberList.Value
.Split(',')
.Select(p => p.Trim())
.ToList();
_lstFilteredMembers = lstMainMembers.Where(p => memberList.Contains(p.MemberId)).ToList();
Because I have a feeling hdnSelectedMemberList may be "1, 2, 3, 4".
Use a join:
var memquery = from member in lstMainMembers
join memberid in memberList
on member.MemberId equals memberid
select member;
With jmh, I'd use a join
var members = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var ids = new[] { 1, 3, 6, 14 };
var result = members.Join(ids, m => m, id => id, (m, id) => m);
foreach (var r in result)
Console.WriteLine(r); //prints 1, 3, 6
The code you are showing is correct, and works in a Unit Test:
public class Data
{
public string MemberId { get; set; }
}
[TestMethod]
public void Your_Code_Works()
{
// Arrange fake data.
var hdnSelectedMemberList = "1,2,3,4";
var lstMainMembers = new[]
{
new Data { MemberId = "1" },
new Data { MemberId = "2" },
new Data { MemberId = "3" },
new Data { MemberId = "4" },
new Data { MemberId = "5" }
};
// Act - copy/pasted from StackOverflow
string[] memberList = hdnSelectedMemberList.Split(',');
var _lstFilteredMembers = lstMainMembers.Where(p => memberList.Contains(p.MemberId)).ToList();
// Assert - All pass.
Assert.AreEqual(4, _lstFilteredMembers.Count);
Assert.AreEqual("1", _lstFilteredMembers[0].MemberId);
Assert.AreEqual("2", _lstFilteredMembers[1].MemberId);
Assert.AreEqual("3", _lstFilteredMembers[2].MemberId);
Assert.AreEqual("4", _lstFilteredMembers[3].MemberId);
}
There must be something wrong with your code outside what you have shown.
Try Enumerable.Intersect to get the intersection of two collections:
http://msdn.microsoft.com/en-us/library/system.linq.enumerable.intersect.aspx
_lstFilteredMembers = lstMainMembers.Intersect(memberList.Select(p => p.MemberID.ToString())).ToList()
Why not just project the IDs list into a list of members?
var result = memberList.Select(m => lstMainMembers.SingleOrDefault(mm => mm.MemberId == m))
Of course, that will give you a list that contains null entries for items that don't match.
You could filter those out, if you wanted to...
result = result.Where(r => r != null)
Or you could filter it before the initial select...
memberList.Where(m => lstMainMembers.Any(mm => mm.MemberId == m)).Select(m => lstMainMembers.Single(mm => mm.MemberId == m))
That's pretty ugly, though.

A method to count occurrences in a list

Is there a simple way to count the number of occurrences of all elements of a list into that same list in C#?
Something like this:
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
string Occur;
List<string> Words = new List<string>();
List<string> Occurrences = new List<string>();
// ~170 elements added. . .
for (int i = 0;i<Words.Count;i++){
Words = Words.Distinct().ToList();
for (int ii = 0;ii<Words.Count;ii++){Occur = new Regex(Words[ii]).Matches(Words[]).Count;}
Occurrences.Add (Occur);
Console.Write("{0} ({1}), ", Words[i], Occurrences[i]);
}
}
How about something like this ...
var l1 = new List<int>() { 1,2,3,4,5,2,2,2,4,4,4,1 };
var g = l1.GroupBy( i => i );
foreach( var grp in g )
{
Console.WriteLine( "{0} {1}", grp.Key, grp.Count() );
}
Edit per comment: I will try and do this justice. :)
In my example, it's a Func<int, TKey> because my list is ints. So, I'm telling GroupBy how to group my items. The Func takes a int and returns the the key for my grouping. In this case, I will get an IGrouping<int,int> (a grouping of ints keyed by an int). If I changed it to (i => i.ToString() ) for example, I would be keying my grouping by a string. You can imagine a less trivial example than keying by "1", "2", "3" ... maybe I make a function that returns "one", "two", "three" to be my keys ...
private string SampleMethod( int i )
{
// magically return "One" if i == 1, "Two" if i == 2, etc.
}
So, that's a Func that would take an int and return a string, just like ...
i => // magically return "One" if i == 1, "Two" if i == 2, etc.
But, since the original question called for knowing the original list value and it's count, I just used an integer to key my integer grouping to make my example simpler.
You can do something like this to count from a list of things.
IList<String> names = new List<string>() { "ToString", "Format" };
IEnumerable<String> methodNames = typeof(String).GetMethods().Select(x => x.Name);
int count = methodNames.Where(x => names.Contains(x)).Count();
To count a single element
string occur = "Test1";
IList<String> words = new List<string>() {"Test1","Test2","Test3","Test1"};
int count = words.Where(x => x.Equals(occur)).Count();
var wordCount =
from word in words
group word by word into g
select new { g.Key, Count = g.Count() };
This is taken from one of the examples in the linqpad
public void printsOccurences(List<String> words)
{
var selectQuery =
from word in words
group word by word into g
select new {Word = g.Key, Count = g.Count()};
foreach(var word in selectQuery)
Console.WriteLine($"{word.Word}: {word.Count}");*emphasized text*
}
This is a version which avoids Linq but uses only slightly more code.
// using System.Collections.Generic;
Dictionary<int, int> oGroups = new Dictionary<int, int>();
List<int> oList = new List<int>() { 1, 2, 3, 4, 5, 2, 2, 2, 4, 4, 4, 1 };
foreach (int iCurrentValue in oList)
{
if (oGroups.ContainsKey(iCurrentValue))
oGroups[iCurrentValue]++;
else
oGroups.Add(iCurrentValue, 1);
}
foreach (KeyValuePair<int, int> oGroup in oGroups)
{
Console.WriteLine($"Value {oGroup.Key} appears {oGroup.Value} times.");
}
this code returns a dictionary that contain the world and the occurrence:
var wordsDic = Words
.GroupBy(p => p)
.ToDictionary(p => p.Key, q => q.Count());
Your outer loop is looping over all the words in the list. It's unnecessary and will cause you problems. Remove it and it should work properly.

Categories