Better Coding Practice Than If-Else - c#

Lets say I have this code.
int count = 0;
int id = GetFirstId();
count = getCountById(id);
if(count == 0){
id = GetSecondId();
count = getCountById(id);
if(count == 0){
id = GetThirdId();
count = getCountById(id);
}
}
Is there a better way to do this. Is something like a LOOP and CASE Statement inside better?

If you have different method, you can list the methods and then use LINQ to get first non-zero count. It will work if all of your GetXXXId have same signature.
var idGetters = new Func<int>[]
{
GetFirstId,
GetSecondId,
GetThirdId
// and so on
};
var count = idGetters
.Select(x => x())
.Select(GetCountById)
.SkipWhile(x => x == 0)
.FirstOrDefault();

Related

Find index of element from IQueryable

Hi I am trying to find the position of specific element from IQueryable. I dont want to covert to list then search for it because the process is heavy if the number of element is huge.
I tried .TakeWhile(x => x.ItemId.Equals(ItemId)) but it show me function not supported.
Below is how I doing it now using loop through the query. Is there any better approach for it?
IQueryable<CustomSearchResultItem> contextQueryable = context.GetQueryable<CustomSearchResultItem>().Where(query);
if (ItemId != ID.Null)
{
int i = 0;
foreach (var x in contextQueryable)
{
if(x.ItemId.Equals(ItemId))
{
Position = i;
break;
}
i++;
}
}
Something like this should work:
var contextQueryable = context.GetQueryable<CustomSearchResultItem>().Where(query).GetResults();
var result = contextQueryable.Select((x, i) => new { Item = x, Index = i })
.FirstOrDefault(itemWithIndex => itemWithIndex.Item.Document.ItemId.Guid == ItemId);
if (result != null)
index = result.Index;

What would be the LINQ solution for this query in c#?

I have a class RuleDetail:
public class RuleDetail
{
public int RuleDetailId;
public int StartYear;
}
I have a List of objects of type RuleDetail:
RuleDetailId=1, StartYear=0
RuleDetailId=2, StartYear=2
RuleDetailId=3, StartYear=4
RuleDetailId=4, StartYear=10
RuleDetailId=5, StartYear=13
RuleDetailId=6, StartYear=18
I will be given a number say x (x always >= 0); for that I need to find the RuleDetail object in the above List which matches these conditions:
Get the RuleDetail object where x equals to StartYear OR
Get the RuleDetail object of the max(StartYear) when StartYear < x
Assuming I have these variables
RuleDetail[] ruleDetails = null;
int x = -1;
// ruleDetails populated
// x populated
This is the code I have come up with:
bool found = false;
RuleDetail ruleDetail = null;
RuleDetail oldRuleDetail = null;
for (int i=0; i<ruleDetails.Length; i++)
{
if (ruleDetails[i].StartYear == x)
{
found = true;
ruleDetail = ruleDetails[i];
break;
}
else if (ruleDetails[i].StartYear > x)
{
found = true;
ruleDetail = oldRuleDetail;
break;
}
oldRuleDetail = ruleDetails[i];
}
if (!found)
{
ruleDetail = oldRuleDetail;
}
return ruleDetail;
The code is working ok. But how can I do this in LINQ?
Thanks
var output = ruleDetails.OrderBy(rule => rule.StartYear).Where(rule => rule.StartYear <= x).Last()
If the list is already in StartYear order then ....
var output = ruleDetails.Where(rule => rule.StartYear <= x).Last()
You can use
ruleDetails.FirstOrDefault(rd => rd.StartYear == x)
?? ruleDetails.Where(rd => rd.StartYear < x).OrderByDescending(rd => rd.StartYear).First();
which is a clear separation of your two requirements, but it is actually more concise to use
ruleDetails.Where(rd => rd.StartYear <= x).OrderByDescending(rd => rd.StartYear).First()
var res1 = (from a in ruleDetails where a.StartYear == x select a).First();
var res2 = (from a in ruleDetails orderby a.StartYear where a.StartYear < x select a).Last();
Here's a real simple LINQ snippet to accomplish what you're trying to do here. I'm writing this with the assumption that your list is ordered as that's what your current code suggests.
We'll first filter the list down to entries that are either the target year or less than it, then take the highest remaining element.
var filteredList = ruledetails.Where(r => r.StartYear <= targetYear);
return filteredList.Last;
It's kinda gross, but maybe something like:
var result = ruleDetails
.OrderBy(r => r.StartYear)
.FirstOrDefault(r => r.StartYear == x || r.StartYear == ruleDetails.Select(y => y.StartYear).Max());
(A) if the list is is initially sorted by StartYear, then
var result = ruleDetails.LastOrDefault(r => r.StartYear <= startYear);
(B) if the list is not sorted, then
var result = ruleDetails.Where(r => r.StartYear <= startYear)
.Aggregate((RuleDetail)null, (a, b) => a == null || a.StartYear < b.StartYear ? b : a);

Select interval linq

Is there some way with LINQ to select certain numbers with shortcut criteria.
Like this:
I have numbers from 1 to 10000.
My criteria is (4012..4190|4229), meaning take numbers between 4012 to 4190 and number 4229:
static int[] test(string criteria)
{
// criteria is 4012..4190|4229
// select numbers from lab where criteria is met
int[] lab = Enumerable.Range(0, 10000).ToArray();
return lab;
}
This should be enough for your case:
return lab.Where((int1) => (int1 >= 4012 && int1 <= 4190) || int1 == 4229).ToArray();
Also a quick way of parsing your criteria would be to use RegEx:
Regex r = new Regex(#"\d+");
MatchCollection m = r.Matches(criteria);
int start = int.Parse(m[0].Value);
int end = int.Parse(m[1].Value);
int specific = int.Parse(m[2].Value);
return lab.Where((int1) => (int1 >= start && int1 <= end) || int1 == specific).ToArray();
If your criteria is always a string, you need some way to parse it, to Func<int, bool, but it's not LINQ specific. In the end you'll need something like this:
Func<int, bool> predicate = Parse(criteria);
return lab.Where(predicate).ToArray();
where very basic implementation of Parse might look as follows:
public static Func<int, bool> Parse(string criteria)
{
var alternatives = criteria
.Split('|')
.Select<string, Func<int, bool>>(
token =>
{
if (token.Contains(".."))
{
var between = token.Split(new[] {".."}, StringSplitOptions.RemoveEmptyEntries);
int lo = int.Parse(between[0]);
int hi = int.Parse(between[1]);
return x => lo <= x && x <= hi;
}
else
{
int exact = int.Parse(token);
return x => x == exact;
}
})
.ToArray();
return x => alternatives.Any(alt => alt(x));
}
You can concatenate two sequenses
int[] lab = Enumerable.Range(4012, 4190-4012).Concat(Enumerable.Range(4229,1)).ToArray();
Update:
you need to parse incoming criteria first
static int[] test(string criteria)
{
// criteria is 4012..4190|4229
// select numbers from lab where criteria is met
// assume you parsed your criteria to 2 dimentional array
// I used count for second part for convience
int[][] criteriaArray = { new int[]{ 4012, 50 }, new int[]{ 4229, 1 } };
var seq = Enumerable.Range(criteriaArray[0][0], criteriaArray[0][1]);
for (int i = 1; i < criteriaArray.Length; i++)
{
int start = criteriaArray[i][0];
int count = criteriaArray[i][1];
seq = seq.Concat(Enumerable.Range(start, count));
}
return seq.ToArray();
}
You could :
Flatten[{Range[4012, 4190], 4229}]
And in some way this would work as well 4012..4190|4229, but answer is exactly that - list of items from 4012 to 4190 and item 4229.
Lambda just imitates pure functions. However unless you have free wolfram kernel, using this approach might no be most cost effective. However, you do not need to write boilerplate code.

List<T>.Any(); How to get index of matched item?

mI am comparing Listview items with Generic List items with List.Any Method like this:
foreach (ListViewItem itemRow in lstviewAddsheets.Items)
{
if (InvalidSheets.Any(x => x != null && x.FilePath == itemRow.Tag.ToString()))
{
//Math found
}
}
Please tell me, how to get InvalidSheets list index which was matched with itemRow.Tag.ToString().
Since there seems some debate about how much faster it would be to use List.FindIndex() instead of Linq to find the index, I wrote a test program.
This assumes that you only care about finding the index of the first matching item in a list. It doesn't handle multiple matching items.
Also note that this test is worst-case in that the matching item is at the very end of the list.
My results for an x86 release build (run on Windows 8 x64, quad core processor):
Calling Via FindIndex() 100 times took 00:00:00.9326057
Calling Via Linq 100 times took 00:00:04.0014677
Calling Via FindIndex() 100 times took 00:00:00.8994282
Calling Via Linq 100 times took 00:00:03.9179414
Calling Via FindIndex() 100 times took 00:00:00.8971618
Calling Via Linq 100 times took 00:00:03.9134804
Calling Via FindIndex() 100 times took 00:00:00.8963758
showing that List.FindIndex() is roughly four times faster than using Linq.
Here's the test code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Demo
{
class Test
{
public string FilePath;
}
class Program
{
private void run()
{
int count = 1000000;
List<Test> list = new List<Test>(count);
for (int i = 0; i < count; ++i)
list.Add(new Test{ FilePath = i.ToString()});
string target = (count-1).ToString();
for (int trial = 0; trial < 4; ++trial)
{
Action viaFindIndex =
(
() =>
{
int index = list.FindIndex(x => (x != null) && (x.FilePath == target));
}
);
Action viaLinq =
(
() =>
{
int index = list.Select((x, i) => new { Item = x, Index = i })
.First(x => (x != null) && (x.Item.FilePath == target))
.Index;
}
);
viaFindIndex.TimeThis("Via FindIndex()", 100);
viaLinq.TimeThis("Via Linq", 100);
}
}
private static void Main()
{
new Program().run();
}
}
static class DemoUtil
{
public static void TimeThis(this Action action, string title, int count = 1)
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < count; ++i)
action();
Console.WriteLine("Calling {0} {1} times took {2}", title, count, sw.Elapsed);
}
}
}
So given that List.FindIndex() is both much faster AND much easier to read than using the Linq, I can see no reason to use Linq to solve this particular problem.
int index = list.FindIndex(x => (x != null) && (x.FilePath == target));
versus
int index = list.Select((x, i) => new { Item = x, Index = i })
.First(x => (x != null) && (x.Item.FilePath == target))
.Index;
The first version wins on all counts IMO.
You can do this
int index = InvalidSheets.FindIndex(x => x != null && x.FilePath == itemRow.Tag.ToString());
if you want to get the object directly then do this
var matchedObject = InvalidSheets.FirstOrDefault(x => x != null && x.FilePath == itemRow.Tag.ToString());
Here is how you can get the index:
var index = InvalidSheets.Select((x, i) => new {Item = x, Index = i})
.First(x => x.Item != null && x.Item.FilePath == itemRow.Tag.ToString())
.Index;
However you might want to refactor this with FirstOrDefault like this:
foreach (ListViewItem itemRow in lstviewAddsheets.Items)
{
var sheet = InvalidSheets.Select((x, i) => new {Item = x, Index = i})
.FirstOrDefault(x => x.Item != null && x.Item.FilePath == itemRow.Tag.ToString());
if (sheet != null)
{
var index = sheet.Index;
}
}
Try this:
InvalidSheets.IndexOf(InvalidSheets.First(x => x != null && x.FilePath == itemRow.Tag.ToString()))
It will get index of first invalid sheet matching the predicate
You can project the index with the overload, therefore you need to select an anonymous type:
var invalids = InvalidSheets.Select((s, i) => { Sheet=s, Index=i })
.Where(x => x.Sheet != null && x.Sheet.FilePath == itemRow.Tag.ToString()));
bool anyInvalid = invalids.Any(); // is any invalid
IEnumerable<int> indices = invalids.Select(x => x.Index);// if you need all indices

Does Linq provide a way to easily spot gaps in a sequence?

I am managing a directory of files. Each file will be named similarly to Image_000000.png, with the numeric portion being incremented for each file that is stored.
Files can also be deleted, leaving gaps in the number sequence. The reason I am asking is because I recognize that at some point in the future, the user could use up the number sequence unless I takes steps to reuse numbers when they become available. I realize that it is a million, and that's a lot, but we have 20-plus year users, so "someday" is not out of the question.
So, I am specifically asking whether or not there exists a way to easily determine the gaps in the sequence without simply looping. I realize that because it's a fixed range, I could simply loop over the expected range.
And I will unless there is a better/cleaner/easier/faster alternative. If so, I'd like to know about it.
This method is called to obtain the next available file name:
public static String GetNextImageFileName()
{
String retFile = null;
DirectoryInfo di = new DirectoryInfo(userVars.ImageDirectory);
FileInfo[] fia = di.GetFiles("*.*", SearchOption.TopDirectoryOnly);
String lastFile = fia.Where(i => i.Name.StartsWith("Image_") && i.Name.Substring(6, 6).ContainsOnlyDigits()).OrderBy(i => i.Name).Last().Name;
if (!String.IsNullOrEmpty(lastFile))
{
Int32 num;
String strNum = lastFile.Substring(6, 6);
String strExt = lastFile.Substring(13);
if (!String.IsNullOrEmpty(strNum) &&
!String.IsNullOrEmpty(strExt) &&
strNum.ContainsOnlyDigits() &&
Int32.TryParse(strNum, out num))
{
num++;
retFile = String.Format("Image_{0:D6}.{1}", num, strExt);
while (num <= 999999 && File.Exists(retFile))
{
num++;
retFile = String.Format("Image_{0:D6}.{1}", num, strExt);
}
}
}
return retFile;
}
EDIT: in case it helps anyone, here is the final method, incorporating Daniel Hilgarth's answer:
public static String GetNextImageFileName()
{
DirectoryInfo di = new DirectoryInfo(userVars.ImageDirectory);
FileInfo[] fia = di.GetFiles("Image_*.*", SearchOption.TopDirectoryOnly);
List<Int32> fileNums = new List<Int32>();
foreach (FileInfo fi in fia)
{
Int32 i;
if (Int32.TryParse(fi.Name.Substring(6, 6), out i))
fileNums.Add(i);
}
var result = fileNums.Select((x, i) => new { Index = i, Value = x })
.Where(x => x.Index != x.Value)
.Select(x => (Int32?)x.Index)
.FirstOrDefault();
Int32 index;
if (result == null)
index = fileNums.Count - 1;
else
index = result.Value - 1;
var nextNumber = fileNums[index] + 1;
if (nextNumber >= 0 && nextNumber <= 999999)
return String.Format("Image_{0:D6}", result.Value);
return null;
}
A very simple approach to find the first number of the first gap would be the following:
int[] existingNumbers = /* extract all numbers from all filenames and order them */
var allNumbers = Enumerable.Range(0, 1000000);
var result = allNumbers.Where(x => !existingNumbers.Contains(x)).First();
This will return 1,000,000 if all numbers have been used and no gaps exist.
This approach has the drawback that it performs rather badly, as it iterates existingNumbers multiple times.
A somewhat better approach would be to use Zip:
allNumbers.Zip(existingNumbers, (a, e) => new { Number = a, ExistingNumber = e })
.Where(x => x.Number != x.ExistingNumber)
.Select(x => x.Number)
.First();
An improved version of DuckMaestro's answer that actually returns the first value of the first gap - and not the first value after the first gap - would look like this:
var tmp = existingNumbers.Select((x, i) => new { Index = i, Value = x })
.Where(x => x.Index != x.Value)
.Select(x => (int?)x.Index)
.FirstOrDefault();
int index;
if(tmp == null)
index = existingNumbers.Length - 1;
else
index = tmp.Value - 1;
var nextNumber = existingNumbers[index] + 1;
Improving over the other answer, use the alternate version of Where.
int[] existingNumbers = ...
var result = existingNumbers.Where( (x,i) => x != i ).FirstOrDefault();
The value i is a counter starting at 0.
This version of where is supported in .NET 3.5 (http://msdn.microsoft.com/en-us/library/bb549418(v=vs.90).aspx).
var firstnonexistingfile = Enumerable.Range(0,999999).Select(x => String.Format("Image_{0:D6}.{1}", x, strExt)).FirstOrDefault(x => !File.Exists(x));
This will iterate from 0 to 999999, then output the result of the String.Format() as an IEnumerable<string> and then find the first string out of that sequence that returns false for File.Exists().
It's an old question, but it has been suggested (in the comments) that you could use .Except() instead. I tend to like this solution a little better since it will give you the first missing number (the gap) or the next smallest number in the sequence. Here's an example:
var allNumbers = Enumerable.Range(0, 999999); //999999 is arbitrary. You could use int.MaxValue, but it would degrade performance
var existingNumbers = new int[] { 0, 1, 2, 4, 5, 6 };
int result;
var missingNumbers = allNumbers.Except(existingNumbers);
if (missingNumbers.Any())
result = missingNumbers.First();
else //no missing numbers -- you've reached the max
result = -1;
Running the above code would set result to:
3
Additionally, if you changed existingNumbers to:
var existingNumbers = new int[] { 0, 1, 3, 2, 4, 5, 6 };
So there isn't a gap, you would get 7 back.
Anyway, that's why I prefer Except over the Zip solution -- just my two cents.
Thanks!

Categories