Related
I have implemented a equality comparer in below manner.
class BoxEqualityComparer : IEqualityComparer<Box>
{
public bool Equals(Box b1, Box b2)
{
if (b2 == null && b1 == null)
return true;
else if (b1 == null | b2 == null)
return false;
else if(b1.Height == b2.Height && b1.Length == b2.Length
&& b1.Width == b2.Width)
return true;
else
return false;
}
public int GetHashCode(Box bx)
{
int hCode = bx.Height ^ bx.Length ^ bx.Width;
return hCode.GetHashCode();
}
}
Then I have created a Dictionary, in that I will add some values. So here it will compare object based on it's properties (height, width, length). I am getting the expected output. But I am wondering about the execution of GetHashCode method. I put a breakpoint in there, but I am unable to debug it. My question is when does GeHashCode method will be executed and how many times?
class Example
{
static void Main()
{
BoxEqualityComparer boxEqC = new BoxEqualityComparer();
var boxes = new Dictionary<Box, string>(boxEqC);
var redBox = new Box(4, 3, 4);
AddBox(boxes, redBox, "red");
var blueBox = new Box(4, 3, 4);
AddBox(boxes, blueBox, "blue");
var greenBox = new Box(3, 4, 3);
AddBox(boxes, greenBox, "green");
Console.WriteLine();
Console.WriteLine("The dictionary contains {0} Box objects.",
boxes.Count);
}
private static void AddBox(Dictionary<Box, String> dict, Box box, String name)
{
try {
dict.Add(box, name);
}
catch (ArgumentException e) {
Console.WriteLine("Unable to add {0}: {1}", box, e.Message);
}
}
}
public class Box
{
public Box(int h, int l, int w)
{
this.Height = h;
this.Length = l;
this.Width = w;
}
public int Height { get; set; }
public int Length { get; set; }
public int Width { get; set; }
public override String ToString()
{
return String.Format("({0}, {1}, {2})", Height, Length, Width);
}
}
See https://referencesource.microsoft.com/#mscorlib/system/collections/generic/dictionary.cs,fd1acf96113fbda9.
Add(key, value) calls the insert method, which in turn will always calculate a hash code via
int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
So in other words, each call to Dictionary.Add should always trigger a calculation of the key's hash via the IEqualityComparer you provided.
As for your example code, this works fine for me, VS 2015 does break at BoxEqualityComparer.GetHashCode() for me.
I have a few classes that have a sequence of integer, those sequences are registered in another class that checks if a number in the sequence isn't already used.
The sequences are for the most contiguous, going from a number to another.
For now I've been using a simple List that means if a sequence represents from 5000 to 15000 there will be 10000 elements in the List.
I would like to replace it by something more suitable that can represents range in a simple element.
In my particular case, I would also like those ranges to represent an object (the class where the sequence originated from), so that when I lookup for a number, I have access to its origin rather than looking through each class to see if they contain the number I'm looking for.
Here is my pseudocode with the results I expect:
/* int is the integer type, while string is the "tag" object */
var animals = new IntRangeArray<int, string>();
animals.Add(1, "dog");
// [0] begin: 1, end: 1, object: "dog"
animals.Add(2, "dog");
// [0] begin: 1, end: 2, object: "dog"
/* AddRange with C#7.0 ValueTuple */
animals.AddRange((4,14), "dog");
// [0] begin: 1, end: 2, object: "dog"
// [1] begin: 4, end: 14, object: "dog"
animals.Add(3, "dog");
// [0] begin: 1, end: 14, object: "dog"
/* All sequences have been merged because they are contiguous and have the same tag */
animals.AddRange( new int[]{ 15, 17, 18, 19 }, "dog");
// [0] begin: 1, end: 15, object: "dog"
// [1] begin: 17, end: 19, object: "dog"
animals.Add(16, "cat");
// [0] begin: 1, end: 15, object: "dog"
// [1] begin: 16, end: 16, object: "cat"
// [2] begin: 17, end: 19, object: "dog"
animals.Remove(8);
// [0] begin: 1, end: 7, object: "dog"
// [1] begin: 9, end: 15, object: "dog"
// [2] begin: 16, end: 16, object: "cat"
// [3] begin: 17, end: 18, object: "dog"
animals.At(11);
// struct { Begin = 9, End = 15, Tag = "dog" }
animals.RemoveWithTag("dog");
// [0] begin: 16, end: 16, object: "cat"
animals.TagOf(16);
// "cat"
I could not find any classes within the .NET Framework that implements this behavior so I would like to know how could I implement this or if there is any already existing implementation.
For this kind of thing I usually end up writing my own classes. Here's what I would do for this:
First, the Range class, which has a Begin, End, and Tag. It also has some helper methods to simplify querying for ranges that are overlapping and adjacent, or for doing case-insensitive tag comparison, and for outputting a string value:
class Range
{
public int Begin { get; set; }
public int End { get; set; }
public string Tag { get; set; }
public bool CombineWith(Range other)
{
Range combinedRange;
if (TryCombine(this, other, out combinedRange))
{
this.Begin = combinedRange.Begin;
this.End = combinedRange.End;
return true;
}
return false;
}
public bool IsAdjacentTo(Range other)
{
return AreAdjacent(this, other);
}
public bool OverlapsWith(Range other)
{
return AreOverlapping(this, other);
}
public bool ContainsIndex(int index)
{
return this.Begin <= index && this.End >= index;
}
public bool TagEquals(string tag)
{
if (this.Tag == null) return tag == null;
return this.Tag.Equals(tag, StringComparison.OrdinalIgnoreCase);
}
public static bool TryCombine(Range first, Range second, out Range combined)
{
combined = new Range();
if (first == null || second == null) return false;
if (!TagsEqual(first, second)) return false;
if (!AreAdjacent(first, second) && !AreOverlapping(first, second)) return false;
combined.Begin = Math.Min(first.Begin, second.Begin);
combined.End = Math.Max(first.End, second.End);
combined.Tag = first.Tag;
return true;
}
public static bool AreAdjacent(Range first, Range second)
{
if (first == null || second == null) return false;
if (!Range.TagsEqual(first, second)) return false;
return (first.Begin == second.End + 1) ||
(first.End == second.Begin - 1);
}
public static bool AreOverlapping(Range first, Range second)
{
if (first == null || second == null) return false;
return (first.Begin >= second.Begin && first.Begin <= second.End) ||
(first.End >= second.Begin && first.End <= second.End);
}
public static bool TagsEqual(Range first, Range second)
{
if (first == null || second == null) return false;
return first.TagEquals(second.Tag);
}
public override string ToString()
{
return $"begin: {Begin}, end: {End}, tag: {Tag}";
}
}
Next is your IntRangeArray class, which manages the adding and removing of items in the a list of Range objects:
class IntRangeArray
{
private readonly List<Range> ranges = new List<Range>();
public bool Add(int index, string tag)
{
return AddRange(index, index, tag);
}
public bool AddRange(IEnumerable<int> indexes, string tag)
{
if (indexes == null || string.IsNullOrWhiteSpace(tag)) return false;
bool result = true;
foreach (var index in indexes)
{
if (!Add(index, tag)) result = false;
}
return result;
}
public bool AddRange(Tuple<int, int> range, string tag)
{
return AddRange(range.Item1, range.Item2, tag);
}
public bool AddRange(int begin, int end, string tag)
{
if (begin < 0 || end < 0 || string.IsNullOrWhiteSpace(tag)) return false;
var newRange = new Range {Begin = begin, End = end, Tag = tag};
var overlappingRanges = ranges.Where(r => r.OverlapsWith(newRange)).ToList();
var adjacentRanges = ranges.Where(r => r.IsAdjacentTo(newRange)).ToList();
if (overlappingRanges.Any())
{
if (!overlappingRanges.All(r => r.TagEquals(newRange.Tag)))
{
return false;
}
foreach (var overlappingRange in overlappingRanges)
{
newRange.CombineWith(overlappingRange);
ranges.Remove(overlappingRange);
}
}
foreach (var adjacentRange in adjacentRanges)
{
newRange.CombineWith(adjacentRange);
ranges.Remove(adjacentRange);
}
ranges.Add(newRange);
return true;
}
public string At(int index)
{
var matchingRange = ranges.SingleOrDefault(r => r.ContainsIndex(index));
return matchingRange?.ToString() ?? $"No item exists at {index}";
}
public void Remove(int index)
{
var matchingRange = ranges.SingleOrDefault(r => r.ContainsIndex(index));
if (matchingRange == null) return;
if (matchingRange.Begin == matchingRange.End)
{
ranges.Remove(matchingRange);
}
else if (index == matchingRange.Begin)
{
matchingRange.Begin += 1;
}
else if (index == matchingRange.End)
{
matchingRange.End -= 1;
}
else
{
// Split the range by creating a new one for the beginning
var newRange = new Range
{
Begin = matchingRange.Begin,
End = index - 1,
Tag = matchingRange.Tag
};
matchingRange.Begin = index + 1;
ranges.Add(newRange);
}
}
public void RemoveWithTag(string tag)
{
ranges.RemoveAll(r => r.TagEquals(tag));
}
public string TagOf(int index)
{
var matchingRange = ranges.SingleOrDefault(r => r.ContainsIndex(index));
return matchingRange == null ? $"No item exists at {index}" : matchingRange.Tag;
}
public override string ToString()
{
if (ranges == null || !ranges.Any()) return "No items exist.";
ranges.Sort((x, y) => x.Begin.CompareTo(y.Begin));
var output = new StringBuilder();
for(int i = 0; i < ranges.Count; i++)
{
output.AppendLine($"[{i}] {ranges[i]}");
}
return output.ToString();
}
}
To test it out, I just copied and pasted your code sample above:
private static void Main()
{
/* int is the integer type, while string is the "tag" object */
var animals = new IntRangeArray();
animals.Add(1, "dog");
Console.WriteLine(animals);
animals.Add(2, "dog");
Console.WriteLine(animals);
/* AddRange with C#7.0 ValueTuple */
animals.AddRange(Tuple.Create(4, 14), "dog");
Console.WriteLine(animals);
animals.Add(3, "dog");
Console.WriteLine(animals);
animals.AddRange(new int[] { 15, 17, 18, 19 }, "dog");
Console.WriteLine(animals);
animals.Add(16, "cat");
Console.WriteLine(animals);
animals.Remove(8);
Console.WriteLine(animals);
Console.WriteLine(animals.At(11));
animals.RemoveWithTag("dog");
Console.WriteLine(animals);
Console.WriteLine(animals.TagOf(16));
Console.WriteLine("\nDone!\nPress any key to exit...");
Console.ReadKey();
}
And the output is as you expected (except there is one item different, but that was a bug on your side):
Here is my implementation. I created a help class to manage integer ranges using SortedList and then created the main class to manage tagged integer ranges using a Dictionary to consolidate the tags.
class IntegerRangeCollection {
SortedList<int, int> ranges = new SortedList<int, int>();
public class Range {
public int begin, end;
}
public IntegerRangeCollection() {
}
public IntegerRangeCollection(int b, int e) {
this.Add(b, e);
}
public void Add(int b, int e) {
if (ranges.Any()) {
if (ranges.ContainsKey(b)) {
if (e > ranges[b])
ranges[b] = e;
}
else
ranges.Add(b, e);
FixUp();
}
else
ranges.Add(b, e); // new ranges list
}
public void Add(int p) => this.Add(p, p);
public void Remove(int p) {
var r = ranges.Where(pr => pr.Key <= p && p <= pr.Value).First();
if (r.Key == p) { // Remove Range begin
ranges.Remove(r.Key);
if (p+1 <= r.Value)
ranges.Add(p+1, r.Value);
}
else if (p == r.Value) // Remove Range end
ranges[r.Key] = p - 1;
else { // Split Range
ranges[r.Key] = p-1;
ranges.Add(p+1, r.Value);
}
}
public Range At(int n) {
var kvr = ranges.Where(kv => kv.Key <= n && n <= kv.Value);
if (kvr.Any()) {
var kvrf = kvr.First();
return new Range { begin = kvrf.Key, end = kvrf.Value };
}
else
return null;
}
public bool Contains(int n) => ranges.Where(kv => kv.Key <= n && n <= kv.Value).Any();
public bool IsEmpty() => !ranges.Any();
private bool DoFixUp() { // remove any overlapping ranges
foreach (var r in ranges) {
foreach (var pr in ranges.Where(pr => r.Key != pr.Key && r.Value == pr.Key - 1)) { // consolidate adjacent ranges
ranges.Remove(pr.Key);
ranges[r.Key] = pr.Value;
return true;
}
foreach (var pr in ranges.Where(pr => r.Key != pr.Key && pr.Key <= r.Value && r.Value <= pr.Value)) { // overlap end
if (pr.Key > r.Key) { // partial overlap, extend beginning
ranges.Remove(pr.Key);
ranges[r.Key] = pr.Value;
return true;
}
else { // complete overlap, remove
ranges.Remove(r.Key);
return true;
}
}
}
return false;
}
private void FixUp() {
while (DoFixUp())
;
}
}
class ObjectRangeCollection<objType> where objType : class {
Dictionary<objType, IntegerRangeCollection> d = new Dictionary<objType, IntegerRangeCollection>();
public void Add(int begin, int end, objType obj) {
if (d.TryGetValue(obj, out var ranges))
ranges.Add(begin, end);
else
d.Add(obj, new IntegerRangeCollection(begin, end));
}
public void Add(int p, objType obj) => Add(p, p, obj);
public void AddRange(ValueTuple<int, int> r, objType obj) => Add(r.Item1, r.Item2, obj);
public void AddRange(int[] rs, objType obj) {
foreach (var r in rs)
this.Add(r, r, obj);
}
public class AtAnswer {
public int begin, end;
public object tag;
}
public AtAnswer At(int p) {
var possibles = d.Where(kv => kv.Value.Contains(p));
if (possibles.Any()) {
var kv = possibles.First();
var r = kv.Value.At(p);
return new AtAnswer { tag = kv.Key, begin = r.begin, end = r.end };
}
else
return null;
}
public objType TagOf(int p) {
var possibles = d.Where(kv => kv.Value.Contains(p));
if (possibles.Any())
return possibles.First().Key;
else
return null;
}
public void Remove(int p) {
var possibles = d.Where(kv => kv.Value.Contains(p));
if (possibles.Any()) {
foreach (var kv in possibles) {
kv.Value.Remove(p);
if (kv.Value.IsEmpty())
d.Remove(kv.Key);
}
}
}
public void RemoveWithTag(objType aTag) {
d.Remove(aTag);
}
}
Try something like this
List<KeyValuePair<int, string>> animals = new List<KeyValuePair<int,string>>();
List<KeyValuePair<int, string>> newValues = Enumerable.Repeat(1,11).Select((x,i) => new KeyValuePair<int,string>(i + 4, "dog")).ToList();
animals.AddRange(newValues);
Dictionary<string, List<Tuple<int, int>>> Test = new Dictionary<string,
List<Tuple<int, int>>>();
int rangebegin=1;
int rangeend=4;
Test.Add("dog", new List<Tuple<int, int>>
{ new Tuple<int, int>(rangebegin, rangend) });
rangebegin=16;
rangend=22;
Test[dog].Add(new Tuple<int, int>(rangebegin, rangeend));
// multiple ranges stored under same key
// you can easily calculate which range your number is in and get
// that range
[That title may be wrong for the question, please inform me if so]
I'm coding a little maths quiz in C#, and I was wondering how to make an if statement that says something similiar to:
"if the user responds with 'this' or ' this'
{
do blahblahblah
}
But I don't know how to say the OR bit in C#, I looked through the C# operators page, but kind of got lost in the technical jargon (I'm a rookie).
This is what I have so far:
Console.WriteLine("What is 200 / 5?");
string sFirstAnswer = Console.ReadLine();
if (sFirstAnswer == "40" || " 40")
{
sUser1Score++;
Console.WriteLine("\n Correct, 200 / 5 = 40. You have been awarded 1 point.");
Console.ReadLine();
}
Write
if (sFirstAnswer == "40" || sFirstAnswer == " 40")
or better yet, trim the answer:
if (sFirstAnswer.Trim() == "40")
if (sFirstAnswer == "40" || sFirstAnswer == "40")
You can create a list of allowed answers and then check it's in the list.
var correctFirstAnswers = new List<string>{"40", " 40"};
if (correctFirstAnswers.Contains(sFirstAnswer))
this is more readable than || when there are multiple possible answers.
I thought I might give an (over-the-top) example of what I meant to make it a bit more Dynamic
A few classes now help to ask you the questions, and with a few functions built around it, you can easily show your questions in a menu format, and then ask the question, with random nr's (only whole number division was a bit more annoying :))
You could make it easier that the Generate method limits the range a bit more, but I just thought I wanted to give you an idea of how it could look like
using System;
using System.Collections.Generic;
namespace MathQuiz
{
class Program
{
interface IExercise
{
string Title { get; }
void Generate();
}
abstract class Exercise<TResult> : IExercise
{
public virtual string Title
{
get
{
return "Exercise";
}
}
public abstract bool isCorrect(TResult reply);
public abstract TResult Solve();
public abstract bool TryParse(string value, out TResult result);
public abstract void Generate();
}
abstract class ExerciseWith2Items<TSource, TResult> : Exercise<TResult>
{
public virtual TSource Item1 { get; set; }
public virtual TSource Item2 { get; set; }
public abstract string Operator { get; }
public override string ToString()
{
return string.Format("{0} {1} {2}", Item1, Operator, Item2);
}
}
abstract class WholeNumberExercise : ExerciseWith2Items<int, int>
{
public override void Generate()
{
Random next = new Random();
Item1 = next.Next(100) + 15;
Item2 = next.Next(100) + 15;
}
public override bool TryParse(string value, out int result)
{
return int.TryParse(value, out result);
}
}
class Division : WholeNumberExercise
{
protected bool IsPrime(int nr)
{
int max = (int)Math.Sqrt(nr);
if (nr <= 2)
{
return true;
}
for (int i = 2; i < max; i++)
{
if (nr % i == 0)
{
return false;
}
}
return true;
}
public override int Item1
{
get
{
return base.Item1;
}
set
{
// primes cannot be divived, so increase the value until we don't have a prime
while (IsPrime(value))
{
value++;
}
base.Item1 = value;
}
}
public override int Item2
{
get
{
return base.Item2;
}
set
{
if (value <= 0)
{
// minimum 2
value = 2;
}
// small override: we only want whole number division, so change the nr to the closest nr that has no rest after division
int closest = 0;
while ((value - closest > 1 && Item1 % (value - closest) != 0) ||
(value + closest < Item1 && Item1 % (value + closest) != 0))
{
closest++;
}
// in case closest == 0, it doesn't really change anything
if (Item1 % (value - closest) == 0)
{
value -= closest;
}
else
{
value += closest;
}
base.Item2 = value;
}
}
public override string Operator
{
get { return "/"; }
}
public override bool isCorrect(int reply)
{
return reply == (Item1 / Item2);
}
public override void Generate()
{
Random r = new Random();
Item1 = r.Next(500) + 100;
Item2 = r.Next(50) + 2;
}
public override int Solve()
{
return (Item1 / Item2);
}
}
class Multiplication : WholeNumberExercise
{
public override string Operator
{
get { return "*"; }
}
public override bool isCorrect(int reply)
{
return reply == (Item1 * Item2);
}
public override int Solve()
{
return (Item1 * Item2);
}
}
class Addition : WholeNumberExercise
{
public override string Operator
{
get { return "+"; }
}
public override bool isCorrect(int reply)
{
return reply == (Item1 + Item2);
}
public override int Solve()
{
return (Item1 + Item2);
}
}
class Subtraction : WholeNumberExercise
{
public override string Operator
{
get { return "-"; }
}
public override bool isCorrect(int reply)
{
return reply == (Item1 - Item2);
}
public override int Solve()
{
return (Item1 - Item2);
}
}
static IExercise ShowMenu(IList<IExercise> exercises)
{
int menu;
do
{
Console.Clear();
Console.WriteLine("Test your match skills :)\r\n");
for (int i = 0; i < exercises.Count; i++)
{
Console.WriteLine("\t{0}\t{1}", i, exercises[i].GetType().Name);
}
Console.WriteLine("\r\n\t99\tExit\r\n");
Console.Write("Please enter your choice: ");
if (!int.TryParse(Console.ReadLine(), out menu))
{
// wrong input
menu = -1;
}
if (menu != 99)
{
if (menu >= exercises.Count)
{
menu = -1;
}
}
} while (menu < 0);
IExercise result = null;
if (menu != 99)
{
result = exercises[menu];
}
return result;
}
static void Solve(IExercise exercise)
{
if (exercise == null)
{
return;
}
if (!(exercise is WholeNumberExercise))
{
Console.WriteLine("Don't know how to solve this exercise, please contact developer :)");
Console.ReadLine();
return;
}
var solvable = exercise as WholeNumberExercise;
solvable.Generate();
Console.Write("{0}: '{1}' = ", solvable.GetType().Name, exercise);
int reply;
bool validAnswerGiven;
do
{
validAnswerGiven = solvable.TryParse(Console.ReadLine(), out reply);
if (validAnswerGiven)
{
if (solvable.isCorrect(reply))
{
Console.WriteLine("Correct!");
}
else
{
Console.WriteLine("Incorrect, the correct result is {0}", solvable.Solve());
}
}
else
{
Console.WriteLine("Please enter valid value (whole number)!");
}
} while (!validAnswerGiven);
Console.ReadLine();
}
static void Main(string[] args)
{
IList<IExercise> potentialExercises = new List<IExercise>()
{
new Addition(),
new Subtraction(),
new Division(),
new Multiplication()
};
IExercise selectedExercise;
do
{
selectedExercise = ShowMenu(potentialExercises);
Solve(selectedExercise);
} while (selectedExercise != null);
Console.WriteLine("Program completed!");
}
}
}
it is runnable code, so copy and paste in visual studio console project should do the trick ;)
In addition to the above you could also try to convert the string to an integer
int number = 0
bool result = Int32.TryParse(Console.ReadLine(), out number);
if (number== 40){
...
}
number will be 0 if conversion fails to int and result false but for your case you dont care its not 40...
MSDN Int TryParse
Since you're validating an answer from an equation, you want to Parse it and get it into a numeric form pronto.
var answer = Convert.ToInt32(sFirstAnswer.Trim());
var expected = 40;
if(answer == expected){
//gold ribbon
}
my TIME Enum contains Annual, Monthly, weekly, daily and Hourly.
Here I want to decide which is the minimum and want to return that.
How can I do this ? Here is the code I tried.
private Time DecideMinTime(IEnumerable<Time> g)
{
var minTime = Time.Hourly;
foreach (var element in g)
{
minTime = element;
}
return minTime;
}
Assuming that the numeric value of the enum elements decides what the minimum is:
private Time DecideMinTime(IEnumerable<Time> g)
{
if (g == null) { throw new ArgumentNullException("g"); }
return (Time)g.Cast<int>().Min();
}
If the numeric values indicate the opposite order then you would use .Max() instead of .Min().
As indicated, the numeric order is not consistent. This can be worked around simply by using a mapping indicating the correct order:
static class TimeOrdering
{
private static readonly Dictionary<Time, int> timeOrderingMap;
static TimeOrdering()
{
timeOrderingMap = new Dictionary<Time, int>();
timeOrderingMap[Time.Hourly] = 1;
timeOrderingMap[Time.Daily] = 2;
timeOrderingMap[Time.Weekly] = 3;
timeOrderingMap[Time.Monthly] = 4;
timeOrderingMap[Time.Annual] = 5;
}
public Time DecideMinTime(IEnumerable<Time> g)
{
if (g == null) { throw new ArgumentNullException("g"); }
return g.MinBy(i => timeOrderingMap[i]);
}
public TSource MinBy<TSource, int>(
this IEnumerable<TSource> self,
Func<TSource, int> ordering)
{
if (self == null) { throw new ArgumentNullException("self"); }
if (ordering == null) { throw new ArgumentNullException("ordering"); }
using (var e = self.GetEnumerator()) {
if (!e.MoveNext()) {
throw new ArgumentException("Sequence is empty.", "self");
}
var minElement = e.Current;
var minOrder = ordering(minElement);
while (e.MoveNext()) {
var curOrder = ordering(e.Current);
if (curOrder < minOrder) {
minOrder = curOrder;
minElement = e.Current;
}
}
return minElement;
}
}
}
To make it easier you can assign int values to your enum:
enum Time : byte {Hourly=1, Daily=2, Weekly=3, Monthly=4, Annual=5};
and then
private static Time DecideMinTime(IEnumerable<Time> g)
{
return g.Min();
}
That way you avoid casting back and forth.
I have a collection of Layers where they have names and colors. What I want to do is to sort these first based on colors, then based on their names:
class Layer
{
public string Name {get; set;}
public LayerColor Color {get; set;}
}
enum LayerColor
{
Red,
Blue,
Green
}
Like:
(red) layer2
(red) layer7
(blue) layer0
(blue) layer3
...
I was looking at SortedList but that acts like a Dictionary so doesn't allow for duplicate items.
Also I am using an API where I get the list of Layers by creation order, so I need to get the full list of Layers to sort them the way I want.
Eventually the list of Layers will be binded to a WPF UI where the users will have the ability to add new Layers, so that's why I wanted the internal list to always be sorted as the performance is not important (the number of Layers are less than a thousand).
In the end the Layers I sorted will be accessed via something like this:
class Image
{
public MySortedList<Layer> Layers {get; set;}
}
What's the best way to do this?
A little late to the party, but up for posterity's sake.
in order to optimise separation of concerns, I wrote a wrapper class which keeps a list sorted (and allows duplicates), as below:
public class OrderedList<T> : IList<T>, ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable
{
#region Fields
readonly List<T> _list;
readonly IComparer<T> _comparer;
#endregion
#region Constructors
OrderedList(List<T> list, IComparer<T> comparer)
{
_list = list;
_comparer = comparer;
}
public OrderedList()
: this(new List<T>(), Comparer<T>.Default)
{
}
public OrderedList(IComparer<T> comparer)
: this(new List<T>(), comparer)
{
}
public OrderedList(IEnumerable<T> collection)
: this(collection, Comparer<T>.Default)
{
}
public OrderedList(IEnumerable<T> collection, IComparer<T> comparer)
: this(new List<T>(collection), comparer)
{
_list.Sort(comparer);
}
public OrderedList(int capacity)
: this(new List<T>(capacity), Comparer<T>.Default)
{
}
public OrderedList(int capacity, IComparer<T> comparer)
: this(new List<T>(capacity), comparer)
{
}
//yet to be implemented
//public void OrderedList(Comparison<T> comparison);
#endregion
#region Properties
public int Capacity { get { return _list.Capacity; } set { _list.Capacity = value; } }
public int Count { get { return _list.Count; } }
object IList.this[int index] { get { return _list[index]; } set { _list[index] = (T)value; } }
public T this[int index] { get { return _list[index]; } set { _list[index] = value; } }
//public bool IsSynchronized { get { return false; } }
bool ICollection.IsSynchronized { get { return false; } }
//public object SyncRoot { get { return _list; } }
object ICollection.SyncRoot { get { return _list; } } //? should return this
bool IList.IsFixedSize { get { return false; } }
bool IList.IsReadOnly { get { return false; } }
bool ICollection<T>.IsReadOnly { get { return false; } }
#endregion
#region Methods
void ICollection<T>.Add(T item)
{
Add(item);
}
/// <summary>
/// Adds a new item to the appropriate index of the SortedList
/// </summary>
/// <param name="item">The item to be removed</param>
/// <returns>The index at which the item was inserted</returns>
public int Add(T item)
{
int index = BinarySearch(item);
if (index < 0)
{
index = ~index;
}
_list.Insert(index, item);
return index;
}
int IList.Add(object item)
{
return Add((T)item);
}
//NOT performance tested against other ways algorithms yet
public void AddRange(IEnumerable<T> collection)
{
var insertList = new List<T>(collection);
if (insertList.Count == 0)
{
return;
}
if (_list.Count == 0)
{
_list.AddRange(collection);
_list.Sort(_comparer);
return;
}
//if we insert backwards, index we are inserting at does not keep incrementing
insertList.Sort(_comparer);
int searchLength = _list.Count;
for (int i=insertList.Count-1;i>=0;i--)
{
T item = insertList[i];
int insertIndex = BinarySearch(0, searchLength, item);
if (insertIndex < 0)
{
insertIndex = ~insertIndex;
}
else
{
while (--insertIndex>=0 && _list[insertIndex].Equals(item)) { }
insertIndex++;
}
if (insertIndex<=0)
{
_list.InsertRange(0, insertList.GetRange(0, i+1 ));
break;
}
searchLength = insertIndex-1;
item = _list[searchLength];
int endInsert = i;
while (--i>=0 && _comparer.Compare(insertList[i], item) > 0) { }
i++;
_list.InsertRange(insertIndex, insertList.GetRange(i, endInsert - i +1));
}
}
public int BinarySearch(T item)
{
return _list.BinarySearch(item, _comparer);
}
public int BinarySearch(int index, int count, T item)
{
return _list.BinarySearch(index,count,item, _comparer);
}
public ReadOnlyCollection<T> AsReadOnly()
{
return _list.AsReadOnly();
}
public void Clear() { _list.Clear(); }
public bool Contains(T item) { return BinarySearch(item) >= 0; }
bool IList.Contains(object item)
{
return Contains((T)item);
}
public List<TOutput> ConvertAll<TOutput>(Converter<T, TOutput> converter) { return _list.ConvertAll(converter); }
public void CopyTo(T[] array) { _list.CopyTo(array); }
public void CopyTo(T[] array, int arrayIndex) { _list.CopyTo(array,arrayIndex); }
void ICollection.CopyTo(Array array, int arrayIndex) { _list.CopyTo((T[])array, arrayIndex); }
public void CopyTo(int index, T[] array, int arrayIndex, int count) { _list.CopyTo(index, array, arrayIndex, count); }
public void ForEach(Action<T> action)
{
foreach (T item in _list)
{
action(item);
}
}
IEnumerator IEnumerable.GetEnumerator() { return _list.GetEnumerator(); }
public IEnumerator<T> GetEnumerator() { return _list.GetEnumerator(); }
public List<T> GetRange(int index, int count) { return _list.GetRange(index,count); }
public bool Remove(T item)
{
int index = BinarySearch(item);
if (index < 0)
{
return false;
}
_list.RemoveAt(index);
return true;
}
void IList.Remove(object item)
{
Remove((T)item);
}
public void RemoveAt(int index) { _list.RemoveAt(index); }
public void RemoveRange(int index, int count) { _list.RemoveRange(index, count); }
public T[] ToArray() { return _list.ToArray(); }
public void TrimExcess() { _list.TrimExcess(); }
/// <summary>
/// Find the first index of the given item
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public int IndexOf(T item)
{
int index = BinarySearch(item);
if (index < 0) return -1;
while(--index >= 0 && _list[index].Equals(item)){}
return index+1;
}
int IList.IndexOf(object item)
{
return IndexOf((T)item);
}
/// <summary>
/// Find the last index of the given item
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public int LastIndexOf(T item)
{
int index = BinarySearch(item);
if (index < 0) return -1;
while (++index < _list.Count && _list[index].Equals(item)) { }
return index-1;
}
/// <summary>
/// Return all values within bounds specified
/// </summary>
/// <param name="min">Minimum Bound</param>
/// <param name="max">Maximum Bound</param>
/// <returns>subset of list with values within or equal to bounds specified</returns>
public T[] WithinRange(T min, T max)
{
if (_comparer.Compare(min,max) > 0)
{
throw new ArgumentException("min must be <= max");
}
int minSearchLength;
int maxIndex = _list.BinarySearch(max, _comparer);
if (maxIndex >= 0)
{
minSearchLength = maxIndex + 1;
while (++maxIndex < _list.Count && _comparer.Compare(max, _list[maxIndex]) == 0) { }
--maxIndex;
}
else
{
minSearchLength = ~maxIndex;
if (minSearchLength <= 0)
{
return new T[0];
}
maxIndex = minSearchLength - 1;
}
int minIndex = _list.BinarySearch(0, minSearchLength, min, _comparer);
if (minIndex >= 0)
{
while (--minIndex >= 0 && _comparer.Compare(max, _list[minIndex]) == 0) { }
++minIndex;
}
else
{
minIndex = ~minIndex;
if (minIndex > maxIndex)
{
return new T[0];
}
}
int length = maxIndex - minIndex + 1;
var returnVar = new T[length];
_list.CopyTo(minIndex, returnVar, 0, length);
return returnVar;
}
#endregion
#region NotImplemented
const string _insertExceptionMsg = "SortedList detemines position to insert automatically - use add method without an index";
void IList.Insert(int index, object item)
{
throw new NotImplementedException(_insertExceptionMsg);
}
void IList<T>.Insert(int index, T item)
{
throw new NotImplementedException(_insertExceptionMsg);
}
#endregion
}
Tests written are not extensive (or pretty) but are included in case anyone wanted to expand on them
[TestClass]
public class TestOrderedList
{
[TestMethod]
public void TestIntegerList()
{
var startList = new List<int>(new int[] { 5, 2, 1, 4, 5, 5, 2 });
var olist = new OrderedList<int>(startList);
startList = startList.OrderBy(l => l).ToList();
CollectionAssert.AreEqual(startList, olist);
Assert.AreEqual(0, olist.Add(0));
int nextInc = olist.Max() + 1;
Assert.AreEqual(olist.Count, olist.Add(nextInc));
CollectionAssert.AreEqual(startList.Concat(new int[] { 0, nextInc }).OrderBy(l => l).ToList(), olist);
Assert.IsTrue(olist.Remove(0));
Assert.IsFalse(olist.Remove(0));
Assert.IsTrue(olist.Remove(nextInc));
CollectionAssert.AreEqual(startList, olist);
var addList = new List<int>(new int[] { 5, -1, 2, 2, -1, 3, 2 });
olist.AddRange(addList);
addList = startList.Concat(addList).OrderBy(l => l).ToList();
CollectionAssert.AreEqual(addList, olist);
olist.Remove(-1);
addList.Remove(-1);
CollectionAssert.AreEqual(addList, olist);
olist.Remove(2);
addList.Remove(2);
CollectionAssert.AreEqual(addList, olist);
olist = new OrderedList<int>();
int[] seed = new int[] { -2, -2 };
olist.AddRange(seed);
CollectionAssert.AreEqual(seed, olist);
olist.AddRange(new int[] { });
olist.AddRange(new int[] { -2 });
CollectionAssert.AreEqual(seed.Concat(new int[] { -2 }).ToList(), olist);
olist.AddRange(new int[] { -3 });
CollectionAssert.AreEqual((new int[] { -3, -2 }).Concat(seed).ToList(), olist);
}
[TestMethod]
public void TestIndexOf()
{
var test = new OrderedList<int>(new[] { 0, -1, -2 });
Assert.AreEqual(0, test.IndexOf(-2));
Assert.AreEqual(2, test.IndexOf(0));
test.Add(-2);
Assert.AreEqual(0, test.IndexOf(-2));
Assert.AreEqual(1, test.LastIndexOf(-2));
test.Add(0);
Assert.AreEqual(3, test.IndexOf(0));
Assert.AreEqual(4, test.LastIndexOf(0));
}
[TestMethod]
public void TestRangeFinding()
{
var test = new OrderedList<int> { 2 };
CollectionAssert.AreEqual(new[] { 2 }, test.WithinRange(0, 6));
CollectionAssert.AreEqual(new[] { 2 }, test.WithinRange(0, 2));
CollectionAssert.AreEqual(new[] { 2 }, test.WithinRange(2, 4));
CollectionAssert.AreEqual(new int[0], test.WithinRange(-6, 0));
CollectionAssert.AreEqual(new int[0], test.WithinRange(6, 8));
test = new OrderedList<int>();
CollectionAssert.AreEqual(new int[0], test.WithinRange(6, 8));
test = new OrderedList<int>{ -4, -2, 0 ,4, 6, 6 };
CollectionAssert.AreEqual(new[] { 0, 4 }, test.WithinRange(0, 4));
CollectionAssert.AreEqual(new[] { 0, 4 }, test.WithinRange(-1, 5));
CollectionAssert.AreEqual(new[] { 6, 6 }, test.WithinRange(6, 8));
CollectionAssert.AreEqual(new[] { 6, 6 }, test.WithinRange(5, 8));
CollectionAssert.AreEqual(new[] { -4, -2 }, test.WithinRange(-5, -1));
CollectionAssert.AreEqual(new[] { -4, }, test.WithinRange(-4, -3));
CollectionAssert.AreEqual(new int[0], test.WithinRange(-6, -5));
Assert.ThrowsException<ArgumentException>(() => test.WithinRange(6, 4));
}
}
Did you search for it? Generic SortedList and SortedList.
So I missed the duplicate part which make it a little bit harder I agree. But here is how I would solve it:
var sortedList = new SortedList<LayerColor, SortedList<Layer, Layer>>();
var redSortedList = new SortedList<Layer, Layer>();
// Add all layers associated with the color red
sortedList.Add(LayerColor.Red, redSortedList);
Will that work for you. Also, I would prefer to use linq but if you really want a sorted list my solution will most likely work.
Last try:) :
public class YourClass
{
private List<Layer> _layers;
public List<Layer> Layers
{
get
{
_layers = _layers.OrderBy(y => y.LayerColor).ThenBy(y => y.Name).ToList();
return _layers;
}
set
{
_layers = value;
}
}
}
Note that I'm writing directly in browser without testing it in VS (sitting on OS X), but you probably get the point.
You could use the regular List<T>, but call the Sort() method prior to displaying the list, and after new values are added. That should give you the functionality that you need. The performance will be good enough for this application.
Of course, you will have to define your own comparison for it to use, but that shouldn't be too much trouble.
If you don't have any hooks into the add event that can be used to sort the list, then you can wrap the list in a custom collection class as #Justin recommends.
If the sort is only for display purposes, let WPF handle it:
ICollectionView view = CollectionViewSource.GetDefaultView(Layers);
view.SortDescriptions.Add(new SortDescription("Color", ListSortDirection.Ascending);
view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending);
then just bind Layers to your UI ItemsControl.
You're on the right track. I would create a custom collection class that inherits from Collection. In this custom collection you can override on the on insert/on delete methods and sort your collection as items are added/removed from it.
using System.Linq, do:
from layer in layers
orderby layer.Color, layer.Name
select layer
Start by implementing the IComparable interface in Layer and declaring a CompareTo method. Then use a SortedList collection to store your object.
public class Layer : IComparable {
public int CompareTo(object obj) {
//return -1 if this is before obj, 0 is same, 1 is after.
}
}
You can use arraylist and do below linq query to sort them
ArrayList myList = new ArrayList();
Layer obj1 = new Layer();
obj1.Color = LayerColor.Red;
obj1.Name = "Layer1";
myList.Add(obj1);
Layer obj2 = new Layer();
obj2.Color = LayerColor.Green;
obj2.Name = "Layer2";
myList.Add(obj2);
Layer obj3 = new Layer();
obj3.Color = LayerColor.Blue;
obj3.Name = "Layer3";
myList.Add(obj3);
Layer obj4 = new Layer();
obj4.Color = LayerColor.Green;
obj4.Name = "Layer4";
myList.Add(obj4);
var mySortedList = myList.OfType<Layer>().OrderBy(l => l.Color)
.ThenBy(l => l.Name);