1

The dictionary ints I originally generated randomly within a range between 1 - 100 using:

var lstNumbers = Enumerable.Range(1, 100).OrderBy(n => Guid.NewGuid)
                           .GetHashCode()).ToList();

The dictionary is created and used to assign ColorType Values to each of the numbers in sequence Red, Yellow, White, Red, Yellow, White, etc., etc. to all of the numbers in the list:

var startColor = ColorType.Red;
var map = new Dictionary<int, ColorType>();
foreach(var number in lstNumbers) {
  // First color for assignment
  map[number] = startColor.Next();
  // Go to the next color
  startColor = startColor.Next();
}

I then Remove items a few different ways:

foreach(KeyValuePair<int, ColorType> entry in map.ToList()) {
  if (entry.Key % 2 == 0 && entry.Value == ColorType.Red) {
    map.Remove(entry.Key);
  }
  if (entry.Key % 2 == 1 && entry.Value == ColorType.Yellow) {
    map.Remove(entry.Key);
  }
  if (entry.Key % 3 == 0 && entry.Value == ColorType.White) {
    map.Remove(entry.Key);
  }
}

Then I sort the numbers in ascending order:

foreach(var number in map.OrderBy(i => i.Key)) {
  Console.WriteLine(number);
}

Console.ReadLine();

So my list pretty much looks like this now:

[53, Red]
[54, Yellow]
[55, White]
[56, Yellow]
[61, White]
[62, White]
[64, Yellow]
[65, Red]
ect., ect.

Now I need to sort the final list from this dictionary by value with White results being at the top, Yellow in the middle, and red at the bottom. Red < Yellow < White, then display.

I tried this OrderBy but I may have not implemented it correctly or it might not work for my scenario because I get an error:

The == operator can not be applied to operands of 'ColorType' and 'string'.

foreach(var color in map.OrderBy(c => c.Value == "White" ? 0 : c.Value == "Yellow" ? 1 : 2)) {
  Console.WriteLine(color);
}

I tried using IComparer, but I ran into a problem because I am using a custom type in my dictionary that I use for the colors:

public class CustomerComparer : IComparer<KeyValuePair<int, ColorType>> {
  private List<string> orderedColors = new List<string>() { "White", "Yellow", "Red" };

  public int Compare(KeyValuePair<int, ColorType> str1, KeyValuePair<int, ColorType> str2) {
    return orderedColors.IndexOf(str1.Value) - orderedColors.IndexOf(str2.Value);
  }
}

var sorted = map.OrderBy(x => x, new CustomerComparer());

foreach(var entry in sorted) {
  Console.WriteLine("{0}: {1}", entry.Key, entry.Value);
}
Console.ReadKey();

I'm guessing there is a better way of doing this, another way, or I am missing something entirely.

Racil Hilan
  • 24,690
  • 13
  • 50
  • 55
  • 1
    What about `colors.OrderBy(c => c.Value != ColorType.White).ThenBy(c => c.Value != ColorType.Yellow).ThenBy(c => c.Value != ColorType.Red)` –  Apr 07 '18 at 21:38
  • Which `ColorType`? ? It exists in several libraries. Provide the full qualification for it. – Racil Hilan Apr 07 '18 at 21:47
  • I would use a single int16 for your dictionary key. The least significant 7 bits would be a number between 0 to 100. Bits 8 & 9 would be a two bit color code with white being : 00, yellow = 01, red being 10. Then when you sort in reverse order red will be first, then yellow, and finally white. – jdweng Apr 07 '18 at 21:57
  • @RacilHilan ColorType is a custom data type – Justeasy StackAttacka Apr 07 '18 at 22:02
  • 2
    Never use a guid as a source of randomness; it is not *guaranteed* to be random; it is guaranteed to be *unique*, and those are different properties. Second, never use a hash code for anything except balancing a hash table. You've got two bad practices in the same expression. If you want to shuffle a list then either sort by something *guaranteed* to be random, or implement a shuffle. – Eric Lippert Apr 07 '18 at 22:58
  • @Eric Yea thanks I know it's more of a shuffle then being random. – Justeasy StackAttacka Apr 07 '18 at 23:04
  • @ Eric Any suggestions of any articles or links that outline how to guarantee randomness when shuffling dictionary keys? – Justeasy StackAttacka Apr 07 '18 at 23:26
  • 1
    @JusteasyStackAttacka: Maybe just read over https://stackoverflow.com/q/375351/18192 ? Or just use Leopold Bushkin's [RandomSubset](https://github.com/morelinq/MoreLINQ/blob/master/MoreLinq/RandomSubset.cs) class (from Jon Skeet's MoreLinq library). It is documented as providing results in random order, so just set size to the length of your list. – Brian Apr 09 '18 at 13:30
  • 1
    If you need a sequence of pseudo-random numbers then just write `static IEnumerable PseudorandomNumbers() { Random r = new Random(); while(true) yield return r.NextDouble(); }` and now you can say `Enumerable.Range(1,100).Zip(PseudorandomNumbers(), (i, d) => new { i, d }).OrderBy(p=>p.d).Select(p=>p.i).ToList()` and you're done. If you need crypto strength randomness, do the same thing but with an infinite sequence of crypto strength numbers. – Eric Lippert Apr 09 '18 at 17:12

2 Answers2

2

I don't think you need a custom comparer here. You can chain orderings with .ThenBy.

You can write

 var sorted = map.OrderBy(c => c.Value != ColorType.White)
                .ThenBy(c => c.Value != ColorType.Yellow)
                .ThenBy(c => c.Value != ColorType.Red);

Note the != operator -- you are sorting by booleans, so false comes before true.

  • 2
    This is a good few line solution and I've up voted it. I should point out though if the list is huge we could probably make it more efficient with making the initial key value naturally sort based on its value with bits as @jdweng mentioned – angelsix Apr 07 '18 at 22:17
2

As the error that you get indicates, you tried to compare ColorType to String. You need to make the two side the same, either both ColorType:

foreach (var color in map.OrderBy(c => c.Value == ColorType.White ?
                                       0 : c.Value == ColorType.Yellow ? 1 : 2)) {
    Console.WriteLine(color);
}

or both String. You said that the ColorType was a custom data type, so I don't know if ToString() will return the name of the color:

foreach (var color in map.OrderBy(c => c.Value.ToString() == "White" ?
                                       0 : c.Value.ToString() == "Yellow" ? 1 : 2)) {
    Console.WriteLine(color);
}

For example, if you use the System.Drawing.Color class, the ToString() will not work because it returns something like Color [Red]. Instead you can use the Name property:

foreach (var color in map.OrderBy(c => c.Value.Name == "White" ?
                                       0 : c.Value.Name == "Yellow" ? 1 : 2)) {
    Console.WriteLine(color);
}

So I don't know if your ColorType class has a similar property that returns the color name, or if the ToString() will do the job.

Racil Hilan
  • 24,690
  • 13
  • 50
  • 55
  • Wow ok Yea I see it in the first code block. I should have not been using strings. Thanks for pointing that out! Now I just need to split up how the two Writelines Read out so there is a line separating them. I thought `break;` would work but it didn't. – Justeasy StackAttacka Apr 07 '18 at 23:01