Linq query to select single string from multiple List<string> - c#

I am having a hard time understanding why I am getting the results that I am.
I have two lists of strings:
var list1 = new List<String> {"item 1", "item 2"};
var list2 = new List<String> { "item 3", "item 4" };
Version 1 - Output: "item 2"
var item =
from x in (list1.Concat(list2))
where x.EndsWith("2")
select x;
Console.WriteLine(item.First());
Version 2 - Output: "i"
var item =
from x in (list1.Concat(list2))
where x.EndsWith("2")
select x.First();
Console.WriteLine(item.First());
Version 3 - Output: "System.Linq.Enumerable+WhereEnumerableIterator`1[System.String]"
var item =
from x in (list1.Concat(list2))
where x.EndsWith("2")
select x;
Console.WriteLine(item);
Given that version 2 outputs "i", I would expect version 3 to output "item 2". Why is this behavior occurring?

In version 3, select x is returning a sequence of strings that match your critera; it just happens to be a sequence with one item in it.
Console.WriteLine internally calls .ToString() on whatever you pass into it. Since there is no meaningful string representation for IEnumerable<T>, the default in .NET is to print the string name of the type.
Based on your wording, I think part of your confusion does come from a misunderstanding of why version 2 works the way it does. In version 2, select x.First() is actually a bit of a quirk/coincidence, because a string is also an IEnumerable<char>, so you can do LINQ operations on the string. .First() returns the first element of that char sequence, for each result that matches your criteria. So you're saying:
"For each element which matches my criteria, select the first character, and then return the sequence of all the first characters for the matches."
So in fact, item in version 2 is an IEnumerable<char> with one element in it. Calling Console.WriteLine() on an IEnumerable<char> will just print the chars in order. So you get "i".
(note: I see after I answered this, the question was edited to call .First() both inside the projection and on the result, so the bit about passing IEnumerable<char> to Console.WriteLine isn't totally relevant anymore)
Keep in mind LINQ is pretty much about working with sets until you explicitly reduce them down. For example, Select is simply a projection or transformation. It returns the same number of items that were passed to it, transformed. Where reduces down the set, but it's still a set.

Your Version 2 is selecting first item/char from the string x.First(), whereas your first version is selecting first item from the result set-> First string.
Version 1 is like - Select First Item from the Result Set
var item = (from x in (list1.Concat(list2))
where x.EndsWith("2")
select x).First(); //First complete string will be selected
and version 2 is like- Select First Item from a string in result set
var item = from x in (list1.Concat(list2))
where x.EndsWith("2")
select x.First(); //only first char will be selected for string
Your third case is selecting an IEnumerable<string>, so when you call Console.WriteLine, it calls the default implementation of ToString and thus you get
"System.Linq.Enumerable+WhereEnumerableIterator`1[System.String]"

When using First() you are materializing the list, causing the iterator to execute. That is eager execution. Your third version uses select which does not materialize the list abd uses Defeered Execution which returns an iterator, hence calling ToString() on it returning the iterators name

Because Where returns an IEnumerable.
You have written the equivalent of:
var whoKnows = list1.Concat(list2).Where(x => x.EndsWith("2"));
Console.WriteLine(whoKnows);
The ToString of a collection just returns the class name.

The type of the x in your Version 2 is String. The type of item in Version 1 is IEnumerable.
So your Version 2 return a list of characters which are the first characters of each string. In your Version 1, item.First() return the first element of the result set which is a string.

//This raise a exception if no item found
var item=list1.Concat(list2).First(i => i.EndsWith("2"));
//this return default value (null) if no item found
var item2 = list1.Concat(list2).FirstOrDefault(i => i.EndsWith("2"));

Related

How to sort a numeric set of strings that are added to a combobox? C#

I want to display in ascendent order a list of string (numbers) that is added to a combobox. The project is in .NET 4.7.2
I have a string list of numbers such as:
{"3.453","1.123","2.024","1.567"}
and I would like that when these are displayed in my combobox they appear in order :
{,"1.123","1.567","2.024","3.453"}
The values come from reading multiple XML files and when the name == CardID is found it is added to the combobox "cb_card" items.
...
if (name == "CardID")
{
if (!mainWindow.cb_card.Items.Contains(value))
{
mainWindow.cb_card.Items.Add(value);
}
}
...
I have tried to:
Set the Combobox property Sorted = "true" but an error appears:
XLS0413 The property 'Sorted' was not found in type 'ComboBox'.
I tried to add the values to a list, then sort the list and finally add them to the combobox. I edited the code shown above:
...
List<string> sortedCardId = new List<string>();
if (name == "CardID")
{
if (!mainWindow.cb_card.Items.Contains(value))
{
sortedCardId.Add();
}
}
sortedCardId.Sort();
foreach (string ID in sortedCardId)
{
mainWindow.cb_card.Items.Add(ID);
}
...
but the order stayed the same as when it is nor ordered.
I tried some variants of this last code, by converting the string list in doubled, sort it and reconvert it ot string, but I got to many errors qhich I couldn't debugg with my current knowledge.
I tried to add the values to an array instad of a list, sort the array and add the values, but then the combobox appeared empty.
thanks a lot for your time and help in advance .
You can use List.Sort for this. If you are sure that the list contains only numeric values that can be parsed as a decimal (or double, ...), you can use a custom sort comparison that converts the strings to a decimal before comparing them:
var lst = new List<string>() {"3.453","1.123","2.024","1.567"};
lst.Sort((string a, string b) => (int)(decimal.Parse(a) - decimal.Parse(b)));
// This writes the list content as "1.123, 2.024, 1.567, 3.453"
Console.WriteLine(string.Join(", ", lst));
When comparing two items, the comparison returns
less than 0: a is smaller than b
0: a == b
greater than 0: a is bigger than b
This is why subtracting b from a leads to a correct result of the comparison.

Trying to figure out how to add a string character to a string array using .Where

Below is my string array. I got this .Where code online and I found it really interesting and I tried modifying it to make it add a character instead of removing a character. However I keep getting the "Method Name Expected error under the 'indexremove' variable on the last line.
Is there any solution to this problem? Or is my modification not able to work?
string[] TestArray = {"a", "b", "c", "d", "e"};
int indexremove = 0; // for removing the character in the 1st position; a.
TestArray = TestArray.Where((source, index) => index != indexremove).ToArray(); //source, index points to the array's character collection, and index != indexremove causes A to be removed from the index of the array. Then it is converted to an array.
foreach (string value in TestArray) {Console.WriteLine(value); //displays output of new array without "a" }
string indextoadd = "a"; //trying to re-add the character back in to the array
TestArray = TestArray.Where((source, index) => index + indexremove(indextoadd)).ToArray(); //Method Name Expected on indexremove
Where doesn't work like this. It's a filtering device so it intrinsically cannot be used to extend/add to a list of things. The only thing you can do with a Where is prevent one of the input items being output, if some test returns false
For any given list of items;
string[] testArray = {"a", "b", "c", "d", "e"};
You can write a Where, and pass to the Where a small nugget of code (we tend to refer to it as a lambda) that is like a nameless method. It must accept one or two arguments and it must return a bool. Where will loop over the input list of items, calling the lambda you passed on each one of the items. If the lambda returns true, the inputted item is output. If it returns false, the list item being looped is not output
These are the conceptual equivalents:
//this nice short thing
testArray.Where(s => s.Length == 1);
//is like this longer thing
public bool LengthIs1(string s){
return s.Length == 1;
}
...
foreach(var s in testArray)
if(LengthIs1(s))
output s; //this is a pretend thing: `output` doesn't exist but it means I don't also have to explain yield return. Just imagine that it sends the value out and carries on looping
Examples
testArray.Where(s => true); //every item will be output
testArray.Where(s => false); //no item will be output
testArray.Where(s => s == "a"); //only items equal to "a" will be output
testArray.Where(s => s.StartsWith("b")); //only items starting with b will be output
testArray.Where(s => DateTime.Now.DayOfWeek == DayOfWeek.Tuesday); //every item will be output if it's Tuesday, otherwise no item will be output
Those are examples that just take one argument s, to the lambda. There is another form, the one you're using, where the lambda can have two parameters - the first parameter is again the item from the array. The second parameter is the index of the item in the array
testArray.Where((s,index) => true); //every item will be output
testArray.Where((s,index) => index == 0); //only the first item will be output
testArray.Where((s,index) => index != 1); //everything apart from the second item will be output
testArray.Where((s,index) => index%2 == 0); //even indexed items will be output
As you can see there isn't any scope for extending or adding more items. You start with 100 items, you can only output somewhere between 0 and 100 items
In the code you posted the first item is filtered out because the lambda assessed index versus index to remove, and will return true (keep) in all cases where they're not the same, and return false (remove) when they are the same
If you want to output more items you have to use something else, like Concat
new[]{1,2}.Concat(new[]{3,4})
This will output an enumeration of 1,2,3,4
Because Where produces an enumerable thing, and Concat can be called on an enumerable thing and accepts as a parameter another enumerable thing it means we could do something like:
testArray
.Where((s,index) => index != indextoremove)
.Concat(
testArray.Where((s,index) => index == indextoremove)
)
The first Where excludes only the indextoremove (it keeps the rest) and the second Where keeps only the indextoremove (it excludes the rest). The Concat would just effectively take the first sequence, where "a" has been excluded and then concatenate on the second sequence where "a" is the only one included. This would essentially move "a" from the start, to the end of the sequence
It's horrifically inefficient, but this example is more about learning
(source, index) => index + indexremove(indextoadd)
This is, alas, flat out a syntax error. You've made indextoadd a string, and index to remove is an int. you can't follow an int variable name with a ( because you cannot execute/call an int. You this also cannot execute an int passing a string to it. As the final nail in the lambda's coffin, it's supposed to return a bool but you've done something approaching a numeric result, if int + int(string) was even possible. In full method terms you've done something like:
public bool AMethod(string source, int index){
string indextoadd = "a";
int indexremove = 1;
return index + indexremove(indextoadd);
}
Which hopefully you can see has a lot of syntax and logical problems..
using System;
using System.Linq;
namespace Problems
{
class Program
{
static void Main(string[] args)
{
string[] TestArray = { "a", "b", "c", "d", "e" };
int indexremove = 1;
var RemovedIndex = TestArray.Skip(indexremove);
foreach(var t in RemovedIndex)
{
Console.WriteLine(t);
}
RemovedIndex.ToList().Insert(indexremove, "a");
RemovedIndex = TestArray.ToArray();
foreach (var t in RemovedIndex)
{
Console.WriteLine(t);
}
}
}
}
Since Arrays are fixed, I opted to skip over the first index in the array. Print the first console to see that the first character is skipped.
Then, since you needed the character back, I converted the current array to a list and inserted the character back. Remember to convert it back to an array afterward.
See the result by running the second loop.

Comparing 2 string arrays using Linq and Regex for a partial match

I have an array of search words and a second array that needs to be searched, returning true or false depending if any of the strings in the 2nd array contain text from the first. I tried using linq but I can't get it to return true if any of the values in the second array contain more than just those search words. I was thinking of using Regex maybe in combination with the linq query but I'm not sure how that would work. Here is what I was tried
string[] gsdSearchVerbiage =
{
"grade",
"transcript",
"gsd"
};
string[] tableColumns = new string []
{
"maxsgrades",
"somethingElse",
};
bool gsdFound = tableColumns.Any(
x => gsdSearchVerbiage.Contains(x));
This returns false. I understand why it's returning false but I can't figure out how to fix it. If the answer is to use a regular expression I couldn't figure out how to do that over 2 arrays...
Last statement should be
bool gsdFound = tableColumns.Any(
x => gsdSearchVerbiage.Any(y => x.Contains(y)));
since you're trying to know if the current item of tableColumns (x) contains any of gsdSearchVerbiage words (y)

Efficiently Sort Collection in C# by Substring and Index

I'm fetching some records from my database using entity framework as the user types into a searchbox and need to sort the items as they are fetched. I'll try to simplify the problem with the below.
Say I have a random list like the below that I would like to sort in place according to the occurrence of a substring
var randomList = new List<string> { "corona", "corolla", "pecoroll", "copper", "capsicum", "because", "cobra" };
var searchText = "cor";
Sort:
var sortedList = testList.OrderBy(x => x.IndexOf("cor"));
Output:
copper -> capsicum -> because -> cobra -> corona -> corolla -> pecoroll
I understand the code works as expected since the list is sorted by the index of the substring which is -1 for the first 4 items in the output, 0 for the 5th and 6th, and 2 for the 7th item.
Problem:
I'm trying to actually sort by the index of the searchsString and it's closest match to provide the user with suggestions of similar items. The expected result would be something like
corolla -> corona -> pecoroll -> cobra -> copper -> capsicum -> because
where the items containing lower indexes of the matching searchtext would appear first and recursively sort the list by 1 less character from the searchText until no characters remain. i.e. priority given to index of "cor" then "co" then "c".
I can probably write a for loop or recursive method for this but is there a built in LINQ method to achieve this objective on a collection or a library that handles searches this way considering that my code fetches records from a database so performance should be considerd? Thanks for your help in advance
To strictly address your question: "is there a built in LINQ method to achieve this(?)", I believe the answer is no. This type of "best match" search is very subjective; for example it could be argued that "cobra" is a better match than "pecoroll" since the user is more likely to have missed a "b" before the required "r", rather than excluding the first two letters, "pe" of the word "pecoroll". I believe that "proper" implementations of this behavior consider key proximity, common misspellings, and any number of other metrics to best auto-complete the entry. There may well be some established libraries available rather than developing your own method.
However, assuming you did want the exact behavior you requested, and whilst it sounds as if you were happy to do this yourself, here is my two cents:
static List<string> SortedList(List<string> baseList, string searchString)
{
// Take a modifiable copy of the base list
List<string> sourceList = new List<string>(baseList);
// Sort it first alphabetically to resolve tie-breakers
sourceList.Sort();
// Create a instance of our list to be returned
List<string> resultList = new List<string>();
while(
// While there are still elements to be sorted
(resultList.Count != baseList.Count) &&
// And there are characters remaining to be searched for
(searchString.Length > 0))
{
// Order the list elements, that contain the full search string,
// by the index of that search string.
var sortedElements = from item in sourceList
where item.Contains(searchString)
orderby item.IndexOf(searchString)
select item;
// For each of the ordered elements, remove it from the source list
// and add it to the result
foreach(var sortedElement in sortedElements)
{
sourceList.Remove(sortedElement);
resultList.Add(sortedElement);
}
// Remove one character from the search to be used against remaining elements
searchString = searchString.Remove(searchString.Length - 1, 1);
}
return resultList;
}
Testing with:
var randomList = new List<string> { "corona", "corolla", "pecoroll", "copper", "capsicum", "because", "cobra" };
var searchText = "cor";
var sortedList = SortedList(randomList, searchText);
foreach(string entry in sortedList)
{
Console.Write(entry + ", ");
}
I get:
corolla, corona, pecoroll, cobra, copper, capsicum, because,
I hope this helps.

How to Count unique values in a column of a datagridview?

I need to count the rows of a column except the duplicate ones
House Number
123
124
11
12
11
11
Total House Number: 4
I have searched and can't find the right syntax for my code.
I tried dictionary but it seems that it is not right for my code.
I am a complete beginner in c#
//Total House
int House = 0;
for (int row = 0; row < dataGridView1.Rows.Count; ++row)
{
if ((string)dataGridView1.Rows.[row].Cells("House_Number").Distinct())
{
House++;
}
}
TotalHouse.Text = "Total Houses " + $"{House}";
I tried the above code but it has an error Identifier expected.
Your code has a few potential problems, but let's start with the ones that will prevent it from compiling.
if ((string)dataGridView1.Rows.[row].Cells("House_Number").Distinct())
One problematic bit here is Rows.[row]. There shouldn't be a period there. If you have a period like that, C# will expect an identifier to follow it, not another operator. In this case, you have the [] operator following it, which is invalid. It should probably look like this:
if ((string)dataGridView1.Rows[row].Cells("House_Number").Distinct())
We're getting closer. However, the test inside an if statement must evaluate to a bool--that's true or false. Yours evaluates to a string because you're casting the whole thing to a string. That's because this part runs first:
dataGridView1.Rows[row].Cells("House_Number").Distinct()
Then this part runs:
(string)
So the whole thing becomes a string. We'll have to remove that (string) bit.
Let's take a closer look at dataGridView1.Rows[row].Cells("House_Number").Distinct(). Cells isn't a method--it's a property. That means you can't use the syntax Cells("House_Number"). However, the result of Cells is a DataGridViewCellCollection, which allows [] syntax, so you can do something like Cells["House_Number"].
Distinct() isn't going to give you a bool value--it will give you a collection of unique cells in the form of something called an IEnumerable.
dataGridView1.Rows[row].Cells.Distinct() isn't going to give you distinct cells in a column--it's going to give you distinct cells in a row. That's probably not what you want.
You're probably going to want something that looks like this:
int houses = dataGridView1.Rows
.Cast<DataGridViewRow>()
.Select(r => (int)r.Cells["House_Number"].Value)
.Distinct()
.Count();
Walking through this:
Start with dataGridView1.
Get a DataGridViewCellCollection of rows: .Rows
DataGridViewCellCollection is pretty old, so it implements IEnumerable instead of IEnumerable<DataGridViewRow>. We need to turn it into an IEnumerable<DataGridViewRow>, so we call LINQ's Cast<DataGridViewRow>().
Use LINQ to turn that into an IEnumerable<int>: .Select(r => (int)r.Cells["House_Number"].Value)
a. The argument to Select is a lambda expression. It takes one argument, r, which is a DataGridViewRow. It will return an int.
b. Get the cells for the row: .Cells
c. Get the specific cell we want: ["House_Number"]
d. Get the value of that cell
e. The value is returned as an object; we need to cast it to an int: (int)
Use LINQ to turn that IEnumerable<int> into another one that only has distinct values: .Distinct()
Count our results: .Count()
You'll need a reference to System.Linq for this to work. Put this at the top of your file if it isn't already there:
using System.Linq;
You can achieve this easily by adding a nuget reference to System.Data.DataSetExtensions
and then using linq to select the distinct house numbers:
var count = dataGridView1.AsEnumerable().Select(dr => dr["House_Number"]).Distinct().Count();
Otherwise you could achieve this using a hashset:
var hs = new HashSet<string>();
foreach (DataRow dataRow in dataGridView1.Rows)
{
hs.Add(dataRow["House_Number"].ToString());
}
TotalHouse.Text = "Total Houses " + $"{hs.Count}";

Categories

Resources