Can I add a where clause to an IOrderedEnumerable list? - c#

I have a list of arrays. Each array consists of a score and a difficulty. Read in from a text file.
This is how I get the data and order it by the score in descending order.
// load highscores
public void LoadScores()
{
// loop through each line in the file
while ((line = file.ReadLine()) != null)
{
// seperate the score from the difficulty
lineData = line.Split(',');
// add each line to the list
list.Add(lineData);
}
// order the list by descending order
IOrderedEnumerable<String[]> scoresDesc = list.OrderByDescending(ld => lineData[0]);
}
Is it possible to add the a clause to the IOrderedEnumerable so that it orders by the score in descending order where the difficulty is 1 ?

Assuming the "difficulty" is the second item in the array:
IEnumerable<String[]> scoresDesc =
list.OrderByDescending(ld => lineData[0])
.Where(ld => lineData[1] == 1);
You can sort it afterwards, but Where returns an IEnumerable<T>, not an IOrderedEnumerable<T>, so if you need it to be an IOrderedEnumerable<T> then it's cleaner (and faster) to filter the list first:
IOrderedEnumerable<String[]> scoresDesc =
list.Where(ld => lineData[1] == 1)
.OrderByDescending(ld => lineData[0]);
(this is where var eases some pain, since you aren't bound to the return type)

If you want to filter for those of difficulty 1, you can use the .Where() extension method, if you want to sort by more than one field, you can use the .ThenBy() extension method.

first filter then order:
list.Where(x => x.difficulty == 1).OrderByDescending(ld => lineData[0]);

Related

Linq value, use Where to Remove items

I am trying to remove items from an IQueryable list but the result is only pulling in those items:
public IQueryable<Biz.Data.AllItems> items_GetData()
{
var submissions = Biz.Data.AllItems.LoadNotDeleted().Where(x =>
// these items need to match to remove the item
x.itemOne != null &&
x.itemTwo != null &&
x.itemThree != null));
var filter = new Biz.Data.AllItemsFilter();
return submissions = Biz.Data.Registration.Load(filter).OrderBy(x => x.LastName).ThenBy(x => x.FirstName);
}
Currently, it's only pulling in items that match those instead of removing. I can't use RemoveAll because it's not a List and I don't want to reformat this because it passes through a filter process after this code. Is there another way to remove items that match these results first before it passes through a filter?
As discussed in comments, simply negate the condition in your predicate.
So if this is your original statement:
var itemsThatMatch = list.Where(x => /* some condition */);
This will give you the opposite:
var itemsThatDoNotMatch = list.Where(x => !(/* some condition */));

Filter a collection column by multiple values

I have a collection and I would like to filter it with one of the column contains multiple values. The filter values are dynamically generated and I dont know how many I will get.
I tried the following without success:
var input = #"was.Name.Contains(""Test"") || was.Name.Contains(""Test2"")";
var test = collection.Where(was => input)).ToList();
Assuming you receive the filter values as a CSV string:
var csvFilters = "Test1, Test2";
// split by ',', remove empty entries,
// trim each filter and store the result in a list
var filters = csvFilters.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList();
// return items in collection whose Name property
// is equal to any of the items in filters
var result = collection.Where(x => filters.Contains(x.Name)).ToList();
This should translate to the following SQL:
SELECT * FROM collection c
WHERE c.Name IN ('Test1', 'Test2')
I guess you want to use LINQ. The question is, how the "filter" values are kept? I'll answer in the way I understand your question.
If input is supposed to be a condition then I'd suggest using Func<Object,bool>. This means, the input would be the condition you're looking for, and if found, it would return true.
Here is a simple example:
IEnumerable <T> FindElements (Func<Object, bool> condition, IEnumerable<T> inputList)
{
List<T> outputList = new List<T>();
foreach(var element in inputList)
{
if(condition != null && condition(element))
outputList.Add(element);
}
return outputList;
}
Then, if you call the function given exemplary parameters:
string input[] = {"Test1","Test2"};
foreach(string s in input)
{
targetList = FindElements(element=>((cast)element).Name.Contains(s), collection);
}
You should get all elements in collection which name has Test1 or Test2. Cast is of course name of the class which element instantiates.

Update IList data

I have used two IList object. In first list, it contains 10 records and the second contains 5 records. Now I want to update the first IList with second IList data.
foreach (ObjList newlist in New)
{
ObjList list = ExtList.FirstOrDefault(x => x.Id == newlist.Id);
if (list!= null)
{
ExtList.Remove(list);
ExtList.Add(newlist);
}
}
I tried the above. But the added object appended at the end of the list. So sort order changed. I need it in the same existing order.
Updated
I tried the sorting, but it is not sorted.
ExtList.OrderBy(x => x.Id);
You can try using IList.Insert() to add new item at the index of to-be-replaced item, so that you can keep the list in it's original order :
ObjList list = ExtList.FirstOrDefault(x => x.Id == newlist.Id);
if (list!= null)
{
var position = ExtList.IndexOf(list);
ExtList.Insert(position, newlist);
ExtList.Remove(list);
}
If you want to keep the order after update, you can :
ExtList=(from e in ExtList
join n in New
on e.Id equals n.Id into left
from n in left.DefaultIfEmpty()
select new /*Class Name */
{
Id=e.Id,
Name=n==null?e.Name:n.Name
}).ToList();
You should use ExtList.IndexOf instead ExtList.FirstOrDefault.
And then use ExtList.Insert to given index instead ExtList.Add

LINQ for removing elements that are started with other element from list

I have a list List<string> with some paths.
C:\Dir\Test\
C:\MyDir\
C:\YourDir\
C:\Dir\
I want to go through all the elements (using LINQ) and remove entries that are started with other element from my list.
In my example C:\Dir\Test\ starts with C:\Dir\ - so I want to remove C:\Dir\Test\.
Use List<T>.RemoveAll() method:
sourceList.RemoveAll(x => sourceList.Any(y => x != y && x.StartsWith(y)));
Try this:
myInitialList.RemoveAll(x =>myInitialList.Any(q => q != x && q.StartsWith(x)));
Or if you want to keep the original list, this is a way to get all the records that do not match your criteria:
List<string> resultList = myInitialList.Except(x => myInitialList.Any(q => q != x && q.StartsWith(x)));
How about
mylist = mylist.Where(a => mylist.All(b => b == a || !a.StartsWith(b)))
.Distinct()
.ToList();
This will return a new list where there isn't another item in the list that it starts with.
It has the extra check to allow returning the value where there string is the same, otherwise all items would be removed from the list.
Finally the distinct call means that two occurrences of the same string are removed.
Building on nsinreal's comment and solution you could do something like
myList = myList.OrderBy(d => d)
.Aggregate(new List<string>(),
(list, item) => {
if (!list.Any(x => item.StartsWith(x)))
list.Add(item);
return list;
}).ToList();
This reduces the complexity of the solution by reducing the size of the search list for each test. It still requires an initial sort.
Personally I find this alternative solution harder to read and my first answer is more expressive the problem to solve.
The most efficient way is IMO to sort the paths, then iterate them and return only the ones not starting as one of the previous, i.e. :
public static IEnumerable<string>
GetRootPathsOfSet(this IEnumerable<string> paths)
{
var sortedSet = new SortedSet<string>(paths,
StringComparer.CurrentCultureIgnoreCase);
string currRoot = null;
foreach (var p in sortedSet)
{
if (currRoot == null ||
!p.StartsWith(currRoot, StringComparison.InvariantCultureIgnoreCase))
{
currRoot = p;
yield return currRoot;
}
}
}
Some notes:
All the paths MUST terminate with a trailing back-slash, otherwise the StartsWith approach is not safe (e.g. C:\Dir and C:\Directory)
This code uses case-insensitive comparison
I'm not using pure LINQ here, but it's an extension method

How can i sort Generic list with Linq?

How can i sort myScriptCellsCount.MyCellsCharactersCount (list int type) in linq
public class MyExcelSheetsCells
{
public List<int> MyCellsCharactersCount { get; set; }
public MyExcelSheetsCells()
{
MyCellsCharactersCount = new List<int>();
}
}
void ArrangedDataList(DataTable dTable)
{
DAL.MyExcelSheets myexcelSheet = new DAL.MyExcelSheets();
myScriptCellsCount = new TestExceltoSql.DAL.MyExcelSheetsCells();
foreach (DataColumn col in dTable.Columns)
myexcelSheet.MyColumnNames.Add(col.ColumnName.ToString());
foreach(DataColumn dc in dTable.Columns)
foreach (DataRow dr in dTable.Rows)
myScriptCellsCount.MyCellsCharactersCount.Add(dr[dc].ToString().Length);
//How can i sort desc
//myScriptCellsCount.MyCellsCharactersCount = from list in myScriptCellsCount.MyCellsCharactersCount
// orderby list.CompareTo( descending
// select list;
CreatSqlTable(myexcelSheet.MyColumnNames, dTable.TableName, myScriptCellsCount.MyCellsCharactersCount[0].ToString());
myscript.WriteScript(myscript.SqlScripts);
}
// using Linq
MyCellsCharactersCount.OrderBy(x => x); // ascending
MyCellsCharactersCount.OrderByDescending(x => x); // descending
or
// not using Linq
MyCellsCharactersCount.Sort(); // ascending
MyCellsCharactersCount.Sort().Reverse(); // descending
You can use OrderBy or Sort, but there is a difference between the 2 that you should understand:
If you do sort, it sorts your list "in place", so in this example, the variable "list" gets sorted:
// you can manipulate whether you return 1 or -1 to do ascending/descending sorts
list.Sort((x, y) =>
{
if (x > y) return 1;
else if (x == y) return 0;
else return -1;
});
If you do an OrderBy, the original list is unaffected, but a new, sorted enumerable is returned:
var sorted = list.OrderByDescending(x => x)
Edit
This answer was recently upvoted, so I reviewed it. In my original response, I left out a really important detail:
If you use the LINQ code above (second example), the sort is going to occur every time you iterate over the variable "sorted". So, if you have it in more than 1 foreach, you will repeat the sort. To avoid that, change the code above to:
var sorted = list.OrderByDescending(x => x).ToList(); // or .ToArray()
That will force the enumerator to run, and will store the result in sorted.
If you are only going to enumerate it once, you can leave out the ToList/ToArray call.
You should be able to use the OrderBy method on your list.
IEnumerable sortedList = myScriptCellsCount.MyCellsCharactersCount.OrderBy(anInt => anInt);
var sorted = from i in MyCellsCharacterCount orderby i descending select i;
Take a look at this answer. You can replace "Process" with your object by getting typeOf your List.
Cheers

Categories