Compare two lists from user - c#

I have a predefined list List words.Say it has 7 elements:
List<string> resourceList={"xyz","dfgabr","asxy", "abec","def","geh","mnbj"}
Say, the user gives an input "xy+ ab" i.e he wants to search for "xy" or "ab"
string searchword="xy+ ab";
Then I have to find all the words in the predefined list which have "xy" or "ab" i.e all words split by '+'
So, the output will have:
{"xyz","dfgabr","abec",""}
I am trying something like:
resourceList.Where(s => s.Name.ToLower().Contains(searchWords.Any().ToString().ToLower())).ToList()
But, I am unable to frame the LINQ query as there are 2 arrays and one approach I saw was concatenate 2 arrays and then try; but since my second array only contains part of the first array, my LINQ does not work.

You need to first split your search pattern with + sign and then you can easily find out which are those item in list that contains your search pattern,
var result = resourceList.Where(x => searchword.Split('+').Any(y => x.Contains(y.Trim()))).ToList();
Where:
Your resourceList is
List<string> resourceList = new List<string> { "xyz", "dfgabr", "asxy", "abec", "def", "geh", "mnbj" };
And search pattern is,
string searchword = "xy+ ab";
Output: (From Debugger)

Try following which doesn't need Regex :
List<string> resourceList= new List<string>() {"xyz","dfgabr","asxy","abec","def","geh","mnbj"};
List<string> searchPattern = new List<string>() {"xy","ab"};
List<string> results = resourceList.Where(r => searchPattern.Any(s => r.Contains(s))).ToList();

You can try querying with a help of Linq:
List<string> resourceList = new List<string> {
"xyz", "dfgabr", "asxy", "abec", "def", "geh", "mnbj"
};
string input = "xy+ ab";
string[] toFind = input
.Split('+')
.Select(item => item.Trim()) // we are looking for "ab", not for " ab"
.ToArray();
// {"xyz", "dfgabr", "asxy", "abec"}
string[] result = resourceList
.Where(item => toFind
.Any(find => item.IndexOf(find) >= 0))
.ToArray();
// Let's have a look at the array
Console.Write(string.Join(", ", result));
Outcome:
xyz, dfgabr, asxy, abec
If you want to ignore case, add StringComparison.OrdinalIgnoreCase parameter to IndexOf
string[] result = resourceList
.Where(item => toFind
.Any(find => item.IndexOf(find, StringComparison.OrdinalIgnoreCase) >= 0))
.ToArray();

Related

LINQ query to group strings by first letter and determine total length

A sequence of non-empty strings stringList is given, containing only uppercase letters of the Latin alphabet. For all strings starting with the same letter, determine their total length and obtain a sequence of strings of the form "S-C", where S is the total length of all strings from stringList that begin with the character C.
var stringList = new[] { "YELLOW", "GREEN", "YIELD" };
var expected = new[] { "11-Y", "5-G" };
I tried this:
var groups =
from word in stringList
orderby word ascending
group word by word[0] into groupedByFirstLetter
orderby groupedByFirstLetter.Key descending
select new { key = groupedByFirstLetter.Key, Words = groupedByFirstLetter.Select(x => x.Length) };
But the output of this query is Y 6 5 G 5 instead of Y-11 G-5.
What I would like to know is how to sum the lengths if there is more than 1 word in the group, and how to format the result/display it as expected?
This should do it:
var results = stringList.OrderByDescending(x => x[0])
.ThenBy(x => x)
.GroupBy(x => x[0])
.Select(g => $"{g.Sum(x => x.Length)}-{g.Key}")
.ToArray();
var result = stringList.GroupBy(e => e[0]).Select(e => $"{e.Sum(o => o.Length)}-{e.Key}").ToArray();
Not sure I am able to rewrite it in your form.

LINQ query that combines grouping and sorting

I am relatively new to LINQ and currently working on a query that combines grouping and sorting. I am going to start with an example here. Basically I have an arbitrary sequence of numbers represented as strings:
List<string> sNumbers = new List<string> {"34521", "38450", "138477", "38451", "28384", "13841", "12345"}
I need to find all sNumbers in this list that contain a search pattern (say "384")
then return the filtered sequence such that the sNumbers that start with the search pattern ("384") are sorted first followed by the remaining sNumbers that contain the search pattern somewhere. So it will be like this (please also notice the alphabetical sort with in the groups):
{"38450", "38451", "13841", "28384", "138477"}
Here is how I have started:
outputlist = (from n in sNumbers
where n.Contains(searchPattern
select n).ToList();
So now we have all number that contain the search pattern. And this is where I am stuck. I know that at this point I need to 'group' the results into two sequences. One that start with the search pattern and other that don't. Then apply a secondary sort in each group alphabetically. How do I write a query that combines all that?
I think you don't need any grouping nor list splitting for getting your desired result, so instead of answer about combining and grouping I will post what I would do to get desired result:
sNumbers.Where(x=>x.Contains(pattern))
.OrderByDescending(x => x.StartsWith(pattern)) // first criteria
.ThenBy(x=>Convert.ToInt32(x)) //this do the trick instead of GroupBy
.ToList();
This seems fairly straight forward, unless I've misunderstood something:
List<string> outputlist =
sNumbers
.Where(n => n.Contains("384"))
.OrderBy(n => int.Parse(n))
.OrderByDescending(n => n.StartsWith("384"))
.ToList();
I get this:
var result = sNumbers
.Where(e => e.StartsWith("384"))
.OrderBy(e => Int32.Parse(e))
.Union(sNumbers
.Where(e => e.Contains("384"))
.OrderBy(e => Int32.Parse(e)));
Here the optimized version which only needs one LINQ statement:
string match = "384";
List<string> sNumbers = new List<string> {"34521", "38450", "138477", "38451", "28384", "13841", "12345"};
// That's all it is
var result =
(from x in sNumbers
group x by new { Start = x.StartsWith(match), Contain = x.Contains(match)}
into g
where g.Key.Start || g.Key.Contain
orderby !g.Key.Start
select g.OrderBy(Convert.ToInt32)).SelectMany(x => x);
result.ToList().ForEach(x => Console.Write(x + " "));
Steps:
1.) Group into group g based on StartsWith and Contains
2.) Just select those groups which contain the match
3.) Order by the inverse of the StartsWith key (So that StartsWith = true comes before StartsWith = false)
4.) Select the sorted list of elements of both groups
5.) Do a flatMap (SelectMany) over both lists to receive one final result list
Here an unoptimized version:
string match = "384";
List<string> sNumbers = new List<string> {"34521", "38450", "138477", "38451", "28384", "13841", "12345"};
var matching = from x in sNumbers
where x.StartsWith(match)
orderby Convert.ToInt32(x)
select x;
var nonMatching = from x in sNumbers
where !x.StartsWith(match) && x.Contains(match)
orderby Convert.ToInt32(x)
select x;
var result = matching.Concat(nonMatching);
result.ToList().ForEach(x => Console.Write(x + " "));
Linq has an OrderBy method that allows you give a custom class for deciding how things should be sorted. Look here: https://msdn.microsoft.com/en-us/library/bb549422(v=vs.100).aspx
Then you can write your IComparer class that takes a value in the constructor, then a Compare method that prefers values that start with that value.
Something like this maybe:
public class CompareStringsWithPreference : IComparer<string> {
private _valueToPrefer;
public CompareStringsWithPreference(string valueToPrefer) {
_valueToPrefer = valueToPrefer;
}
public int Compare(string s1, string s2) {
if ((s1.StartsWith(_valueToPrefer) && s2.StartsWith(_valueToPrefer)) ||
(!s1.StartsWith(_valueToPrefer) && !s2.StartsWith(_valueToPrefer)))
return string.Compare(s1, s2, true);
if (s1.StartsWith(_valueToPrefer)) return -1;
if (s2.StartsWith(_valueToPrefer)) return 1;
}
}
Then use it like this:
outputlist = (from n in sNumbers
where n.Contains(searchPattern)
select n).OrderBy(n, new CompareStringsWithPreference(searchPattern))ToList();
You can create a list with strings starting with searchPattern variable and another containing searchPattern but not starting with (to avoid repeating elements in both lists):
string searchPattern = "384";
List<string> sNumbers = new List<string> { "34521", "38450", "138477", "38451", "28384", "13841", "12345" };
var list1 = sNumbers.Where(s => s.StartsWith(searchPattern)).OrderBy(s => s).ToList();
var list2 = sNumbers.Where(s => !s.StartsWith(searchPattern) && s.Contains(searchPattern)).OrderBy(s => s).ToList();
var outputList = new List<string>();
outputList.AddRange(list1);
outputList.AddRange(list2);
Sorry guys, after reading through the responses, I realize that I made a mistake in my question. The correct answer would be as follows: (sort by "starts with" first and then alphabetically (not numerically)
// output: {"38450", "38451", "13841", "138477", "28384"}
I was able to achieve that with the following query:
string searchPattern = "384";
List<string> result =
sNumbers
.Where(n => n.Contains(searchpattern))
.OrderBy(s => !s.StartsWith(searchpattern))
.ThenBy(s => s)
.ToList();
Thanks

Get a sub-list of string matching a list of word

List<string> myList = new List<string>()
{
"This is a first matching exemple",
"This is second matching exemple",
"this one don't match",
"Here, the last item"
};
List<string> words = new List<string>() { "This", "is" };
How can i get a list matching all the words
List<string> result = myList.Contains(AllWords???);
Edit 1 : I have forgotten to specify that words don't have to be completely matched.
Thus :
List<string> words = new List<string>() { "This", "i" }
for exemple must return the same result.
Edit 2 : After reading some answers (chomba code exemple), i updated my code to :
List<string> _list = new List<string>();
// Populated from factory.
_list = SQLFactory .GetDataView("Provider")
.ToTable()
.AsEnumerable()
.Select(r => r[1].ToString())
.ToList<string>();
// Construct list of word from textbox.
List<string> words = txtName.Text.ToLower().Split(' ').ToList();
// Update ListView
ListView.ListViewItemCollection lvic = new ListView.ListViewItemCollection(lvName);
lvName.BeginUpdate();
lvName.Items.Clear();
lvic.AddRange(_list .AsParallel()
.Where(x => words.All(word => x.Contains(word)))
.AsParallel()
.Select(t => new ListViewItem(t))
.ToList()
.ToArray());
lvName.EndUpdate();
But resulting listview still miss some items.
Edit 3: Problem solved. I just have to manage the case of words.
lvic.AddRange(_list .AsParallel()
.Where(x => words.All(word => x.Contains(word)))
.AsParallel()
.Select(t => new ListViewItem(t))
.ToList()
.ToArray());
There might be a bit more efficient way of doing this but, this comes to mind at the moment:
var result = (from sentence in myList let wordsArray = sentence.Split(' ').ToList()
where wordsArray.Intersect(words).Count() == words.Count
select sentence).ToList();
This should work:
List<string> result = myList.Where(x => !words.Except(x.Split(' ')).Any()).ToList();
or if you don't need words to be completely matched:
List<string> result = myList.Where(x => words.All(word => x.Contains(word))).ToList();
List<String> myList = new ArrayList<String>();
myList.add("This is a first matching exemple");
myList.add("This is second matching exemple");
myList.add( "this one don't match");
myList.add("Here, the last item");
List<String> words = new ArrayList<String>();
words.add("This");
words.add("is");
List<String> newList = new ArrayList<String>();
String text = words.get(0)+" "+words.get(1);
Iterator it = myList.iterator();
Iterator it1 = words.iterator();
while(it.hasNext()){
String x = (String) it.next();
if(x.contains(text)){
newList.add(x);
}
}
System.out.println(newList);
Here is the final solution. I've added some parallel syntax to speed execution.
lvName is a listview.
txtName is a text box to find matching words (separate with spaces).
DataView _view is initialized with a query possessing 2 columns at least. Column number 2 is the string we are searching for.
DataView _view = GettingDataFromFunctionCall();
List<string> _list = new List<string>();
// Populated from factory.
_list = _view.ToTable()
.AsEnumerable()
.Select(row => row[1].ToString())
.ToList<string>();
// Construct list of word from textbox.
List<string> words = txtName.Text.ToLower().Split(' ').ToList();
// Update ListView
ListView.ListViewItemCollection lvic = new ListView.ListViewItemCollection(lvName);
lvName.BeginUpdate();
lvName.Items.Clear();
lvic.AddRange(_list .AsParallel()
.Where(x => words.All(word => x.ToLower().Contains(word)))
.AsParallel()
.Select(t => new ListViewItem(t))
.ToList()
.ToArray());
lvName.EndUpdate();

Split a string with condition

I have a string variable that contains csv value like this:
string str = "105, c#, vb, 345, 53, sql51";
so now i want to get only alphanumeric items in a list or array without using loop.
required result:
string result = "c#, vb, sql51";
Or in list Or in array...
string str = "105, c#, vb, 345, 53, sql51";
var separator = ", ";
int dummy;
var parts = str.Split(new[]{separator}, StringSplitOptions.RemoveEmptyEntries)
.Where(s => !int.TryParse(s, out dummy));
string result = string.Join(separator, parts);
Console.WriteLine(result);
prints:
c#, vb, sql51
Split using the Split method, filter with a LINQ expression, and call ToArray or ToList on the result to produce a filtered array:
var res = str
.Split(new[] {',', ' '})
.Where(s => s.Any(c => !Char.IsDigit(c)))
.ToList();
Demo on ideone.
Something like:
var str = "test,test,tes,123,5";
var result = string.Join(",", str.Split(',').Where(s => !s.All(t => Char.IsNumber(t))));
result.Dump();
"105, c#, vb, 345, 53, sql51".Split(",")
.Where(item => new Regex("[#A-Za-z0-9]").IsMatch(item))
.Select(item=> item.Trim())
.ToList();
Note: Not sure why the OP wants the numbers filtered out-- Numbers are alphanumeric.

Linq select strings from list when condition is met and save the index

I have a list of strings
List<string> lstOne = new List<string>() { "January:1", "February", "March:4"};
And I am filtering the strings that contain : with this code
var withcolumns = lstOne.Find(t => t.Contains(':'));
and I am getting a new list with { "January:1", "March:4"}
I want to select in a new list the values January:1 and March:4 but also save the indexes of then in the previous list so the result would be
"0" "January:1"
"2" "March:4"
I can be simple or complicated but right now my brain is not functioning to solve this issue.
list.Select((item, index) => new { item, index })
.Where(o => o.item.Contains(':'))
not sure what you want as the result ? a list of strings? or ?
but anyways.....with the index prefixed to your string...
List<string> lstOne = new List<string>() { "January:1", "February", "March:4" };
var list = lstOne.Select((s, i) => i+ " " + s ).Where(s => s.Contains(":")).ToList();

Categories