I have an unordered list that can look something like this:
1
2.2
1.1.1
3
When i sort the list, 1.1.1 becomes greater than 3 and 2.2, and 2.2 becomes greater than 3.
This is because Double.Parse removes the dots and makes it a whole number.
This is the method i use to sort with:
public class CompareCategory: IComparer<Category>
{
public int Compare(Category c1, Category c2)
{
Double cat1 = Double.Parse(c1.prefix);
Double cat2 = Double.Parse(c2.prefix);
if (cat1 > cat2)
return 1;
else if (cat1 < cat2)
return -1;
else
return 0;
}
}
How can i fix this?
Thanks
Are these version #s by chance? Can you use the Version class? It sorts each part as you seem to want, although it only works up to 4 parts. I would not recommend parsing into a numeric value like you are doing.
It has an IComparable interface. Assuming your inputs are strings, here's a sample:
public class CompareCategory: IComparer<Category>
{
public int Compare(Category c1, Category c2)
{
var cat1 = new Version(c1.prefix);
var cat2 = new Version(c2.prefix);
if (cat1 > cat2)
return 1;
else if (cat1 < cat2)
return -1;
else
return 0;
}
}
If you need something with more than 4 "parts", I think I would create a comparer which split the strings at the dots, and then parse each element as an integer and compare them numerically. Make sure to consider cases like 1.002.3 and 1.3.3 (what do you want the sort order to be?).
Update, here is a sample of what I mean. Lightly tested:
public class CategoryComparer : Comparer<Category>
{
public override int Compare(Category x, Category y)
{
var xParts = x.prefix.Split(new[] { '.' });
var yParts = y.prefix.Split(new[] { '.' });
int index = 0;
while (true)
{
bool xHasValue = xParts.Length > index;
bool yHasValue = yParts.Length > index;
if (xHasValue && !yHasValue)
return 1; // x bigger
if (!xHasValue && yHasValue)
return -1; // y bigger
if (!xHasValue && !yHasValue)
return 0; // no more values -- same
var xValue = decimal.Parse("." + xParts[index]);
var yValue = decimal.Parse("." + yParts[index]);
if (xValue > yValue)
return 1; // x bigger
if (xValue < yValue)
return -1; // y bigger
index++;
}
}
}
public static void Main()
{
var categories = new List<Category>()
{
new Category { prefix = "1" },
new Category { prefix = "2.2" },
new Category { prefix = "1.1.1" },
new Category { prefix = "1.1.1" },
new Category { prefix = "1.001.1" },
new Category { prefix = "3" },
};
categories.Sort(new CategoryComparer());
foreach (var category in categories)
Console.WriteLine(category.prefix);
}
Output:
1
1.001.1
1.1.1
1.1.1
2.2
3
public class CodeComparer : IComparer<string>
{
public int Compare(string x, string y)
{
var xParts = x.Split(new char[] { '.' });
var yParts = y.Split(new char[] { '.' });
var partsLength = Math.Max(xParts.Length, yParts.Length);
if (partsLength > 0)
{
for (var i = 0; i < partsLength; i++)
{
if (xParts.Length <= i) return -1;// 4.2 < 4.2.x
if (yParts.Length <= i) return 1;
var xPart = xParts[i];
var yPart = yParts[i];
if (string.IsNullOrEmpty(xPart)) xPart = "0";// 5..2->5.0.2
if (string.IsNullOrEmpty(yPart)) yPart = "0";
if (!int.TryParse(xPart, out var xInt) || !int.TryParse(yPart, out var yInt))
{
// 3.a.45 compare part as string
var abcCompare = xPart.CompareTo(yPart);
if (abcCompare != 0)
return abcCompare;
continue;
}
if (xInt != yInt) return xInt < yInt ? -1 : 1;
}
return 0;
}
// compare as string
return x.CompareTo(y);
}
}
Maybe you could just string compare it?
I'm surprised that Double.Parse doesn't throw an exception with those numbers with more than one decimal place.
You really need to write some rules about how to compare these strings.
I would split the strings using String.Split() on the dot character, then iterate through the two lists created and as soon as one of the levels contained a lower or higher number than the other, or if you ran out of items in one of the lists then you wold return 1 or -1 as appropriate. If you get to the end of both lists in the same iteration of the loop then they are the same and return 0.
I would write the code but I don't have VS in front of me.
Related
I need to compare section numbers in a document, at first I was just going to convert to decimal and check to see if one number is greater than another.
The issue there is that some sections have multiple decimals.
Example: I need to perform a math comparison on 1.1 with 1.1.2.3 to determine which one is farther along in a document.
They are strings to begin with and I need to do some math comparisons on them essentially.
I though about removing the decimals then converting to int but this throws off certain sections, like section 2 would be considered less than section 1.1 since 1.1 would be changed to a 11, which is no good.
string section1 = "1.1";
string section2 = "2";
int convertedSection1 = Convert.ToInt32(section1.Replace(".",""));
int convertedSection2 = Convert.ToInt32(section2.Replace(".",""));
if(convertedSection1 < convertedSection2)
//This will incorrectly say 1.1 is greater than 2
string section1 = "1.1.2.4";
string section2 = "2.4";
decimal convertedSection1 = Convert.ToDecimal(section1);
decimal convertedSection2 = Convert.ToDecimal(section2);
if(convertedSection1 < convertedSection2)
//This will convert 1.1.2.4 to 1.1 which is no good
You can create a class similar to the Version class of the .NET framework. If you implement some operators and IComparable, it's really nice.
How does it work? It will convert the given string into a list of integer numbers. Upon comparison, it will start at the beginning of each list and compare each individual part.
public class Section: IComparable<Section>
{
// Stores all individual components of the section
private List<int> parts = new List<int>();
// Construct a section from a string
public Section(string section)
{
var strings = section.Split('.');
foreach (var s in strings)
{
parts.Add(int.Parse(s));
}
}
// Make it nice for display
public override string ToString()
{
return string.Join(".", parts);
}
// Implement comparison operators for convenience
public static bool operator ==(Section a, Section b)
{
// Comparing the identical object
if (ReferenceEquals(a, b)) return true;
// One object is null and the other isn't
if ((object)a == null) return false;
if ((object)b == null) return false;
// Different amount of items
if (a.parts.Count != b.parts.Count) return false;
// Check all individual items
for (int i=0; i<a.parts.Count;i++)
{
if (a.parts[i] != b.parts[i]) return false;
}
return true;
}
public static bool operator !=(Section a, Section b)
{
return !(a == b);
}
public static bool operator <(Section a, Section b)
{
// Use minimum, otherwise we exceed the index
for (int i=0; i< Math.Min(a.parts.Count, b.parts.Count); i++)
{
if (a.parts[i] < b.parts[i]) return true;
}
if (b.parts.Count > a.parts.Count) return true;
return false;
}
public static bool operator >(Section a, Section b)
{
// Use minimum, otherwise we exceed the index
for (int i = 0; i < Math.Min(a.parts.Count, b.parts.Count); i++)
{
if (a.parts[i] > b.parts[i]) return true;
}
if (a.parts.Count > b.parts.Count) return true;
return false;
}
// Implement the IComparable interface for sorting
public int CompareTo(Section other)
{
if (this == other) return 0;
if (this < other) return -1;
return 1;
}
}
Tests for 96% coverage:
Assert.IsTrue(new Section("1.2.3.4") > new Section("1.2.3"));
Assert.IsFalse(new Section("1.2.3.4") < new Section("1.2.3"));
Assert.IsFalse(new Section("1.2.3.4") == new Section("1.2.3"));
Assert.IsTrue(new Section("1.2.3.4") == new Section("1.2.3.4"));
Assert.IsFalse(new Section("1.2.3.4") == new Section("1.2.3.5"));
Assert.IsTrue(new Section("1.2.3.4") != new Section("1.2.3.5"));
var sec = new Section("1");
Assert.IsTrue(sec == sec);
Assert.AreEqual("1.2.3.4", new Section("1.2.3.4").ToString());
var sortTest = new List<Section> { new Section("2"), new Section("1.2"), new Section("1"), new Section("3.1") };
sortTest.Sort();
var expected = new List<Section> { new Section("1"), new Section("1.2"), new Section("2"), new Section("3.1") };
CollectionAssert.AreEqual(expected, sortTest, new SectionComparer());
If you know that your section strings are always well formed, and you know that they don't go deeper than 6 levels, and that no level has more than 999 items, then this works nicely:
string zero = ".0.0.0.0.0.0";
long Section2Long(string section) =>
(section + zero)
.Split('.')
.Take(6)
.Select(t => long.Parse(t))
.Aggregate((x, y) => x * 1000 + y);
Now, if I have this:
string[] sections = new []
{
"1.2.4", "2.3", "1", "1.2", "1.1.1.1", "1.0.0.1.0.1", "2.2.9"
};
I can easily sort it like this:
string[] sorted = sections.OrderBy(x => Section2Long(x)).ToArray();
I get this output:
1
1.0.0.1.0.1
1.1.1.1
1.2
1.2.4
2.2.9
2.3
Assuming the function takes in a list of double and an index to perform the check from, I need to check if the values alternates up and down consecutively.
For example, a list of [14.0,12.3,13.0,11.4] alternates consecutively but a list of [14.0,12.3,11.4,13.0] doesn't.
The algorithm doesn't have to be fast, but I'd like it to be compact to write (LINQ is totally fine). This is my current method, and it looks way too crude to my taste:
enum AlternatingDirection { Rise, Fall, None };
public bool CheckConsecutiveAlternation(List<double> dataList, int currDataIndex)
{
/*
* Result True : Fail
* Result False : Pass
*/
if (!_continuousRiseFallCheckBool)
return false;
if (dataList.Count < _continuousRiseFallValue)
return false;
if (currDataIndex + 1 < _continuousRiseFallValue)
return false;
AlternatingDirection direction = AlternatingDirection.None;
int startIndex = currDataIndex - _continuousRiseFallValue + 1;
double prevVal = 0;
for (int i = startIndex; i <= currDataIndex; i++)
{
if (i == startIndex)
{
prevVal = dataList[i];
continue;
}
if (prevVal > dataList[i])
{
prevVal = dataList[i];
switch (direction)
{
case AlternatingDirection.None:
direction = AlternatingDirection.Fall;
continue;
case AlternatingDirection.Rise:
direction = AlternatingDirection.Fall;
continue;
default:
//Two falls in a row. Not a signal.
return false;
}
}
if (prevVal < dataList[i])
{
prevVal = dataList[i];
switch (direction)
{
case AlternatingDirection.None:
direction = AlternatingDirection.Rise;
continue;
case AlternatingDirection.Fall:
direction = AlternatingDirection.Rise;
continue;
default:
//Two rise in a row. Not a signal.
return false;
}
}
return false;
}
//Alternated n times until here. Data is out of control.
return true;
}
Try this:
public static bool IsAlternating(double[] data)
{
var d = GetDerivative(data);
var signs = d.Select(val => Math.Sign(val));
bool isAlternating =
signs.Zip(signs.Skip(1), (a, b) => a != b).All(isAlt => isAlt);
return isAlternating;
}
private static IEnumerable<double> GetDerivative(double[] data)
{
var d = data.Zip(data.Skip(1), (a, b) => b - a);
return d;
}
Live demo
The idea is:
If the given list of values is alternating up and down, mathematically it means that it's derivative keeps changing its sign.
So this is exactly what this piece of code does:
Get the derivative.
Checks for sign fluctuations.
And the bonus is that it will not evaluate all of the derivative / signs arrays unless it is necessary.
I'd do it with a couple of consecutive zips, bundled in an extension method:
public static class AlternatingExtensions {
public static bool IsAlternating<T>(this IList<T> list) where T : IComparable<T>
{
var diffSigns = list.Zip(list.Skip(1), (a,b) => b.CompareTo(a));
var signChanges = diffSigns.Zip(diffSigns.Skip(1), (a,b) => a * b < 0);
return signChanges.All(s => s);
}
}
Edit: for completeness, here's how you'd use the feature:
var alternatingList = new List<double> { 14.0, 12.3, 13.0, 11.4 };
var nonAlternatingList = new List<double> { 14.0, 12.3, 11.4, 13.0 };
alternatingList.IsAlternating(); // true
nonAlternatingList.IsAlternating(); // false
I also changed the implementation to work on more types, making use of generics as much as possible.
Here is a small pseudo code. Assuming no repeated elements (can be handled easily though by few tweaks)
Idea is to have a sign variable which is alternating 1,-1,... that is multipled by the difference of two consecutive pairs, the difference multipled by this sign variable must always be positive. If it's not at some point, return false.
isUpAndDown(l):
if size(l) < 2: // empty,singleton list is always good.
return true
int sign = (l[0] < l[1] ? 1 : -1)
for i from 0 to n-1 (exclusive):
if sign * (l[i+1] - l[i]) < 0:
return false //not alternating
sign = sign * -1
end for
return true //all good
You may create kind of a signed array first:
double previous = 0;
var sign = myList.Select(x => {
int s = Math.Sign(x - previous);
previos = x;
return s;
});
This gives you a list similar to
{ -1, 1, -1, ... }
Now you can take a similar appraoch as the previos Select-statement to check if a -1 follows a 1:
var result = sign.All(x => {
bool b = x == -previous;
previous = x;
return b;
});
Now result is true if your list alternates, false otherwise.
EDIT: To ensure that the very first check within the second query also passes add previous = -sign[0]; before the second query.
Assuming that two equal values in a row are not acceptable (if they are, just skip over equal values):
if (dataList[0] == dataList[1])
return false;
bool nextMustRise = dataList[0] > dataList[1];
for (int i = 2; i < dataList.Count; i++) {
if (dataList[i - 1] == dataList[i] || (dataList[i - 1] < dataList[i]) != nextMustRise)
return false;
nextMustRise = !nextMustRise;
}
return true;
public double RatioOfAlternations(double[] dataList)
{
double Alternating = 0;
double Total = (dataList.Count()-2);
for (int (i) = 0; (i) < Total; (i)++)
{
if (((dataList[i+1]-dataList[i])*(dataList[i+2]-dataList[i+1]))<0)
// If previous change is opposite sign to current change, this will be negative
{
Alternating++;
}
else
{
}
}
return (Alternating/Total);
}
This question already has answers here:
Natural Sort Order in C#
(18 answers)
Closed 7 years ago.
I am trying to order mixed strings in a unique way and am wondering if anyone else has done this. I have found several articles on using IOrderConparer but can not find a solution for my particular sorting problem.
I have the following:
1017, 650, 650C, 650B, W323, 10, 20, 1000, W1000
I need to order them as follows:
10, 20, 650, 650B, 650C, 1000, 1017, W323, W1000
Any help would be appreciated. Thanks.
Implement the compare using StrCmpLogicalW:
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern int StrCmpLogicalW(string x, string y);
This will do a logical (taking numbers into account) string compare rather then a standard string based one which will give you the result you are after.
A bit longer but all managed code
public class LogicalSorter : IComparer
{
public int Compare(object a, object b)
{
var first = Regex.Split((string)a,"([0-9]+)").Where(s => s != "").ToArray();
var second = Regex.Split((string)b,"([0-9]+)").Where(s => s != "").ToArray();
var endIdx = Math.Min(first.Count(), second.Count());
for (var i = 0; i < endIdx; i++)
{
var part1 = first.ElementAt(i);
var part2 = second.ElementAt(i);
if (part1.All(char.IsDigit) && part2.All(char.IsDigit) && part1 != part2)
{
return int.Parse(part1).CompareTo(int.Parse(part2));
}
if (part1 != part2) return part1.CompareTo(part2);
}
return first.Count().CompareTo(second.Count());
}
}
Use it like this
string[] values = { "1017", "650", "650C", "650B", "W323", "10", "20", "1000", "W1000" };
Array.Sort(values, new LogicalSorter());
foreach (var value in values)
Console.WriteLine(value);
Or using generics as suggested by Mrinal (preferred)
public class LogicalSorter : IComparer<String>
{
public int Compare(String a, String b)
{
var first = Regex.Split(a, "([0-9]+)").Where(s => s != "").ToArray();
var second = Regex.Split(b, "([0-9]+)").Where(s => s != "").ToArray();
var endIdx = Math.Min(first.Count(), second.Count());
for (var i = 0; i < endIdx; i++)
{
var part1 = first.ElementAt(i);
var part2 = second.ElementAt(i);
if (part1.All(char.IsDigit) && part2.All(char.IsDigit) && part1 != part2)
{
return int.Parse(part1).CompareTo(int.Parse(part2));
}
if (part1 != part2) return part1.CompareTo(part2);
}
return first.Count().CompareTo(second.Count());
}
}
Example of optimized managed code (for speed not for looks), performs at 47x the regex version
public class LogicalSorter : IComparer<String>
{
public int Compare(String a, String b)
{
var aLength = a.Length;
var bLength = b.Length;
var aIdx = 0;
var bIdx = 0;
int aPartLen;
int bPartLen;
int aPartEndIndex;
int bPartEndIndex;
bool aIsString;
bool bIsString;
// Examine both strings on character level, keep track of where
// we are in each string since lengths might differ
while (aIdx < aLength && bIdx < bLength)
{
// If both strings contain digit at current index
// compare numbers
if (char.IsDigit(a[aIdx]) && char.IsDigit(b[bIdx]))
{
// Get longest consecutive list of digits from each string
aPartEndIndex = aIdx;
while (aPartEndIndex < aLength && char.IsDigit(a[aPartEndIndex])) { aPartEndIndex++; }
bPartEndIndex = bIdx;
while (bPartEndIndex < bLength && char.IsDigit(b[bPartEndIndex])) { bPartEndIndex++; }
aPartLen = aPartEndIndex - aIdx;
bPartLen = bPartEndIndex - bIdx;
// Compare lengths (longest number is greater)
if (aPartLen != bPartLen) return aPartLen < bPartLen ? -1 : 1;
// Same length numbers, compare chars until not same or end
while (aIdx < aPartEndIndex && a[aIdx] == b[bIdx])
{
aIdx++;
bIdx++;
}
// If not at end compare last characters that were not same
if(aIdx != aPartEndIndex)
return a[aIdx] < b[bIdx] ? -1 : 1;
}
else
{
// Comparing string vs number or string vs string
aIsString = char.IsLetter(a[aIdx]);
bIsString = char.IsLetter(b[bIdx]);
// if not 2 strings, number is always first
if (aIsString != bIsString) return aIsString ? 1 : -1;
// Get longest consecutive list of letters from each string
aPartEndIndex = aIdx;
while (aPartEndIndex < aLength && (char.IsLetter(a[aPartEndIndex]) == aIsString))
{
aPartEndIndex++;
}
bPartEndIndex = bIdx;
while (bPartEndIndex < bLength && (char.IsLetter(b[bPartEndIndex]) == bIsString))
{
bPartEndIndex++;
}
// Compare chars until not same or end
while (aIdx < aPartEndIndex && bIdx < bPartEndIndex && a[aIdx] == b[bIdx])
{
aIdx++;
bIdx++;
}
// if not at end compare last letters found
if ((aIdx != aPartEndIndex) || (bIdx != bPartEndIndex))
return a[aIdx] < b[bIdx] ? -1 : 1;
}
}
// Use length as tie breaker
return aLength < bLength ? -1 : 1;
}
}
I'm currently in the process of writing a class that can represent an infinitely large number (in theory). The constructor of this class creates the object from a string value, which is why the number could be of an extremely large, yet unknown, size.
The reason I started writing this class was because I wanted to be able to make a program that would be able to perform mathematical calculations with numbers of arbitrarily large size. Thus, I started writing a class that could handle values well over the standard ranges of integers, floats, doubles, (hopefully) decimals, etc.
Here are the declarations and the main constructor for the class:
/// <summary>
/// Creates a new instance of the LargeDecimal class, which represents either a whole or decimal number.
/// </summary>
/// <param name="number">The string representation of the number.</param>
public LargeDecimal(string value)
{
string number = value.Replace(" ", "");
if (number.Contains("-") && (number.IndexOf('-') == 0)) {
number = number.Replace("-", "");
IsNegative = true;
}
// Determining whether the number is whole or contains a decimal.
if (number.IndexOf('.') == -1) {
// Does not contain a decimal.
for (int i = 0; i < number.Length; i++)
wholeDigits.Add(int.Parse(number[i].ToString()));
IsWhole = true;
}
else {
// Still check if number is whole. Add all decimal digits.
string[] numArray = number.Split('.');
int sumOfDecimalDigits = 0;
for (int i = 0; i < numArray[1].ToString().Length; i++)
sumOfDecimalDigits += int.Parse(numArray[1].ToString()[i].ToString());
if (sumOfDecimalDigits <= 0) {
// Is a whole number.
for (int i = 0; i < numArray[0].ToString().Length; i++)
wholeDigits.Add(int.Parse(numArray[0].ToString()[i].ToString()));
IsWhole = true;
}
else {
// Is not a whole number.
for (int i = 0; i < numArray[0].ToString().Length; i++)
wholeDigits.Add(int.Parse(numArray[0].ToString()[i].ToString()));
for (int i = 0; i < numArray[1].ToString().Length; i++)
decimalDigits.Add(int.Parse(numArray[1].ToString()[i].ToString()));
IsWhole = false;
}
}
}
The class is basically a representation of a number through two lists of type int, where one list represents the digits that make up the whole partition of the number, and the other list represents the digits that make up the decimal partition of the number (if applicable).
I have written an Add method which accepts two LargeDecimal objects, adds their values together, and returns a new LargeDecimal object with the sum as its value. Though incomplete, it does work with LargeDecimal objects that are whole numbers only, and are both positive or both negative (picture!).
I have realized that adding methods to compare two values (greater than / less than / equal to) would be extremely useful in calculations. However, I am not sure how to check whether the value of a LargeDecimal object is greater or less than the value of another LargeDecimal.
There are cases where I can just compare the amount of items in the wholeDigits list, but that is only when the amounts of items are different for both values.
I am unsure about how to compare two numbers such as: 15498765423654973246 and 15499111137583924246.
And I think it will get more difficult if I will try and compare two fractional numbers: 8573819351.86931 and 8573809999.85999
I do not wish to use integer calculations in conjunction with place values (e.g. in the number 831, the value of the number 8 would be 8 * 100, the value of 3 would be 3 * 10, and the value of 1 would be 1 * 1), because I would like this class to be able to represent values of any given size and length and range (while an int cannot handle values up to 2147483647).
Any help regarding this would be highly appreciated! Thank you all!
I would start by implementing IComparable:
public class LargeDecimal : IComparable<LargeDecimal>
And the implementation would look like:
public int CompareTo(LargeDecimal other)
{
if (other == null) return 1;
if (ReferenceEquals(this, other)) return 0;
if (IsNegative != other.IsNegative)
{
if (other.IsNegative) return 1;
return -1;
}
int multiplier = (IsNegative) ? -1 : 1;
if (wholeDigits.Count > other.wholeDigits.Count) return 1 * multiplier;
if (wholeDigits.Count < other.wholeDigits.Count) return -1 * multiplier;
for (int i = 0; i < wholeDigits.Count; i++)
{
if (wholeDigits[i] > other.wholeDigits[i]) return 1 * multiplier;
if (wholeDigits[i] < other.wholeDigits[i]) return -1 * multiplier;
}
for (int i = 0; i < Math.Min(decimalDigits.Count, other.decimalDigits.Count); i++)
{
if (decimalDigits[i] > other.decimalDigits[i]) return 1 * multiplier;
if (decimalDigits[i] < other.decimalDigits[i]) return -1 * multiplier;
}
if (decimalDigits.Count > other.decimalDigits.Count) return 1 * multiplier;
if (decimalDigits.Count < other.decimalDigits.Count) return -1 * multiplier;
return 0;
}
Update
This project was sitting on my brain at dinner tonight, so I went at it some more for fun. Not sure if this is helpful, but figured I'd share what I came up with.
First, I added fields to make the class actually work:
public bool IsNegative { get; private set; }
public bool IsWhole { get; private set; }
private List<int> wholeDigits;
private List<int> decimalDigits;
Second, I overrode the ToString method so the numbers display nicely:
public override string ToString()
{
return string.Format("{0}{1}{2}{3}",
(IsNegative) ? "-" : "",
string.Join("", wholeDigits),
(IsWhole) ? "" : ".",
(IsWhole) ? "" : string.Join("", decimalDigits));
}
Then I implemented the Equals methods so they work as expected for a number type:
public static bool Equals(LargeDecimal first, LargeDecimal second)
{
return ReferenceEquals(first, null)
? ReferenceEquals(second, null)
: first.Equals(second);
}
public override bool Equals(object obj)
{
return Equals(obj as LargeDecimal);
}
protected bool Equals(LargeDecimal other)
{
return CompareTo(other) == 0;
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (wholeDigits != null)
? wholeDigits.GetHashCode()
: 0;
hashCode = (hashCode * 397) ^
(decimalDigits != null ? decimalDigits.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ IsNegative.GetHashCode();
hashCode = (hashCode * 397) ^ IsWhole.GetHashCode();
return hashCode;
}
}
Next, I added some utility methods to help out with some upcoming tasks:
private void ResetToZero()
{
wholeDigits = new List<int> { 0 };
decimalDigits = new List<int> { 0 };
IsWhole = true;
IsNegative = false;
}
private void NormalizeLists()
{
RemoveLeadingZeroes(wholeDigits);
RemoveTrailingZeroes(decimalDigits);
IsWhole = (decimalDigits.Count == 0
|| (decimalDigits.Count == 1 && decimalDigits[0] == 0));
}
private void AddLeadingZeroes(List<int> list, int numberOfZeroes)
{
if (list == null) return;
for (int i = 0; i < numberOfZeroes; i++)
{
list.Insert(0, 0);
}
}
private void AddTrailingZeroes(List<int> list, int numberOfZeroes)
{
if (list == null) return;
for (int i = 0; i < numberOfZeroes; i++)
{
list.Add(0);
}
}
private void RemoveLeadingZeroes(List<int> list, bool leaveOneIfEmpty = true)
{
if (list == null) return;
var temp = list;
for (int i = 0; i < temp.Count; i++)
{
if (temp[i] == 0)
{
list.RemoveAt(i);
}
else
{
break;
}
}
if (leaveOneIfEmpty && !list.Any()) list.Add(0);
}
private void RemoveTrailingZeroes(List<int> list, bool leaveOneIfEmpty = true)
{
if (list == null) return;
var temp = list;
for (int i = temp.Count -1; i >= 0; i--)
{
if (temp[i] == 0)
{
list.RemoveAt(i);
}
else
{
break;
}
}
if (leaveOneIfEmpty && !list.Any()) list.Add(0);
}
Next, I added some constructors. A default that sets the number to '0', one that parses a string, and another that copies the values from another LargeDecimal:
public LargeDecimal() : this("0") { }
public LargeDecimal(string value)
{
if (value == null) throw new ArgumentNullException("value");
string number = value.Replace(" ", ""); // remove spaces
number = number.TrimStart('0'); // remove leading zeroes
IsNegative = (number.IndexOf('-') == 0); // check for negative
number = number.Replace("-", ""); // remove dashes
// add a zero if there were no numbers before a decimal point
if (number.IndexOf('.') == 0) number = "0" + number;
// Initialize lists
wholeDigits = new List<int>();
decimalDigits = new List<int>();
// Get whole and decimal parts of the number
var numberParts = number.Split(new[] {'.'},
StringSplitOptions.RemoveEmptyEntries);
IsWhole = numberParts.Length == 1;
// Add whole digits to the list
wholeDigits.AddRange(numberParts[0].Select(n => int.Parse(n.ToString())));
// Add decimal digits to the list (if there are any)
if (numberParts.Length > 1 &&
numberParts[1].Sum(n => int.Parse(n.ToString())) > 0)
{
numberParts[1] = numberParts[1].TrimEnd('0');
decimalDigits.AddRange(numberParts[1].Select(n => int.Parse(n.ToString())));
}
NormalizeLists();
}
public LargeDecimal(LargeDecimal initializeFrom)
{
wholeDigits = initializeFrom.wholeDigits
.GetRange(0, initializeFrom.wholeDigits.Count);
decimalDigits = initializeFrom.decimalDigits
.GetRange(0, initializeFrom.decimalDigits.Count);
IsWhole = initializeFrom.IsWhole;
IsNegative = initializeFrom.IsNegative;
NormalizeLists();
}
Then I implemented the Add and Subtract methods
public void Add(LargeDecimal other)
{
if (other == null) return;
if (IsNegative != other.IsNegative)
{
// Get the absolue values of the two operands
var absThis = new LargeDecimal(this) {IsNegative = false};
var absOther = new LargeDecimal(other) {IsNegative = false};
// If the signs are different and the values are the same, reset to 0.
if (absThis == absOther)
{
ResetToZero();
return;
}
// Since the signs are different, we will retain the sign of the larger number
IsNegative = absThis < absOther ? other.IsNegative : IsNegative;
// Assign the difference of the two absolute values
absThis.Subtract(absOther);
wholeDigits = absThis.wholeDigits.GetRange(0, absThis.wholeDigits.Count);
decimalDigits = absThis.decimalDigits.GetRange(0, absThis.decimalDigits.Count);
NormalizeLists();
return;
}
// start with the larger decimal digits list
var newDecimalDigits = new List<int>();
newDecimalDigits = decimalDigits.Count > other.decimalDigits.Count
? decimalDigits.GetRange(0, decimalDigits.Count)
: other.decimalDigits.GetRange(0, other.decimalDigits.Count);
// and add the smaller one to it
int carry = 0; // Represents the value of the 'tens' digit to carry over
for (int i = Math.Min(decimalDigits.Count, other.decimalDigits.Count) - 1; i >= 0; i--)
{
var result = decimalDigits[i] + other.decimalDigits[i] + carry;
carry = Convert.ToInt32(Math.Floor((decimal) result / 10));
result = result % 10;
newDecimalDigits[i] = result;
}
var newWholeDigits = new List<int>();
newWholeDigits = wholeDigits.Count > other.wholeDigits.Count
? wholeDigits.GetRange(0, wholeDigits.Count)
: other.wholeDigits.GetRange(0, other.wholeDigits.Count);
for (int i = Math.Min(wholeDigits.Count, other.wholeDigits.Count) - 1; i >= 0; i--)
{
var result = wholeDigits[i] + other.wholeDigits[i] + carry;
carry = Convert.ToInt32(Math.Floor((decimal)result / 10));
result = result % 10;
newWholeDigits[i] = result;
}
if (carry > 0) newWholeDigits.Insert(0, carry);
wholeDigits = newWholeDigits.GetRange(0, newWholeDigits.Count);
decimalDigits = newDecimalDigits.GetRange(0, newDecimalDigits.Count);
NormalizeLists();
}
public void Subtract(LargeDecimal other)
{
if (other == null) return;
// If the other value is the same as this one, then the difference is zero
if (Equals(other))
{
ResetToZero();
return;
}
// Absolute values will be used to determine how we subtract
var absThis = new LargeDecimal(this) {IsNegative = false};
var absOther = new LargeDecimal(other) {IsNegative = false};
// If the signs are different, then the difference will be the sum
if (IsNegative != other.IsNegative)
{
absThis.Add(absOther);
wholeDigits = absThis.wholeDigits.GetRange(0, absThis.wholeDigits.Count);
decimalDigits = absThis.decimalDigits.GetRange(0, absThis.decimalDigits.Count);
NormalizeLists();
return;
}
// Subtract smallNumber from bigNumber to get the difference
LargeDecimal bigNumber;
LargeDecimal smallNumber;
if (absThis < absOther)
{
bigNumber = new LargeDecimal(absOther);
smallNumber = new LargeDecimal(absThis);
}
else
{
bigNumber = new LargeDecimal(absThis);
smallNumber = new LargeDecimal(absOther);
}
// Pad the whole number and decimal number lists where necessary so that both
// LargeDecimal objects have the same count of whole and decimal numbers.
AddTrailingZeroes(
bigNumber.decimalDigits.Count < smallNumber.decimalDigits.Count
? bigNumber.decimalDigits
: smallNumber.decimalDigits,
Math.Abs(bigNumber.decimalDigits.Count - smallNumber.decimalDigits.Count));
AddLeadingZeroes(smallNumber.wholeDigits,
Math.Abs(bigNumber.wholeDigits.Count - smallNumber.wholeDigits.Count));
var newWholeDigits = new List<int>();
var newDecimalDigits = new List<int>();
bool borrowed = false; // True if we borrowed 1 from next number
for (int i = bigNumber.decimalDigits.Count - 1; i >= 0; i--)
{
if (borrowed)
{
bigNumber.decimalDigits[i] -= 1; // We borrowed one from this number last time
borrowed = false;
}
if (bigNumber.decimalDigits[i] < smallNumber.decimalDigits[i])
{
bigNumber.decimalDigits[i] += 10; // Borrow from next number and add to this one
borrowed = true;
}
// Since we're working from the back of the list, always add to the front
newDecimalDigits.Insert(0, bigNumber.decimalDigits[i] - smallNumber.decimalDigits[i]);
}
for (int i = bigNumber.wholeDigits.Count - 1; i >= 0; i--)
{
if (borrowed)
{
bigNumber.wholeDigits[i] -= 1;
borrowed = false;
}
if (bigNumber.wholeDigits[i] < smallNumber.wholeDigits[i])
{
bigNumber.wholeDigits[i] += 10;
borrowed = true;
}
newWholeDigits.Insert(0, bigNumber.wholeDigits[i] - smallNumber.wholeDigits[i]);
}
if (absThis < absOther) IsNegative = !IsNegative;
wholeDigits = newWholeDigits.GetRange(0, newWholeDigits.Count);
decimalDigits = newDecimalDigits.GetRange(0, newDecimalDigits.Count);
NormalizeLists();
}
And finally overrode the numeric operators:
public static LargeDecimal operator +(LargeDecimal first, LargeDecimal second)
{
if (first == null) return second;
if (second == null) return first;
var result = new LargeDecimal(first);
result.Add(second);
return result;
}
public static LargeDecimal operator -(LargeDecimal first, LargeDecimal second)
{
if (first == null) return second;
if (second == null) return first;
var result = new LargeDecimal(first);
result.Subtract(second);
return result;
}
public static bool operator >(LargeDecimal first, LargeDecimal second)
{
if (first == null) return false;
return first.CompareTo(second) > 0;
}
public static bool operator <(LargeDecimal first, LargeDecimal second)
{
if (second == null) return false;
return second.CompareTo(first) > 0;
}
public static bool operator >=(LargeDecimal first, LargeDecimal second)
{
if (first == null) return false;
return first.CompareTo(second) >= 0;
}
public static bool operator <=(LargeDecimal first, LargeDecimal second)
{
if (second == null) return false;
return second.CompareTo(first) >= 0;
}
public static bool operator ==(LargeDecimal first, LargeDecimal second)
{
return Equals(first, second);
}
public static bool operator !=(LargeDecimal first, LargeDecimal second)
{
return !Equals(first, second);
}
Thanks for the fun challenge!!
Assuming that this implementation looks something like this:
List<int> WholeList;
List<int> FactionalList;
bool IsNegative;
and they both grow away from the decimal point, then a comparison algorithm would go like this
First compare signs. Negative is always less than positive.
Compare lengths of WholeList, longer has larger magnitude (larger number is dependent on sign)
If WholeList.Count the same. Compare each digit starting with most significant (aka WholeList[Count-1] first), first that are different between numbers will determine larger magnitude.
If you make it into the FractionalList, and then run out of digits in one list. The number with the longer FractionalList will be the larger magnitude.
Lets say I have List<string> = new List<string>() {"20","26","32"}
I want to create a new List based on the first number in the previous list and it should have the same number of elements in it. I will be adding a certain number to that first number and so on and so on. As an example, using 6 as the number to add I would get 20,26,32. The resulting list will be List. The number 6 is a class wide property.
The issue comes if I have a list of "N","N","32"
I need to produce the same list of 20,26,32 but I have to use the last number to work out the others.
If I had "N","26","N" I would have to use the middle number to work out the others.
The N represents no data in the input list and it will always be this character
In summary, I need to produce a new list with the same number of elements as the input list and it must take the first or next numerical element to produce the resulting list using a specified number to add/subtract values to.
I wondered if LINQ's aggregate function might be able to handle it but got a bit lost using it.
Examples:
"20","26","32" = 20,26,32
"N","26","32" = 20,26,32
"N","N","32" = 20,26,32
"20","26","N" = 20,26,32
What about something like this:
var n = 6;
List<string> strList = new List<string>() {"20","26","32"};
// list can also be {null, "26", null} , {null, "N", "32"} ,
// {"N", "26", null } etc...
var list = strList.Select(s =>
{
int v;
if(string.IsNullOrEmpty(s) || !int.TryParse(s,out v))
return (int?)null;
return v;
});
var firstValidVal = list.Select((Num, Index) => new { Num, Index })
.FirstOrDefault(x => x.Num.HasValue);
if(firstValidVal == null)
throw new Exception("No valid number found");
var bases = Enumerable.Range(0, strList.Count).Select(i => i * n);
int startVal = firstValidVal.Num.Value - bases.ElementAt(firstValidVal.Index);
var completeSequence = bases.Select(x => x + startVal);
It sounds like you want a function which will
Take a List<int> as input
Make the first element of the original list the first element of the new list
New list has same number of elements as original
Remaining numbers are the first element + a value * position
If so then try the following
static bool TryGetFirstNumber(List<string> list, out number, out index) {
for (var i = 0; i < list.Count; i++) {
var cur = list[0];
if (!String.IsNullOrEmpty(cur) && Int32.TryParse(cur, out number)) {
index = i;
return true;
}
}
number = 0;
index = 0;
return false;
}
static List<T> TheFunction(List<string> list, int increment) {
var newList = new List<int>();
int first;
int index;
if (TryGetFirstNumber(list, out first, out index)) {
first -= index * increment;
} else {
first = 0;
}
newList.Add(first);
for (var i = 1; i < list.Length; i++) {
newList.Add(first + increment);
increment += increment;
}
return newList;
}
For LINQ purposes, I sometimes resort to writing a parse method that returns an int?as the result so that I can return null when it fails to parse. Here's a complete LINQPad implementation that illustrates this and the positional select (taking an approach otherwise similar to digEmAll's):
void Main()
{
var n = 6;
var items = new List<string>
// {"20","N", "N"};
// {"N", "26", "N"};
{"N", "N", "32"};
var first = items
.Select((v,index) => new { val = Parse(v), index })
.First(x => x.val.HasValue);
int start = first.val.Value - n * first.index;
List<string> values = items
.Select((x,i) => (i * n + start).ToString())
.ToList();
}
int? Parse(string strVal)
{
int ret;
if (int.TryParse(strVal, out ret))
{
return ret;
}
return null;
}
Seems like a lot of work to do something kinda simple. Here is a non linq approach.
private List<int> getVals(List<string> input, int modifier)
{
if (input == null) return null; if (input.Count < 1) return null;
foreach (var s in input)
{
int i;
try{i = Convert.ToInt32(s);}
catch{continue;}
var returnList = new List<int>(input.Count);
for (int n = 0; n < input.Count;n++ )returnList[n] = ((n - input.IndexOf(s)) * modifier) + i;
return returnList;
}
return null;
}
DevGeezer's answer, but without the cruft.
But I still learned alot!
static List<String> genlist2(List<String> list, int interval)
{
if (list == null) return null;
var vali = list
.Select((x, i) => x != "N" ? new {val = Convert.ToInt32(x), i } : null)
.First(x => x != null);
if (vali == null) return list.ToList();
return Enumerable.Range(0, list.Count)
.Select(x => (vali.val - (vali.i - x) * interval).ToString())
.ToList();
}