I've seen the articles on StackOverflow regarding c++ long switch statements, but c# is different in this regard.
Specifically, I would like to replace a hugely-long switch statement in c# code, where each case statement does the same thing, just the field name changes.
The code looks like this:
case Fields.TRANSMITTERCONTACTPHONEEXT:
{
int lengthNeeded = 0;
int currentLength = TransmitterContactTelephoneExtension.Length;
int lengthRequired = TransmitterContactTelephoneExtensionLength;
if (currentLength < lengthRequired)
{
lengthNeeded = lengthRequired - currentLength;
for (int i = 0; i < lengthNeeded; i++)
{
TransmitterContactTelephoneExtension += " ";
}
}
} break;
case Fields.TRANSMITTERFEIN:
{
int lengthNeeded = 0;
int currentLength = TransmitterFEIN.Length;
int lengthRequired = TransmitterFEINLength;
if (currentLength < lengthRequired)
{
lengthNeeded = lengthRequired - currentLength;
for (int i = 0; i < lengthNeeded; i++)
{
TransmitterFEIN += " ";
}
}
} break;
I'd like to just get this down to a single function that can figure out which field I mean without having to use a switch statement. Is there a way to pass in a variable containing the field name?
Place all the changeable values into arrays and index into it. Make sure the enum int values are the same as the data in the target arrays.
var current = (int) Fields.TRANSMITTERCONTACTPHONEEXT;
int lengthNeeded = 0;
int currentLength = LengthData[ current ] ;
int lengthRequired = RequiredData[current ];
if (currentLength < lengthRequired)
{
lengthNeeded = lengthRequired - currentLength;
for (int i = 0; i < lengthNeeded; i++)
{
Extensions[ current ] = Extensions[ current ] + " ";
}
}
This post looks at the pattern of the operations in the switch and does not address any localized deficiencies of individual operations. Please look at each of the individual operations and where needed improve it for optimal operational efficiencies.
This just looks like the code for a string pad function repeated multiple times. You could just have
case Fields.TRANSMITTERCONTACTPHONEEXT:
TransmitterContactTelephoneExtension = TransmitterContactTelephoneExtension.PadRight(TransmitterContactTelephoneExtensionLength, ' ');
break;
...
Any time that you find yourself repeating code over and over, you probably can break it out into a separate function (if one does not already exist) and just call it with the right parameters.
And this also makes me wonder if you need the switch case statement at all, and not just a series of pad statements. But that is going further out (in scope) in your code. And your post does not give us enough info to go there.
Finally, somewhat applicable to your question, my rule of thumb (not originally mine, but I forget where I got it) is that if a method is more than a pageful (intentionally vague term), then I need to break it up into other separate methods. That allows me to look at a method and understand it without scrolling around. It also forces me to separate a longer process into smaller logical steps.
You really should break this out into a method. So that you are not duplicating code over and over. That is inefficient and can lead to potential errors when updating.
Also instead of looping you should make use of the PadRight() method on strings.
I would do this:
case Fields.TRANSMITTERCONTACTPHONEEXT:
TransmitterContactTelephoneExtension = PadString(TransmitterContactTelephoneExtensionLength, TransmitterContactTelephoneExtension);
break;
case Fields.TRANSMITTERFEIN:
TransmitterFEIN = PadString(TransmitterFEINLength, TransmitterFEIN);
break;
private string PadString(int requiredLen, string value)
{
if (value == null) return String.Empty.PadRight(requiredLen, ' '); //Create an empty string when the value is null
return value.PadRight(requiredLen, ' ');
}
Declare the variables in advance, and use the switch only for the assignments that differ:
int currentLength;
int lengthRequired;
switch (whatever) {
case Fields.TRANSMITTERCONTACTPHONEEXT:
currentLength = TransmitterContactTelephoneExtension.Length;
lengthRequired = TransmitterContactTelephoneExtensionLength;
break;
case Fields.TRANSMITTERFEIN:
currentLength = TransmitterFEIN.Length;
lengthRequired = TransmitterFEINLength;
break;
default:
throw new Exception(); // Without this, the compiler will complain about uninitialized variables
}
int lengthNeeded = 0;
if (currentLength < lengthRequired)
{
lengthNeeded = lengthRequired - currentLength;
for (int i = 0; i < lengthNeeded; i++)
{
TransmitterFEIN += " ";
}
}
switch (whatever) {
case Fields.TRANSMITTERCONTACTPHONEEXT:
TransmitterContactTelephoneExtension += " ";
break;
case Fields.TRANSMITTERFEIN:
TransmitterFEIN += " ";
break;
}
Edit: OmegaMan's solution is way better if you have the option of replacing the variables with an array.
To me it seems like the code we can't see needs some refactoring, but based on what we can see, I would recommend doing the following:
// 1. Have a class to hold your data
class FieldData
{
public string Value { get; set; }
public int LengthRequired { get; set; }
public string RightPaddedValue
{
get { return Value.PadRight(LengthRequired, ' '); }
}
}
// 2. Fill your data into a dictionary somehow... for example:
Dictionary<Fields, FieldData> fields = new Dictionary<Fields, FieldData>
{
{
Fields.TRANSMITTERCONTACTPHONEEXT,
new FieldData {
Value = TransmitterContactTelephoneExtension,
LengthRequired = TransmitterContactTelephoneExtensionLength
}
},
{
Fields.TRANSMITTERFEIN,
new FieldData {
Value = TransmitterFEIN,
LengthRequired = TransmitterFEINLength
}
}
};
// 3. Then use the data from that dictionary in your code:
FieldData data = fields[selectedField];
data.RightPaddedValue; // use RightPaddedValue
Related
(this is a library)
The function GetUniqueInt is being called with (5, 5) as the variables.
Currently the code will stall unity to a complete halt, or crash my PC with a memory overflow error.
Does anyone have any ideas as to how I could prevent it from crashing or what is making it crash?
using UnityEngine;
namespace MajorSolution
{
public static class MajorMath
{
public static int[] GetUniqueInt(int intCount, int intLength)
{
int[] returnValue = new int[intCount];
int[] temp = new int[intLength];
for (int a = 0; a < intCount; a++)
{
string create = new string("create".ToCharArray());
switch (create)
{
case "create":
returnValue[a] = GetRandomInt(intCount);
goto case "check";
case "check":
bool alreadyTaken = false;
for (int c = 0; c < returnValue.Length - 1; c++)
{
if (returnValue[a] == returnValue[c])
{
// Already Taken!
alreadyTaken = true;
}
}
if (!alreadyTaken)
{
break;
}
else
{
goto case "create";
}
}
}
Debug.Log(returnValue);
return returnValue;
}
public static int GetRandomInt(int intCount)
{
int[] storage = new int[intCount];
int returnValue = 0;
for (int i = 0; i < intCount; i++)
{
storage[i] = (Mathf.FloorToInt(Random.Range(0, 9)) * (int)Mathf.Pow(10,i));
returnValue += storage[i];
}
return returnValue;
}
}
}
Edit I just realized I did not exactly answer the question of why it is bringing the PC to a halt because you have an infinite loop in the code.
The problem is occurring in the following lines of code, notice what is happening.
case "create":
returnValue[a] = GetRandomInt(intCount);
goto case "check";
In the above block of code you are generating a number and putting it in the returnValue array. Now you jump into your "check" block
case "check":
bool alreadyTaken = false;
for (int c = 0; c < returnValue.Length - 1; c++)
{
if (returnValue[a] == returnValue[c])
{
// Already Taken!
alreadyTaken = true;
}
}
In this block of code you are looping over the entire returnValue array including the value you just inserted in it. Basically you are looping over the array asking if a value that you just put in the array is in the array.
Without knowing exactly what you are trying to do with these methods, I will just make a simple fix suggestion with some minor cleanup
public static int[] GetUniqueInt(int count, int length)
{
var returnValue = new int[count];
var values = new HashSet<int>(); // Used to track what numbers we have generated
for (int i = 0; i < count; ++i)
{
// Generate the number and check to be sure we haven't seen it yet
var number = GetRandomInt(length);
while(values.Contains(number)) // This checks if the number we just generated exists in the HashSet of seen numbers
{
// We get here if the HashSet contains the number. If we have
// seen the number then we need to generate a different one
number = GetRandomInt(length);
}
// When we reach this point, it means that we have generated a new unique number
// Add the number to the return array and also add it to the list of seen numbers
returnValue[a] = number;
values.Add(number); // Adds the number to the HashSet
}
Debug.Log(returnValue);
return returnValue;
}
I did end up removing the using of intLength but from your posted code it was only used to declare a temp array which itself was never used. Based on that, I just removed it entirely.
Based on your comment I updated the fix to use intLength. I made one other minor change. I removed int from the variable names of count and length. Hungarian notation is a lot less common in C# code. Personally, I feel like the code is cleaner and easier to read without the Hungarian notation. The key is to use good variable names that express the intent or make it easier to follow. In this case count is the count (read total number) of numbers you want returned and length is the length of the number. You could consider maybe even renaming that to numberOfDigits to make it clearer that the idea is you are going to create a random number with that number of digits in it.
I am working on one project that will utilize nCalc to work with some calculation. These calculation will also contain some function.g.: sum(), avg() etc. Very much like Excel function: sum() will summarize all of the numbers that are provided in brackets and avg will do average.
I have started implementing some changes but I am not even sure if I started in a correct place. But if I started in correct place then what I need to do is to access the parameters so that I can iterate and summarize them.
Up until now I have created a new case in EvaluationVisitor.cs like so:
case "sum":
CheckCase("sum", function.Identifier.Name);
if (function.Expressions.Length != 3) {
throw new ArgumentException("sum() takes exactly 3 arguments");
}
string sum = "";
for (int i = 1; i < function.Expressions.Length; i++)
{
//here comes the logic for getting all parameters and summing them.
//one thing I am not sure about is how to access input parameters
sum = ...;
}
Result = sum;
break;
I am very new to both C# and nCalc, and some help would be greatly appreciated.
Thanks in advance!
Please give this a try.
Add this code to new switch case ("sum") in EvaluationVisitor.cs :
case "sum" :
CheckCase("Sum", function.Identifier.Name);
object[] inputs = new object[function.Expressions.Length];
for (var i = 0; i < function.Expressions.Length; i++)
{
inputs[i] = Evaluate(function.Expressions[i]);
}
Result = Numbers.Sum(inputs);
break;
New method in Numbers.cs file :
public static object Sum(params object[] inputs)
{
var sum = inputs[0];
for (int i = 1; i < inputs.Length; i++)
{
sum = Add(sum,inputs[i]);
}
return sum;
}
I am working in C#, winforms application.
I am reading from a text file where each row has fields divided by tabs:
I am putting each row in a list named tic_string. From here I am trying to search each list object, find the tabs, and put each field in its own array. So there will be an array for column a, column b, column c ... etc.
The problem is when I try to find the tabs in my list objects, it finds nothing. Here is my code:
string[] tic_num = new string[row_counter];
string[] tic_title = new string[row_counter];
string[] tic_owner = new string[row_counter];
string[] tic_open_date = new string[row_counter];
int last_tab = 0;
int char_counter = 0;
int feild_counter = 1;
int feild_char_count = 1;
int current_row=0;
string temp_feild = "";
char temp_char;
char tab_char = '\t';
foreach (string tic_string_value in tic_string)
{
temp_char = tic_string_value[char_counter];
if (temp_char == tab_char)
{
Console.WriteLine("tab_found");
if (feild_char_count == 1)
{
temp_feild = "";
}
else
{
temp_feild = tic_string_value.Substring(last_tab, feild_char_count);
}
last_tab = char_counter;
feild_char_count = 0;
switch (feild_counter)
{
case 1:
tic_num[current_row] = temp_feild;
break;
case 2:
tic_title[current_row] = temp_feild;
break;
case 3:
tic_owner[current_row] = temp_feild;
break;
case 4:
tic_open_date[current_row] = temp_feild;
break;
}
}
current_row++;
feild_char_count++;
char_counter++;
if (feild_counter == 5)
feild_counter = 1;
}
Your code seems to be too complicated for such simple task. Do not parse each line char by char, just use helper functions like String.Split etc.:
foreach (string tic_string_value in tic_string)
{
var parts = tic_string_value.Split(new [] { '\t' },
StringSplitOptions.RemoveEmptyEntries);
tic_num[current_row] = parts[0];
tic_title[current_row] = parts[1];
tic_owner[current_row] = parts[2];
tic_open_date[current_row] = parts[3];
current_row++;
}
First of all, I deduce from the style of your code that you are probably familiar with C/C++ and are new to C#, because this code has a particularly "C++" flavour to it. It reminds me very much of my own C# code when I first made the jump myself.
I am glad that you described the problem you are trying to solve rather than simply posting the code and asking where to find the bug because I think you can actually solve your problem much more simply.
Considering the following code (this assumes that you're iterating over each of the rows outside this code, and I omit some of the declaring of variables that you had already specified):
int field_counter = 0;
foreach (var field in tic_string.Split('\t')) {
switch (field_counter++) {
case 0:
tic_num[current_row] = field;
break;
case 1:
tic_title[current_row] = field;
break;
case 2:
tic_owner[current_row] = field;
break;
case 3:
tic_open_date[current_row] = field;
break;
}
}
This leverages the succinctness of C# and removes quite a few lines of code, which is always good. The String.Split method will take care of most of the string splitting for you, so there's no need to do it all manually and keep track of characters.
Note: I kept your original naming of some of the field names, although generally it is preferable to use CamelCase in C# code.
Now I notice from your original code that it's possible you don't have "rows" in your data in an actual sense (i.e. split by newline characters) but rather you may have the data entirely tab separated and are using the fact that you have a fixed number of columns per row to split up rows.
If this was the case, might I suggest the following code block could help you:
int i = 0;
foreach (var group in tic_string.GroupBy(x => i++ % 4)) {
int current_row = 0;
foreach (var field in group) {
switch (group.Key) {
case 0:
tic_num[current_row] = field;
break;
case 1:
tic_title[current_row] = field;
break;
case 2:
tic_owner[current_row] = field;
break;
case 3:
tic_open_date[current_row] = field;
break;
}
current_row++;
}
}
Now of course you may need to adapt these blocks to your code rather than use it verbatim. I hope that they at least demonstrate a different way of thinking about the problem. In particular, learning to use the various LINQ extension methods and LINQ queries will also be very helpful - they are part of what allows C# code to be so quick and easy to develop.
Best of luck in solving your problem!
You could also use a list instead of 4 string arrays:
public class ObjectToBeUsed
{
public Person(int num, string title, string owner, string opendate)
{
this.Num = num;
this.Title = title;
this.Owner = owner;
this.OpenDate = opendate;
}
private int _num;
public int Num
{
get { return _num; }
set { _num = value; }
}
private string _title;
public string Title
{
get { return _title; }
set { _title = value; }
}
private string _owner;
public string Owner
{
get { return _owner; }
set { _owner = value; }
}
private string _opendate;
public string OpenDate
{
get { return _opendate; }
set { _opendate = value; }
}
}
This is the class which describes each row in your text file.
System.IO.StreamReader file = new System.IO.StreamReader("test.txt");
string currentLine = null;
List<ObjectToBeUsed> peopleList = new List<ObjectToBeUsed>();
while ((currentLine = file.ReadLine()) != null)
{
string[] tokens = Regex.Split(currentLine, #"\t");
peopleList.Add(new ObjectToBeUsed(Convert.ToInt32(tokens[0]), tokens[1], tokens[2], tokens[3]));
}
The code is pretty self-explanatory, but if you need any further explaining, go ahead.
Basically comparing a string that is entered, and trying to get that position from the array.
If I initialize position to 0 then it returns the position zero of the array, if I initialize to 1 then it gives me the item in slot 1, so it's skipping the compare statement.
I also tried using (custStatus == cardStatus[i])
public static int discount(string []cardStatus, int []pDiscount, string custStatus)
{
int position= 0;
int discount;
for(int i = 0; i < 2; i++)
{
if (string.Equals(custStatus, cardStatus[i]))
position = i;
}
discount = pDiscount[position];
return discount;
}
With your code, there's no way to tell if position = 0 means custStatus was found in your cardStatus array or if no match was made at all and the default value is being used. I'd recommend either using a boolean matchFound variable or setting position = -1 and adding an extra if statement at the end either way. Either:
boolean matchFound = false;
...
if(matchFound)
{
discount = pDiscount[position];
}
or else
int position = -1;
...
if(position >= 0)
{
discount = pDiscount[position];
}
Give this a try:
public static int discount(string[] cardStatus, int[] pDiscount, string custStatus) {
var position = Array.IndexOf(cardStatus, custStatus);
return (position == -1) ? -1 : pDiscount[position];
}
public static int discount(string []cardStatus, int []pDiscount, string custStatus)
{
for(int i = 0; i < Math.Min(cardStatus.Length, pDiscount.Length); i++)
{
if (string.Equals(custStatus, cardStatus[i]))
{
return pDiscount[i];
}
}
return -1;
}
Don't be afraid to return directly from FOR-loop, it is old-school that teaches to have only one return point from method. You can have as many returns as it helps you to keep your code clean and easy to read.
And perhaps it would be better to use the following expression in for-loop as it will guard you from possible different lengths of arrays:
for (int i = 0; i < Math.Min(cardStatus.Length, pDiscount.Length; i++)
This looks ok, even though this is somewhat more straightforward:
for(int i = 0; i < cardStatus.Length; i++)
{
if (custStatus == cardStatus[i])
{
position = i;
break;
}
}
Given your question it appears to be the case that all cardStatus[i] match custStatus - did you check the input?
Also given your code what happens if there is no match? Currently you would return pDiscount[0] - that doesn't seem to be correct.
This question already has answers here:
How can I get every nth item from a List<T>?
(10 answers)
Closed 2 years ago.
I've got a list of 'double' values. I need to select every 6th record. It's a list of coordinates, where I need to get the minimum and maximum value of every 6th value.
List of coordinates (sample): [2.1, 4.3, 1.0, 7.1, 10.6, 39.23, 0.5, ... ]
with hundrets of coordinates.
Result should look like: [x_min, y_min, z_min, x_max, y_max, z_max]
with exactly 6 coordinates.
Following code works, but it takes to long to iterate over all coordinates. I'd like to use Linq instead (maybe faster?)
for (int i = 0; i < 6; i++)
{
List<double> coordinateRange = new List<double>();
for (int j = i; j < allCoordinates.Count(); j = j + 6)
coordinateRange.Add(allCoordinates[j]);
if (i < 3) boundingBox.Add(coordinateRange.Min());
else boundingBox.Add(coordinateRange.Max());
}
Any suggestions?
Many thanks! Greets!
coordinateRange.Where( ( coordinate, index ) => (index + 1) % 6 == 0 );
The answer from Webleeuw was posted prior to this one, but IMHO it's clearer to use the index as an argument instead of using the IndexOf method.
Something like this could help:
public static IEnumerable<T> Every<T>(this IEnumerable<T> source, int count)
{
int cnt = 0;
foreach(T item in source)
{
cnt++;
if (cnt == count)
{
cnt = 0;
yield return item;
}
}
}
You can use it like this:
int[] list = new []{1,2,3,4,5,6,7,8,9,10,11,12,13};
foreach(int i in list.Every(3))
{ Console.WriteLine(i); }
EDIT:
If you want to skip the first few entries, you can use the Skip() extension method:
foreach (int i in list.Skip(2).Every(6))
{ Console.WriteLine(i); }
There is an overload of the Where method with lets you use the index directly:
coordinateRange.Where((c,i) => (i + 1) % 6 == 0);
Any particular reason you want to use LINQ to do this?
Why not write a loop that steps with 6 increments each time and get access the value directly?
To find a faster solution start a profile!
Measure how long it takes for every step in your for loop and try to avoid the biggest bottleneck.
After making a second look at your code, it seems, your problem is that you run six times over your big list. So the time needed is always six times of the list size.
Instead you should run once over the whole list and put every item into the correct slot.
Just to make a performance test for yourself you should test these two approaches:
Sample class to hold data
public class Coordinates
{
public double x1 { get; set; }
public double x2 { get; set; }
public double y1 { get; set; }
public double y2 { get; set; }
public double z1 { get; set; }
public double z2 { get; set; }
}
Initializing of the value holder
Coordinates minCoordinates = new Coordinates();
//Cause we want to hold the minimum value, it will be initialized with
//value that is definitely greater or equal than the greatest in the list
minCoordinates.x1 = Double.MaxValue;
minCoordinates.x2 = Double.MaxValue;
minCoordinates.y1 = Double.MaxValue;
minCoordinates.y2 = Double.MaxValue;
minCoordinates.z1 = Double.MaxValue;
minCoordinates.z2 = Double.MaxValue;
Using a for loop if the index operator is O(1)
for (int i = 0; i < allCoordinates.Count; i++)
{
switch (i % 6)
{
case 0:
minCoordinates.x1 = Math.Min(minCoordinates.x1, allCoordinates[i]);
break;
case 1:
minCoordinates.x2 = Math.Min(minCoordinates.x2, allCoordinates[i]);
break;
case 2:
minCoordinates.y1 = Math.Min(minCoordinates.y1, allCoordinates[i]);
break;
case 3:
minCoordinates.y2 = Math.Min(minCoordinates.y2, allCoordinates[i]);
break;
case 4:
minCoordinates.z1 = Math.Min(minCoordinates.z1, allCoordinates[i]);
break;
case 5:
minCoordinates.z2 = Math.Min(minCoordinates.z2, allCoordinates[i]);
break;
}
}
Using foreach if IEnumerator is O(1)
int count = 0;
foreach (var item in allCoordinates)
{
switch (count % 6)
{
case 0:
minCoordinates.x1 = Math.Min(minCoordinates.x1, item);
break;
case 1:
minCoordinates.x2 = Math.Min(minCoordinates.x2, item);
break;
case 2:
minCoordinates.y1 = Math.Min(minCoordinates.y1, item);
break;
case 3:
minCoordinates.y2 = Math.Min(minCoordinates.y2, item);
break;
case 4:
minCoordinates.z1 = Math.Min(minCoordinates.z1, item);
break;
case 5:
minCoordinates.z2 = Math.Min(minCoordinates.z2, item);
break;
}
count++;
}
Well its not LINQ, but If you are worrying about performance, this might help.
public static class ListExtensions
{
public static IEnumerable<T> Every<T>(this IList<T> list, int stepSize, int startIndex)
{
if (stepSize <= 0)
throw new ArgumentException();
for (int i = startIndex; i < list.Count; i += stepSize)
yield return list[i];
}
}
Suggestion:
coordinateRange.Where(c => (coordinateRange.IndexOf(c) + 1) % 6 == 0);
I stand corrected, thanks for the comments.
As stated by codymanix, the correct answer is indeed:
coordinateRange.Where((c, i) => (i + 1) % 6 == 0);
The best way to do this would be refactoring the datastructure so that every dimension would be its own array. That way x1_max would be just x1.Max(). If you cannot change the input data structure, the next best way is to iterate over the array once and do all accesses locally. This helps with staying within the L1-cached memory:
var minValues = new double[] { Double.MaxValue, Double.MaxValue, Double.MaxValue };
var maxValues = new double[] { Double.MinValue, Double.MinValue, Double.MinValue };
for (int i = 0; i < allCoordinates.Length; i += 6)
{
for (int j = 0; i < 3; i++)
{
if (allCoordinates[i+j] < minValues[j])
minValues[j] = allCoordinates[i+j];
if (allCoordinates[i+j+3] > maxValues[j])
maxValues[j] = allCoordinates[i+j+3];
}
}
Use Skip and combintaion with Take.
coordinateRange.Skip(6).Take(1).First();
I recommend you move this to a extension method.