按名称排序,然后按一些布尔关系分组? (。净)

时间:2013-09-21 16:23:29

标签: c# .net arrays linq sorting

我有一个命名对象列表:

class NamedObject {
    public string name;
    public int value;
    public NamedObject(string name, int value) {
        this.name = name;
        this.value = value;
    }
}

...

public static bool HasRelationship(NamedObject a, NamedObject b) {
    return a.value == b.value;
}

...

var objs = new List<NamedObject>();
objs.Add(new NamedObject("D", 1));
objs.Add(new NamedObject("Z", 2));
objs.Add(new NamedObject("Y", 3));
objs.Add(new NamedObject("A", 2));
objs.Add(new NamedObject("C", 1));
objs.Add(new NamedObject("Z", 1));

我希望按名称排序,然后按布尔关系进行子排序。出于此示例的目的,布尔关系为a.value == b.value

输出列表:

  • A(2)
  • Z(2)
  • C(1)
  • D(1)
  • Z(1)
  • Y(3)

因此按名称排序,按布尔关系分组,按名称排序子组。

修改

以上是实际排序的简化,在我的应用程序中,HasRelationship函数确定两个方向是否具有对称性。命名方向使它们在编辑器界面中以逻辑顺序出现。

这是一个可视化:

http://pbrd.co/16okFxp

4 个答案:

答案 0 :(得分:2)

我对这个问题感到困惑,我会尽量保持清醒。

首先,您似乎想要按名称对NamedObjects进行排序,这是一个简单明了的部分。订单应该做的工作。

然后你想通过基于一对NamedObjects的任意谓词来分配它。我认为这是引起混乱的问题。

您提供的谓词确定NamedObjects对的属性,因此现在您正在处理对。这个问题没有独特的答案。

我理解你想要通过谓词来分区对,但你必须明白,对于布尔分区,你只有两个分区(关系是否为真),并且在分区内没有保证的值顺序。

所以最多你可以得到(按照第一个任期的名称排序):

  • 对(A,Z)(真)
  • 对(A,C)(假)
  • 对(A,D)(假)
  • ...
  • 对(C,d)(真)
  • ...

关键是你不能通过配对关系来订购而不会暗中处理配对。所以,为了给你一个答案我会假设:

  • 关系可能不对称
  • 您希望按第一对名称
  • 排序

有了这个背景,答案可能就是。首先得到配对。

var namedPairs = namedObjects.SelectMany(outerNamedObject =>
    namedObjects.Select(innerNamedObject => new
        {
            First = outerNamedObject,
            Second = innerNamedObject
        }));

然后我们进行分组

var partitionedNamedPairs = namedPairs.GroupBy(pair => 
    HasRelationship(pair.First, pair.Second));

之后,按第一个术语名称排序,然后按组密钥(关系分区)

排序
var result = partitionedNamedPairs.SelectMany(
         grouping => grouping.Select(pair => new { pair, key = grouping.Key }))
     .OrderBy(keyedPair => keyedPair.pair.First.name)
     .ThenBy(keyedPair => keyedPair.key);

然后您可以使用select来删除该对的第二项,但我没有看到这一点,因为您提供的谓词是二进制的。

答案 1 :(得分:1)

我认为你应该自己加入你的列表,因为你的 HasRelationship 方法需要两个对象。

var result = objs.OrderBy(x => x.name)
            .Join(objs, _ => true, _ => true, (l, r) => new { l, r, rel = HasRelationship(l, r) })
            .Where(x => x.rel)
            .SelectMany(x=>new []{x.l,x.r})
            .Distinct()
            .ToList();

虽然这会返回您期望的列表,但我不能说我明白了您的要求。

答案 2 :(得分:1)

以下解决方案已得到充分评论,希望能帮助此问题的未来读者理解所需的分类过程。

@QtX的答案很简洁,虽然看起来人们很难理解我实际要求的内容,但对那些家伙感到抱歉!

用法示例:

var sortedObjs = objs.SortAndGroupByRelationship(obj => obj.name, HasRelationship);

排序和分组的扩展方法:

public static IEnumerable<T> SortAndGroupByRelationship<T, TKey>(this IEnumerable<T> objs, Func<T, TKey> keySelector, Func<T, T, bool> relationship) where TKey : IComparable<TKey> {
    // Group items which are related.
    var groups = new List<List<T>>();
    foreach (var obj in objs) {
        bool grouped = false;

        // Attempt to place named object into an existing group.
        foreach (var group in groups)
            if (relationship(obj, group[0])) {
                group.Add(obj);
                grouped = true;
                break;
            }

        // Create new group for named object.
        if (!grouped) {
            var newGroup = new List<T>();
            newGroup.Add(obj);
            groups.Add(newGroup);
        }
    }

    // Sort objects within each group by name.
    foreach (var group in groups)
        group.Sort( (a, b) => keySelector(a).CompareTo(keySelector(b)) );

    // Sort groups by name.
    groups.Sort( (a, b) => keySelector(a[0]).CompareTo(keySelector(b[0])) );

    // Flatten groups into resulting array.
    var sortedList = new List<T>();
    foreach (var group in groups)
        sortedList.AddRange(group);
    return sortedList;
}

答案 3 :(得分:0)

var sorted = objs.GroupBy(x => x.value, (k, g) => g.OrderBy(x => x.name))
                 .OrderBy(g => g.First().name)
                 .SelectMany(g => g);

在不使用HasRelationship方法的情况下准确返回您想要的内容。