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
Related
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);
}
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.
I have a double[] array, i want to use it as key (not literally, but in the way that the key is matched when all the doubles in the double array need to be matched)
What is the fastest way to use the double[] array as key to dictionary?
Is it using
Dictionary<string, string> (convert double[] to a string)
or
anything else like converting it
Given that all key arrays will have the same length, either consider using a Tuple<,,, ... ,>, or use a structural equality comparer on the arrays.
With tuple:
var yourDidt = new Dictionary<Tuple<double, double, double>, string>();
yourDict.Add(Tuple.Create(3.14, 2.718, double.NaN), "da value");
string read = yourDict[Tuple.Create(3.14, 2.718, double.NaN)];
With (strongly typed version of) StructuralEqualityComparer:
class DoubleArrayStructuralEqualityComparer : EqualityComparer<double[]>
{
public override bool Equals(double[] x, double[] y)
{
return System.Collections.StructuralComparisons.StructuralEqualityComparer
.Equals(x, y);
}
public override int GetHashCode(double[] obj)
{
return System.Collections.StructuralComparisons.StructuralEqualityComparer
.GetHashCode(obj);
}
}
...
var yourDict = new Dictionary<double[], string>(
new DoubleArrayStructuralEqualityComparer());
yourDict.Add(new[] { 3.14, 2.718, double.NaN, }, "da value");
string read = yourDict[new[] { 3.14, 2.718, double.NaN, }];
Also consider the suggestion by Sergey Berezovskiy to create a custom class or (immutable!) struct to hold your set of doubles. In that way you can name your type and its members in a natural way that makes it more clear what you do. And your class/struct can easily be extended later on, if needed.
Thus all arrays have same length and each item in array have specific meaning, then create class which holds all items as properties with descriptive names. E.g. instead of double array with two items you can have class Point with properties X and Y. Then override Equals and GetHashCode of this class and use it as key (see What is the best algorithm for an overriding GetHashCode):
Dictionary<Point, string>
Benefits - instead of having array, you have data structure which makes its purpose clear. Instead of referencing items by indexes, you have nice named property names, which also make their purpose clear. And also speed - calculating hash code is fast. Compare:
double[] a = new [] { 12.5, 42 };
// getting first coordinate a[0];
Point a = new Point { X = 12.5, Y = 42 };
// getting first coordinate a.X
[Do not consider this a separate answer; this is an extension of #JeppeStigNielsen's answer]
I'd just like to point out that you make Jeppe's approach generic as follows:
public class StructuralEqualityComparer<T>: IEqualityComparer<T>
{
public bool Equals(T x, T y)
{
return StructuralComparisons.StructuralEqualityComparer.Equals(x, y);
}
public int GetHashCode(T obj)
{
return StructuralComparisons.StructuralEqualityComparer.GetHashCode(obj);
}
public static StructuralEqualityComparer<T> Default
{
get
{
StructuralEqualityComparer<T> comparer = _defaultComparer;
if (comparer == null)
{
comparer = new StructuralEqualityComparer<T>();
_defaultComparer = comparer;
}
return comparer;
}
}
private static StructuralEqualityComparer<T> _defaultComparer;
}
(From an original answer here: https://stackoverflow.com/a/5601068/106159)
Then you would declare the dictionary like this:
var yourDict = new Dictionary<double[], string>(new StructuralEqualityComparer<double[]>());
Note: It might be better to initialise _defaultComparer using Lazy<T>.
[EDIT]
It's possible that this might be faster; worth a try:
class DoubleArrayComparer: IEqualityComparer<double[]>
{
public bool Equals(double[] x, double[] y)
{
if (x == y)
return true;
if (x == null || y == null)
return false;
if (x.Length != y.Length)
return false;
for (int i = 0; i < x.Length; ++i)
if (x[i] != y[i])
return false;
return true;
}
public int GetHashCode(double[] data)
{
if (data == null)
return 0;
int result = 17;
foreach (var value in data)
result += result*23 + value.GetHashCode();
return result;
}
}
...
var yourDict = new Dictionary<double[], string>(new DoubleArrayComparer());
Ok this is what I found so far:
I input an entry (length 4 arrray) to the dictionary, and access it for 999999 times on my machine:
Dictionary<double[], string>(
new DoubleArrayStructuralEqualityComparer()); takes 1.75 seconds
Dictionary<Tuple<double...>,string> takes 0.85 seconds
The code below takes 0.1755285 seconds, which is the fastest now! (in line with the comment with Sergey.)
The fastest - The code of DoubleArrayComparer by Matthew Watson takes 0.15 seconds!
public class DoubleArray
{
private double[] d = null;
public DoubleArray(double[] d)
{
this.d = d;
}
public override bool Equals(object obj)
{
if (!(obj is DoubleArray)) return false;
DoubleArray dobj = (DoubleArray)obj;
if (dobj.d.Length != d.Length) return false;
for (int i = 0; i < d.Length; i++)
{
if (dobj.d[i] != d[i]) return false;
}
return true;
}
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
for (int i = 0; i < d.Length;i++ )
{
hash = hash*23 + d[i].GetHashCode();
}
return hash;
}
}
}
I am trying to implement the IComparable interface in my custom object so that List.Sort() can sort them alphabetically.
My object has a field called _name which is a string type, and I want it to sort based on that. Here is the method I implemented:
public int CompareTo(object obj)
{
//Int reference table:
//1 or greater means the current instance occurs after obj
//0 means both elements occur in the same position
//-1 or less means the current instance occurs before obj
if (obj == null)
return 1;
Upgrade otherUpgrade = obj as Upgrade;
if (otherUpgrade != null)
return _name.CompareTo(otherUpgrade.Name);
else
throw new ArgumentException("Passed object is not an Upgrade.");
}
Not sure if I did something wrong or if it's just the way the string CompareTo works, but basically my List was sorted like this:
Test Upgrade
Test Upgrade 10
Test Upgrade 11
Test Upgrade 12
Test Upgrade 13
Test Upgrade 14
Test Upgrade 15
Test Upgrade 2
Test Upgrade 3
Test Upgrade 4
Test Upgrade 5
I want them to be sorted like this:
Test Upgrade
Test Upgrade 2
Test Upgrade 3
...etc
You want "natural order" -- the collation that a human who is familiar with the conventions of English would choose -- as opposed to what you've got, which is "lexicographic" collation: assign every letter a strict ordering, and then sort by each letter in turn.
Jeff has a good article on some of the ins and outs here, with links to different algorithms that try to solve the problem:
http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html
and Raymond discussed how Windows dealt with it here:
http://technet.microsoft.com/en-us/magazine/hh475812.aspx
Basically the problem is: natural order collation requires solving an artificial intelligence problem; you're trying to emulate what a human would do, and that can be surprisingly tricky. Good luck!
Strings are sorted in lexicographic order. You'll have to either format all your numbers to have the same length (eg: Test Upgrade 02) or parse the number in your comparer and incorporate it in your comparison logic.
The reason this happens is that you are doing string comparison, which has no explicit knowledge of numbers. It orders each string by the respective character codes of each character.
To get the effect you want will require a bit more work. See this question: Sort on a string that may contain a number
AlphaNumeric Sorting
public class AlphanumComparatorFast : IComparer
{
public int Compare(object x, object y)
{
string s1 = x as string;
if (s1 == null)
{
return 0;
}
string s2 = y as string;
if (s2 == null)
{
return 0;
}
int len1 = s1.Length;
int len2 = s2.Length;
int marker1 = 0;
int marker2 = 0;
// Walk through two the strings with two markers.
while (marker1 < len1 && marker2 < len2)
{
char ch1 = s1[marker1];
char ch2 = s2[marker2];
// Some buffers we can build up characters in for each chunk.
char[] space1 = new char[len1];
int loc1 = 0;
char[] space2 = new char[len2];
int loc2 = 0;
// Walk through all following characters that are digits or
// characters in BOTH strings starting at the appropriate marker.
// Collect char arrays.
do
{
space1[loc1++] = ch1;
marker1++;
if (marker1 < len1)
{
ch1 = s1[marker1];
}
else
{
break;
}
} while (char.IsDigit(ch1) == char.IsDigit(space1[0]));
do
{
space2[loc2++] = ch2;
marker2++;
if (marker2 < len2)
{
ch2 = s2[marker2];
}
else
{
break;
}
} while (char.IsDigit(ch2) == char.IsDigit(space2[0]));
// If we have collected numbers, compare them numerically.
// Otherwise, if we have strings, compare them alphabetically.
string str1 = new string(space1);
string str2 = new string(space2);
int result;
if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
{
int thisNumericChunk = int.Parse(str1);
int thatNumericChunk = int.Parse(str2);
result = thisNumericChunk.CompareTo(thatNumericChunk);
}
else
{
result = str1.CompareTo(str2);
}
if (result != 0)
{
return result;
}
}
return len1 - len2;
}
}
Usage :
using System;
using System.Collections;
class Program
{
static void Main()
{
string[] highways = new string[]
{
"100F",
"50F",
"SR100",
"SR9"
};
//
// We want to sort a string array called highways in an
// alphanumeric way. Call the static Array.Sort method.
//
Array.Sort(highways, new AlphanumComparatorFast());
//
// Display the results
//
foreach (string h in highways)
{
Console.WriteLine(h);
}
}
}
Output
50F 100F SR9 SR100
Thanks for all the replies guys. I did my own method and it seems to work fine. It doesn't work for all cases but it works for my scenario. Here is the code for anyone who is interested:
/// <summary>
/// Compares the upgrade to another upgrade
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public int CompareTo(object obj)
{
//Int reference table:
//1 or greater means the current instance occurs after obj
//0 means both elements occur in the same position
//-1 or less means the current instance occurs before obj
if (obj == null)
return 1;
Upgrade otherUpgrade = obj as Upgrade;
if (otherUpgrade != null)
{
//Split strings into arrays
string[] splitStringOne = _name.Split(new char[] { ' ' });
string[] splitStringTwo = otherUpgrade.Name.Split(new char[] { ' ' });
//Will hold checks to see which comparer will be used
bool sameWords = false, sameLength = false, bothInt = false;
//Will hold the last part of the string if it is an int
int intOne = 0, intTwo = 0;
//Check if they have the same length
sameLength = (splitStringOne.Length == splitStringTwo.Length);
if (sameLength)
{
//Check to see if they both end in an int
bothInt = (int.TryParse(splitStringOne[splitStringOne.Length - 1], out intOne) && int.TryParse(splitStringTwo[splitStringTwo.Length - 1], out intTwo));
if (bothInt)
{
//Check to see if the previous parts of the string are equal
for (int i = 0; i < splitStringOne.Length - 2; i++)
{
sameWords = (splitStringOne[i].ToLower().Equals(splitStringTwo[i].ToLower()));
if (!sameWords)
break;
}
}
}
//If all criteria is met, use the customk comparer
if (sameWords && sameLength && bothInt)
{
if (intOne < intTwo)
return -1;
else if (intOne > intTwo)
return 1;
else //Both equal
return 0;
}
//Else use the default string comparer
else
return _name.CompareTo(otherUpgrade.Name);
}
else
throw new ArgumentException("Passed object is not an Upgrade.");
}
Will work for strings spaced out using a " " character, like so:
Test data:
Hello 11
Hello 2
Hello 13
Result
Hello 2
Hello 11
Hello 13
Will not work for data such as Hello11 and Hello2 since it cannot split them. Not case sensitive.
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.