List.Find() issue - c#

I am wondering why I have this problem with my list.
I am trying to retrieve the object for the previous index, so -1;
Is that the good way to do ?
thePreviousList.Find(previousItem => previousItem.Id - 1);

No, the best way is to use the index:
var item = thePreviousList[indexofItem];
var previousItem = thePreviousList[indexofItem -1];
But probably i've misunderstood your requirement.
If you want to find an item by it's ID(assuming that it has this property):
var idToFind=4711;
var thisIndex = thePreviousList.Where((i, index) => i.ID==idToFind)
.Select((i, index) => index).FirstOrDefault();
if(thisIndex > 0)
{
var previousItem = thePreviousList[thisIndex - 1];
}

Find
Works with predicates (msdn)

It sounds like you want:
if (currentItem.ID != 0)
{
var previousItem = list[currentItem.ID - 1];
// Use previous item...
}
else
{
// This was the first item in the list.
}
No need to go over the whole list, if you're sure that the ID matches the index in the list.

Judging by the comment you made on Tim's answer, you want something like the following:
thePreviousList.ElementAt(thePreviousList.Count - 2);
Note that the -2 is because your indexing (used by ElementAt) is 0 based (that justifies -1) and then you want the penultimate element (another -1).

Related

How to quickly cut up and reassemble a C# List?

The setup:
I have a session variable that carries a list of IDs, pipe-delimited. The IDs are related to views in my site, and related to a breadcrumb builder.
Session["breadCrumb"] = "1001|1002|1003|1004";
If I'm on the view that corresponds to 1002, I'd like to cut everything AFTER that id out of the session variable.
I'd thought to use something like:
var curView = "1002";
if (Session["breadCrumb"] != null) {
var crumb = Session["breadCrumb"].ToString().Split('|').ToList();
var viewExists = crumb.Any(c => c.Value == curView);
if (viewExists) {
//remove everything after that item in the array.
}
}
But I'm wide open to methodologies.
You could use TakeWhile to get back only the items from the splitted list that precede the currentView.
var curView = "1002";
if (Session["breadCrumb"] != null)
{
var crumb = Session["breadCrumb"].ToString().Split('|').ToList();
var viewExists = crumb.TakeWhile(c => c != curView).ToList();
viewExists.Add(curView);
string result = string.Join("|",viewExists);
}
While this approach works I think that also the previous answer (now wrongly deleted from Mr. Andrew Whitaker) was correct. Using IndexOf should be faster with less splitting, looping, joining strings. I suggest Mr Whitaker to undelete its answer.
EDIT
This is from the deleted answer from Mr.Whitaker.
I will repost here because I think that its approach is simpler and should give better perfomances, so future readers could see also this option.
var crumb = Session["breadCrumb"].ToString()
int index = crumb.IndexOf(curView);
if (index >= 0)
{
Session["breadCrumb"] = crumb.Substring(0, index + curView.Length);
}
If Andrew decide to undelete its answer I will be glad to remove this part. Just let me know.
You could just store a List<string> in the Session directly. This saves you from having to split/concat the string manually. I know this does not answer the question directly, but I believe it is a superior solution to that.
var curView = "1002";
var crumb = Session["breadCrumb"] as List<string>;
if (crumb != null) {
var viewExists = crumb.Any(c => c.Value == curView);
if (viewExists) {
// remove everything after that item in the array.
}
}
I almost regret this, but frankly I'd just go for a regular expression:
var result = Regex.Replace(input, "(?<=(\\||^)" + current + ")(?=\\||$).*", "");
This does not directly tell you if the current view existed in the input, but even though this is also possible with the regex in this particular instance another, dead simple test exists:
var viewExists = result.Length != current.Length;

How can I filter a list of strings, creating a list containing only those meeting my criteria?

In my application I want the program to search through a list, testing each list element. If the list element is the required length I then want this to be inserted into a new list. Below is the code I have already
List<string> foo = new List<string>();
List<string> newFoo = new List<string>();
for (int h = 0; h < l; h++);
{
// Here I want to search through every element of foo and if the element
// length is greater than say 5 i want to add it to the newFoo
}
I don't know how to search through each element and any examples I can find use LINQ which I don't want to do as I'm sure there is a simpler way. Any help much appreciated.
It sounds like you're looking for a foreach loop:
foreach (string element in foo)
{
if (element.Length > 5)
{
newFoo.Add(element);
}
}
However, assuming you start with an empty newFoo list, this is better done with LINQ:
List<string> newFoo = foo.Where(x => x.Length > 5).ToList();
Or if you already have an existing list, you can use:
newFoo.AddRange(foo.Where(x => x.Length > 5));
(In my experience it's more common to be creating a new list, mind you.)
If you're new to C#, you should probably make sure you understand the first form before you move on to use LINQ, lambda expressions etc.
Note that if you really, really want to use a straight for loop instead of a foreach loop, you can do so:
for (int i = 0; i < foo.Count; i++)
{
string element = foo[i];
if (element.Length > 5)
{
newFoo.Add(element);
}
}
... but I'd strongly recommend using foreach any time you want to iterate over a sequence and don't really care about the index of each entry.
You may use something like this (foreach loop):
foreach (String item in foo)
if (!Object.ReferenceEquals(null, item)) // <- be careful with nulls!
if (item.Length > 5)
newFoo.Add(item);
Or if you prefer index based access
for (int i = 0; i < foo.Count; ++i)
if (!Object.ReferenceEquals(null, foo[i])) // <- be careful with nulls!
if (foo[i].Length > 5)
newFoo.Add(foo[i]);
Yet another possibility is LINQ, e.g.
// Do not forget the nulls...
newFoo.AddRange(foo.Where(item => Object.ReferenceEquals(null, item) ? false : item.Length > 5));
Without Linq, you can do it with a simple loop
foreach(var f in foo)
{
if(f.Length > 5)
{
newFoo.Add(f);
}
}
But with Linq, it's even simpler
newFoo = foo.Where(f => f.Length > 5).ToList()
You can use LINQ to filter items with Length > 5 to your newFoo List
List<string> newFoo = foo.Where(r => r.Length > 5).ToList();
If you want to use simple for loop then:
for (int h = 0; h < foo.Count; h++)
{
if (foo[h] != null && foo[h].Length > 5)
newFoo.Add(foo[h]);
}
(Remember to remove the ; semicolon at the end of your for-loop, currently it will not do anything since it will consider ; as the only statement for the loop to work on)

Searching a list of strings in C#

So I want to use one of these LINQ functions with this List<string> I have.
Here's the setup:
List<string> all = FillList();
string temp = "something";
string found;
int index;
I want to find the string in all that matches temp when both are lower cased with ToLower(). Then I'll use the found string to find it's index and remove it from the list.
How can I do this with LINQ?
I get the feeling that you don't care so much about comparing the lowercase versions as you do about just performing a case-insensitive match. If so:
var listEntry = all.Where(entry =>
string.Equals(entry, temp, StringComparison.CurrentCultureIgnoreCase))
.FirstOrDefault();
if (listEntry != null) all.Remove(listEntry);
OK, I see my imperative solution is not getting any love, so here is a LINQ solution that is probably less efficient, but still avoids searching through the list two times (which is a problem in the accepted answer):
var all = new List<string>(new [] { "aaa", "BBB", "Something", "ccc" });
const string temp = "something";
var found = all
.Select((element, index) => new {element, index})
.FirstOrDefault(pair => StringComparer.InvariantCultureIgnoreCase.Equals(temp, pair.element));
if (found != null)
all.RemoveAt(found.index);
You could also do this (which is probably more performant than the above, since it does not create new object for each element):
var index = all
.TakeWhile(element => !StringComparer.InvariantCultureIgnoreCase.Equals(temp, element))
.Count();
if (index < all.Count)
all.RemoveAt(index);
I want to add to previous answers... why don't you just do it like this :
string temp = "something";
List<string> all = FillList().Where(x => x.ToLower() != temp.ToLower());
Then you have the list without those items in the first place.
all.Remove(all.FirstOrDefault(
s => s.Equals(temp,StringComparison.InvariantCultureIgnoreCase)));
Use the tool best suited for the job. In this case a simple piece of procedural code seems more appropriate than LINQ:
var all = new List<string>(new [] { "aaa", "BBB", "Something", "ccc" });
const string temp = "something";
var cmp = StringComparer.InvariantCultureIgnoreCase; // Or another comparer of you choosing.
for (int index = 0; index < all.Count; ++index) {
string found = all[index];
if (cmp.Equals(temp, found)) {
all.RemoveAt(index);
// Do whatever is it you want to do with 'found'.
break;
}
}
This is probably as fast as you can get, because:
Comparison it done in place - there is no creation of temporary uppercase (or lowercase) strings just for comparison purposes.
Element is searched only once (O(index)).
Element is removed in place without constructing a new list (O(all.Count-index)).
No delegates are used.
Straight for tends to be faster than foreach.
It can also be adapted fairly easily should you want to handle duplicates.

Getting last two rows in a text file

So I am creating a list of lines in a text file like this:
var lines = File.ReadAllLines("C:\\FileToSearch.txt")
.Where(x => !x.EndsWith("999999999999"));
and looping through the lines like this
foreach (var line in lines)
{
if (lineCounter == 1)
{
outputResults.Add(oData.ToCanadianFormatFileHeader());
}
else if (lineCounter == 2)
{
outputResults.Add(oData.ToCanadianFormatBatchHeader());
}
else
{
oData.FromUsLineFormat(line);
outputResults.Add(oData.ToCanadianLineFormat());
}
lineCounter = lineCounter + 1;
textBuilder += (line + "<br>");
}
Similary like I access the first two rows I would like to access the last and second last row individually
Here you can take advantage of LINQ once again:
var numberOfLinesToTake = 2;
var lastTwoLines = lines
.Skip(Math.Max(0, lines.Count() - numberOfLinesToTake))
.Take(numberOfLinesToTake);
var secondToLastLine = lastTwoLines.First();
var lastLine = lastTwoLines.Last();
Or, if you want to retrieve them individually:
var lastLine = lines.Last();
var secondToLastLine =
lines.Skip(Math.Max(0, lines.Count() - 2)).Take(1).First();
I added .First() to the end, because .Take(1) will return an array containing one item, which we then grab with First(). This can probably be optimized.
Again, you might want to familiarize yourself with LINQ since it's a real time-saver sometimes.
This one of the problems of using var when it's not appropriate.
ReadAllLines returns an array of strings: string[]
You can get the length of the array and index backwards from the end.

Compare adjacent list items

I'm writing a duplicate file detector. To determine if two files are duplicates I calculate a CRC32 checksum. Since this can be an expensive operation, I only want to calculate checksums for files that have another file with matching size. I have sorted my list of files by size, and am looping through to compare each element to the ones above and below it. Unfortunately, there is an issue at the beginning and end since there will be no previous or next file, respectively. I can fix this using if statements, but it feels clunky. Here is my code:
public void GetCRCs(List<DupInfo> dupInfos)
{
var crc = new Crc32();
for (int i = 0; i < dupInfos.Count(); i++)
{
if (dupInfos[i].Size == dupInfos[i - 1].Size || dupInfos[i].Size == dupInfos[i + 1].Size)
{
dupInfos[i].CheckSum = crc.ComputeChecksum(File.ReadAllBytes(dupInfos[i].FullName));
}
}
}
My question is:
How can I compare each entry to its neighbors without the out of bounds error?
Should I be using a loop for this, or is there a better LINQ or other function?
Note: I did not include the rest of my code to avoid clutter. If you want to see it, I can include it.
Compute the Crcs first:
// It is assumed that DupInfo.CheckSum is nullable
public void GetCRCs(List<DupInfo> dupInfos)
{
dupInfos[0].CheckSum = null ;
for (int i = 1; i < dupInfos.Count(); i++)
{
dupInfos[i].CheckSum = null ;
if (dupInfos[i].Size == dupInfos[i - 1].Size)
{
if (dupInfos[i-1].Checksum==null) dupInfos[i-1].CheckSum = crc.ComputeChecksum(File.ReadAllBytes(dupInfos[i-1].FullName));
dupInfos[i].CheckSum = crc.ComputeChecksum(File.ReadAllBytes(dupInfos[i].FullName));
}
}
}
After having sorted your files by size and crc, identify duplicates:
public void GetDuplicates(List<DupInfo> dupInfos)
{
for (int i = dupInfos.Count();i>0 i++)
{ // loop is inverted to allow list items deletion
if (dupInfos[i].Size == dupInfos[i - 1].Size &&
dupInfos[i].CheckSum != null &&
dupInfos[i].CheckSum == dupInfos[i - 1].Checksum)
{ // i is duplicated with i-1
... // your code here
... // eventually, dupInfos.RemoveAt(i) ;
}
}
}
I have sorted my list of files by size, and am looping through to
compare each element to the ones above and below it.
The next logical step is to actually group your files by size. Comparing consecutive files will not always be sufficient if you have more than two files of the same size. Instead, you will need to compare every file to every other same-sized file.
I suggest taking this approach
Use LINQ's .GroupBy to create a collection of files sizes. Then .Where to only keep the groups with more than one file.
Within those groups, calculate the CRC32 checksum and add it to a collection of known checksums. Compare with previously calculated checksums. If you need to know which files specifically are duplicates you could use a dictionary keyed by this checksum (you can achieve this with another GroupBy. Otherwise a simple list will suffice to detect any duplicates.
The code might look something like this:
var filesSetsWithPossibleDupes = files.GroupBy(f => f.Length)
.Where(group => group.Count() > 1);
foreach (var grp in filesSetsWithPossibleDupes)
{
var checksums = new List<CRC32CheckSum>(); //or whatever type
foreach (var file in grp)
{
var currentCheckSum = crc.ComputeChecksum(file);
if (checksums.Contains(currentCheckSum))
{
//Found a duplicate
}
else
{
checksums.Add(currentCheckSum);
}
}
}
Or if you need the specific objects that could be duplicates, the inner foreach loop might look like
var filesSetsWithPossibleDupes = files.GroupBy(f => f.FileSize)
.Where(grp => grp.Count() > 1);
var masterDuplicateDict = new Dictionary<DupStats, IEnumerable<DupInfo>>();
//A dictionary keyed by the basic duplicate stats
//, and whose value is a collection of the possible duplicates
foreach (var grp in filesSetsWithPossibleDupes)
{
var likelyDuplicates = grp.GroupBy(dup => dup.Checksum)
.Where(g => g.Count() > 1);
//Same GroupBy logic, but applied to the checksum (instead of file size)
foreach(var dupGrp in likelyDuplicates)
{
//Create the key for the dictionary (your code is likely different)
var sample = dupGrp.First();
var key = new DupStats() {FileSize = sample.FileSize, Checksum = sample.Checksum};
masterDuplicateDict.Add(key, dupGrp);
}
}
A demo of this idea.
I think the for loop should be : for (int i = 1; i < dupInfos.Count()-1; i++)
var grps= dupInfos.GroupBy(d=>d.Size);
grps.Where(g=>g.Count>1).ToList().ForEach(g=>
{
...
});
Can you do a union between your two lists? If you have a list of filenames and do a union it should result in only a list of the overlapping files. I can write out an example if you want but this link should give you the general idea.
https://stackoverflow.com/a/13505715/1856992
Edit: Sorry for some reason I thought you were comparing file name not size.
So here is an actual answer for you.
using System;
using System.Collections.Generic;
using System.Linq;
public class ObjectWithSize
{
public int Size {get; set;}
public ObjectWithSize(int size)
{
Size = size;
}
}
public class Program
{
public static void Main()
{
Console.WriteLine("start");
var list = new List<ObjectWithSize>();
list.Add(new ObjectWithSize(12));
list.Add(new ObjectWithSize(13));
list.Add(new ObjectWithSize(14));
list.Add(new ObjectWithSize(14));
list.Add(new ObjectWithSize(18));
list.Add(new ObjectWithSize(15));
list.Add(new ObjectWithSize(15));
var duplicates = list.GroupBy(x=>x.Size)
.Where(g=>g.Count()>1);
foreach (var dup in duplicates)
foreach (var objWithSize in dup)
Console.WriteLine(objWithSize.Size);
}
}
This will print out
14
14
15
15
Here is a netFiddle for that.
https://dotnetfiddle.net/0ub6Bs
Final note. I actually think your answer looks better and will run faster. This was just an implementation in Linq.

Categories

Resources