0

I am needing to order a list of objects based on one of its properties. I have found all kinds of answers but none order the way I need.The property I am trying to orderby is called "DrawingName". I need the list to be ordered as follows: "411000A","411000B","411000C","411000D","411000A","411000B","411000C","411000D"

instead I get: "411000A","411000A","411000B","411000B","411000C","411000C","411000D","411000D"

when I use the following code.

List<DrawingData> _DrawingList = new List<DrawingData>();

_DrawingList.Add(new DrawingData() { DrawingName = "411000D", DrawingQty = 1 });
_DrawingList.Add(new DrawingData() { DrawingName = "411000D", DrawingQty = 1 });
_DrawingList.Add(new DrawingData() { DrawingName = "411000A", DrawingQty = 1 });
_DrawingList.Add(new DrawingData() { DrawingName = "411000A", DrawingQty = 1 });        
_DrawingList.Add(new DrawingData() { DrawingName = "411000C", DrawingQty = 1 });
_DrawingList.Add(new DrawingData() { DrawingName = "411000C", DrawingQty = 1 });
_DrawingList.Add(new DrawingData() { DrawingName = "411000B", DrawingQty = 1 });
_DrawingList.Add(new DrawingData() { DrawingName = "411000B", DrawingQty = 1 });

_DrawingList.OrderBy(dn => dn.DrawingName);

var _DrawingListInOrder = _DrawingList.OrderBy(dwg => dwg.DrawingName);
Xaruth
  • 4,034
  • 3
  • 19
  • 26
James Morris
  • 353
  • 5
  • 20
  • 1
    Are the possible values for DrawingName a small, fixed set or pretty much anything? – BambooleanLogic Oct 04 '13 at 13:18
  • 7
    So you're saying that "411000A" should be less than "411000B", but also that "411000B" is less than "411000A"? Does not compute... Can you be more precise about the heuristics for determining the order? – Matthew Watson Oct 04 '13 at 13:19
  • 4
    Can you explain the logic behind the sort? How would the code "know" that you want it to go A,B,C,D and then back? Is it always A-D for example or could it be A-Z? Bit more detail needed before we can help fully. – Belogix Oct 04 '13 at 13:19
  • 3
    What about `D`? What if you don't have any `B`, just `A` and `C`? the order should be `ACAC`, then where should `D` be? – King King Oct 04 '13 at 13:19
  • 3
    I'm don't think Orderby or any of the built in ordering functionality is going to help you here since it is all based around the concept of comparing 2 object against each other for equality. Here you are ordering based on the larger set of data available. – Jace Rhea Oct 04 '13 at 13:19
  • 3
    And how do you differentiate between the two objects with `411000B`? Are they treated as the same or does one have higher order? – gitsitgo Oct 04 '13 at 13:21
  • 2
    What's the logic in the wanted sort ? If we can't understand it, computer can't do either ... – Xaruth Oct 04 '13 at 13:21
  • In what string this one should be sorted? "`"4A","4A","4B","4B","4C","4D"`" – AgentFire Oct 04 '13 at 13:24
  • Are you saying that you want to order your list alphabetically but if duplicate entries are found, other "similar" lists should be appended, containing those duplicates? – Andrei V Oct 04 '13 at 13:26
  • 1
    Furthermore, since you are adding each object one by one into your list, you may as well add them through your own order that you desire. Why add them in a jumbled order just to want to order them right after? – gitsitgo Oct 04 '13 at 13:28
  • Thanks for all the responses.To further explain my problem I have created a custom AutoCAD batch plotter add-in. After entering a job number all the files associated with that job are displayed. They can then choose the desired qty for each file. If the qty was 2 each for example the jobs were being plotted file#A, file#A,file#B,file#B,file#C,file#C etc.. then they were manually putting them in the correct order after they printed fileA,B,C,A,B,C etc... I hope this better explains the question. – James Morris Oct 04 '13 at 14:54

8 Answers8

3

Do one pass and calculate the number of occurrences of each DrawingName so far:

411000D 0x
411000D 1x
411000A 0x
411000A 1x
411000C 0x
411000C 1x
411000D 2x
411000B 0x
411000B 1x

Then you can sort them by the occurrence and alphabetically.

411000A 0x
411000B 0x
411000C 0x
411000D 0x
411000A 1x
411000B 1x
411000C 1x
411000D 1x
411000D 2x
Karel Frajták
  • 4,389
  • 1
  • 23
  • 34
  • 1
    this looks nice however the hard thing is **implementation** using `LINQ`. it's better if you provide code for that implementation. – King King Oct 04 '13 at 13:34
  • Yeah, sure, but it's better to push you in the right direction than actually doing the job for you. – Karel Frajták Oct 04 '13 at 15:15
2

Not saying this is the most efficient way to do it but it works:

List<DrawingData> _DrawingList = new List<DrawingData>();

_DrawingList.Add(new DrawingData() { DrawingName = "411000D", DrawingQty = 1 });
_DrawingList.Add(new DrawingData() { DrawingName = "411000D", DrawingQty = 1 });
_DrawingList.Add(new DrawingData() { DrawingName = "411000A", DrawingQty = 1 });
_DrawingList.Add(new DrawingData() { DrawingName = "411000A", DrawingQty = 1 });
_DrawingList.Add(new DrawingData() { DrawingName = "411000C", DrawingQty = 1 });
_DrawingList.Add(new DrawingData() { DrawingName = "411000C", DrawingQty = 1 });
_DrawingList.Add(new DrawingData() { DrawingName = "411000B", DrawingQty = 1 });
_DrawingList.Add(new DrawingData() { DrawingName = "411000B", DrawingQty = 1 });

var _WithIndex = _DrawingList.Select(x => new { DrawingData = x, Index = _DrawingList.Where(y => y.DrawingName == x.DrawingName).ToList().IndexOf(x) });
var _FinalOrder = _WithIndex.OrderBy(x => x.Index).ThenBy(x => x.DrawingData.DrawingName).Select(x => x.DrawingData);

Console.WriteLine("Final Sort:");
Console.WriteLine(string.Join("\n", _FinalOrder));

Console.ReadLine();

Get the index of each duplicated item, then sort on that index and then the name.

Made it a bit simpler. Can be a single LINQ statement:

var _FinalOrder = _DrawingList
    .Select(x => new
        {
            DrawingData = x,
            Index = _DrawingList.Where(y => y.DrawingName == x.DrawingName)
                                .ToList()
                                .IndexOf(x)
        })
    .OrderBy(x => x.Index)
    .ThenBy(x => x.DrawingData.DrawingName)
    .Select(x => x.DrawingData);
Richard Dalton
  • 35,513
  • 6
  • 73
  • 91
0

Assuming that this is used for relatively small amounts of data, the following pseudocode might meet your needs:

Take the list L of things to sort
Sort L normally (AABBCCDD order)
Create an empty list L2 and an empty set S
While L still has elements:
    Iterate over L
        If current element E is in S, ignore it
        Otherwise, add E to S, add it to the end of L2 and remove it from L
    Clear S

After this, L2 should have the elements in an ABCDABCD order.

BambooleanLogic
  • 7,530
  • 3
  • 30
  • 56
0

Try this:

using System;
using System.Collections.Generic;
using System.Linq;

class DrawingData {
    public string DrawingName{get;set;} 
    public int DrawingQty {get;set;}
}

class DrawingDataComparer : IEqualityComparer<DrawingData>
{

    public bool Equals(DrawingData d1, DrawingData d2)
    {
        return d1.DrawingName.Equals(d2.DrawingName);
    }


    public int GetHashCode(DrawingData d)
    {
        return d.DrawingName.GetHashCode();
    }

}

public class Test
{
    public static void Main()
    {
        List<DrawingData> _DrawingList = new List<DrawingData>();
        _DrawingList.Add(new DrawingData() { DrawingName = "411000D", DrawingQty = 1 });
        _DrawingList.Add(new DrawingData() { DrawingName = "411000D", DrawingQty = 1 });
        _DrawingList.Add(new DrawingData() { DrawingName = "411000A", DrawingQty = 1 });
        _DrawingList.Add(new DrawingData() { DrawingName = "411000A", DrawingQty = 1 });        
        _DrawingList.Add(new DrawingData() { DrawingName = "411000C", DrawingQty = 1 });
        _DrawingList.Add(new DrawingData() { DrawingName = "411000C", DrawingQty = 1 });
        _DrawingList.Add(new DrawingData() { DrawingName = "411000B", DrawingQty = 1 });
        _DrawingList.Add(new DrawingData() { DrawingName = "411000B", DrawingQty = 1 });

        _DrawingList = _DrawingList.OrderBy(c => c.DrawingName).ToList();
        var distinct = _DrawingList.Distinct(new DrawingDataComparer());
        var organized = distinct.Concat(_DrawingList.Except(distinct));
        foreach(DrawingData dd in organized)
            Console.WriteLine(dd.DrawingName);
    }
}
Ahmed KRAIEM
  • 10,267
  • 4
  • 30
  • 33
0

From what I understand of the requirement, you need each unique set ordered. So, take a unique set, order it, and keep it. Then remove the ones you just used and do it again. Keep going until you have no more unique sets to order.

List<DrawingData> ordered = new List<DrawingData>();
while (_DrawingList.Any())
{
    var temp = _DrawingList.Distinct().OrderBy(dd => dd.DrawingName, StringComparer.CurrentCultureIgnoreCase);
    ordered.AddRange(temp);
    foreach (var remove in temp)
    {
        _DrawingList.Remove(remove);
    }
}
_DrawingList = ordered;

In order for this to work, I had to override Equals and GetHashCode in DrawingData:

public override bool Equals(object obj)
{
    if (!(obj is DrawingData))
    {
        return base.Equals(obj);
    }
    else
    {
        return this.DrawingName.Equals((obj as DrawingData).DrawingName, StringComparison.OrdinalIgnoreCase);
    }
}

public override int GetHashCode()
{
    return DrawingName.GetHashCode();
}
zimdanen
  • 5,508
  • 7
  • 44
  • 89
0

Assuming you want to split the duplicates off into a separate list, then:

Firstly create a little class for comparing DrawingData objects:

sealed class Comparer: IComparer<DrawingData>
{
    public int Compare(DrawingData x, DrawingData y)
    {
        // NOTE: Assumes DrawingName, x and y are never null.
        return x.DrawingName.CompareTo(y.DrawingName);
    }
}

Then assuming you have a populated list as in your example, List<DrawingData> _DrawingList, split the duplicate items so you have two separate lists:

var list1 = new SortedSet<DrawingData>(new Comparer());
var list2 = new SortedSet<DrawingData>(new Comparer());

foreach (var drawing in _DrawingList)
    if (!list1.Add(drawing))
        list2.Add(drawing);

Then convert list1 and list2 to actual lists if needed:

var l1 = list1.ToList();
var l2 = list2.ToList();
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
0

You're looking for a grouping, followed by a transpose:

string[] data = 
{
    "411000D", 
    "411000D", 
    "411000A",
    "411000A",        
    "411000C",
    "411000C",
    "411000B",
    "411000B"
};

var grouped = data.GroupBy(d => d).OrderBy(g => g.Key).ToList().Transpose();
String.Join(",", grouped.SelectMany(f => f));

results in

411000A,411000B,411000C,411000D,411000A,411000B,411000C,411000D

given the following extension method named Transpose:

public static IEnumerable<IEnumerable<T>> Transpose<T>(this IEnumerable<IEnumerable<T>> @this) 
{
    var enumerators = @this.Select(t => t.GetEnumerator())
                           .Where(e => e.MoveNext()).ToList();

    while (enumerators.Any()) {
        yield return enumerators.Select(e => e.Current);
        enumerators = enumerators.Where(e => e.MoveNext()).ToList();
    }
}

An alternative approach, implementing Karel Frajtak's suggestion:

var result = data.GroupBy(d => d)
                 .SelectMany(g => g.Select((item, indexer) => new { item, indexer}))
                 .OrderBy(a => a.indexer)
                 .ThenBy(a => a.item)
                 .Select(a => a.item);
Community
  • 1
  • 1
sloth
  • 99,095
  • 21
  • 171
  • 219
  • Your `Transpose` method won't work. Your queries cause side effects when executed (the `MoveNext` in the where causes a side effect) and you iterate each query twice (once for `Any` and once for the `yield return`). You'll need to materialize the sequences into lists (or similar) to avoid that. – Servy Oct 04 '13 at 13:56
  • Then you're iterating every single underlying sequence 2*N times, where N is the size of the largest sub-sequence. That's *horribly* inefficient, not to mention causes serious problems in the event that the underlying sequence, or any of the underlying sub-sequences, don't generate the same results when re-iterated. It also means you're grouping and ordering the items 2*N times. Materializing the query would make it *much*, *much* faster. – Servy Oct 04 '13 at 14:26
  • That helps, but it is still seriously flawed, and will still perform *horribly* when the size of sub-groups isn't trivially small. To find the nth item in each group you re-iterate the sequence, from the start, to get the nth item. Every single sub-group is iterated 2 * N^2 times. When `N` is 2 that's not so bad, but when N is even 10 it'll be *horribly* slow. If it gets up higher than that it'll quickly become almost incomputable. – Servy Oct 04 '13 at 14:45
0

Though not optimal, a recursive extension method on IEnumerable> might be fun to use, and useful in some more general case outside grouping :

public static class GroupExtender
{
    public static IEnumerable<T> Mix<T>(this IEnumerable<IEnumerable<T>> groups)
    {
        // enumerate once
        var enumerable = groups as IList<IEnumerable<T>> ?? groups.ToList(); 
        //stop case
        if (!(enumerable.Any(g=>g.Any())))
            return new List<T>();
        // get first elements, iterate over the IEnumerable trimmed of these
        return enumerable
                .SelectMany(g => g.Take(1))
                .Concat(enumerable.Select(g => g.Skip(1)).Mix());
    }
}

Obtaining the "ordered" items would then be :

var _DrawingListInOrder = 
      _DrawingList.GroupBy(x => x.DrawingName).OrderBy(g => g.Key).Mix();
jbl
  • 15,179
  • 3
  • 34
  • 101