我有一个简单的List<T>
,我试图对它进行排序。但是,列表中的项目在可比性方面并不是全部可传递的,例如,对于例如我的List<T>
看起来像是:
A
B
C
D
E
其中 A&gt; B 和 B&gt; C 但 C&gt; A 的。也可以像 A&gt;那样具有圆形的伟大。 B , B&gt; C , C&gt; D 但 D&gt; ,即它不必总是一组3 。我想要的是在给定的List<T>
中找到所有圆形伟大的群体。例如,假设 A&gt; B> C> A 和 A&gt; B> C> D> 是上面两个圆形组,我的输出应该看起来像:
List<List<T>> circulars = [[A, B, C, A], [A, B, C, D, A]]
或
List<List<T>> circulars = [[A, B, C], [A, B, C, D]]
// but in this case I do not want duplicates in the output.
// For e.g., the output shouldn't have both [B, C, A] and [A, B, C]
// since both the groups refer to the same set of circular items A, B & C
// as B > C > A > B is also true.
// But [B, A, C] is a different group (though nothing circular about it)
任何一个对我都没问题。我更喜欢小型(linquish)解决方案,但这看起来并不像最初看起来那么容易。可能是我错过了一些非常简单的事情。
这是体育分析的一部分,其中一个球员/球队将比另一个更强,反过来将比另一个更强,但最后一个将比第一个更强。我无法透露更多信息,但让我举一个体育运动的例子,特别是在网球和国际象棋中,个人比赛导致了这种情况。例如,就头对头而言,克拉姆尼克带领卡斯帕罗夫和卡斯帕罗夫领导卡尔波夫,但卡尔波夫领导克拉姆尼克。或者另一个例如,费德勒带领达维登科,达维登科领先纳达尔,但纳达尔领先费德勒。
我的班级看起来像这样:
class Player : IComparable<Player>
{
// logic
}
这就是我的尝试:
首先生成所有可能的集合项排列,最小组大小为3.如[ABC],[A,C,B] ....,[A,B,C,D],[ A,B,D,C] ....等(这很慢)
然后浏览整个子组并检查模式。就像是否存在A&gt;的情况一样B> C> D(这个速度相当慢,但我很好)
最后浏览整个子组以删除重复的组,如[A,B,C]和[B,C,A]等。
代码:
var players = [.....]; //all the players in the collection
// first generate all the permutations possible in the list from size 3
// to players.Count
var circulars = Enumerable.Range(3, players.Count - 3 + 1)
.Select(x => players.Permutations(x))
.SelectMany(x => x)
.Select(x => x.ToList())
// then check in the each sublists if a pattern like A > B > C > A is
// generated vv this is the player comparison
.Where(l => l.Zip(l.Skip(1), (p1, p2) => new { p1, p2 }).All(x => x.p1 > x.p2) && l.First() < l.Last())
// then remove the duplicate lists using special comparer
.Distinct(new CircularComparer<Player>())
.ToList();
public static IEnumerable<IEnumerable<T>> Permutations<T>(this IEnumerable<T> list, int length)
{
if (length == 1)
return list.Select(t => new[] { t });
return Permutations(list, length - 1)
.SelectMany(t => list.Where(e => !t.Contains(e)), (t1, t2) => t1.Concat(new[] { t2 }));
}
class CircularComparer<T> : IEqualityComparer<ICollection<T>>
{
public bool Equals(ICollection<T> x, ICollection<T> y)
{
if (x.Count != y.Count)
return false;
return Enumerable.Range(1, x.Count)
.Any(i => x.SequenceEqual(y.Skip(i).Concat(y.Take(i))));
}
public int GetHashCode(ICollection<T> obj)
{
return 0;
}
}
这种方法的问题在于它非常慢。对于大约10个项目的集合,必须产生的排列是巨大的(接近100万个项目)。有没有更合理有效的方法?我不是追求最快的代码。这里有更好的递归方法吗?闻起来像。
答案 0 :(得分:2)
情景......
[A,B,C,D,E]
其中A&gt; B,B> C,C> D,C> A,D&gt;甲
...可以使用A -> B
表示A > B
的惯例表示为有向图:
所以问题基本上是“如何在有向图中找到周期?”
要解决此问题,您可以使用Shared Web Credentials。我建议您查找此算法的一个很好的实现并将其应用到您的场景中。
答案 1 :(得分:1)
有许多用于枚举N个对象的排列的方法,使得可以从枚举中的索引有效地获得每个排列。一个如my tutorial on CUDOFY using the Travelling Salesman problem的摘录:
/// <summary>Amended algorithm after SpaceRat (see Remarks):
/// Don't <b>Divide</b> when you can <b>Multiply</b>!</summary>
/// <seealso cref="http://www.daniweb.com/software-development/cpp/code/274075/all-permutations-non-recursive"/>
/// <remarks>Final loop iteration unneeded, as element [0] only swaps with itself.</remarks>
[Cudafy]
public static float PathFromRoutePermutation(GThread thread,
long permutation, int[,] path) {
for (int city = 0; city < _cities; city++) { path[city, thread.threadIdx.x] = city; }
var divisor = 1L;
for (int city = _cities; city > 1L; /* decrement in loop body */) {
var dest = (int)((permutation / divisor) % city);
divisor *= city;
city--;
var swap = path[dest, thread.threadIdx.x];
path[dest, thread.threadIdx.x] = path[city, thread.threadIdx.x];
path[city, thread.threadIdx.x] = swap;
}
return 0;
}
#endregion
}
从这一点开始,人们可以很容易地并行地用圆形伟大来识别排列。首先可以使用CPU上的多个内核来提高性能,然后再使用GPU上可用的内核。经过反复调整旅行商问题 通过这种方式,我使用我的GPU将performance for the 11 cities case从超过14秒(仅使用CPU)提高到约0.25秒;改善50倍。
当然,您的里程将根据问题的其他方面以及您的硬件而有所不同。
答案 2 :(得分:0)
我可以依靠递归来大幅提升性能。我不是事先生成可能的序列的整个排列,而是现在通过集合来查找循环。为了帮助我,我创建了自己的循环引用(更大和更小的项目),以便我可以遍历。代码有点长。
这是基本的想法:
我创建了一个基本接口ICyclic<T>
,它必须由Player
类实现。
我遍历集合并分配较小和较大的项目(在Prepare
方法中)。
我忽略了真正的错误(即集合中没有较少的项目)和非常好的(即集合中没有更多的项目)以避免无限递归并通常提高性能。绝对最好的和最差的不会对周期做出贡献。全部用Prepare
方法完成。
现在每件商品都有一个小于商品的商品集合。并且集合中的项目将拥有自己的更糟糕项目的集合。等等。这是我递归遍历的路径。
在每一点上,将最后一项与访问路径中的第一项进行比较,以检测周期。
将周期添加到HashSet<T>
以避免重复。定义了相等比较器以检测等效的循环列表。
代码:
public interface ICyclic<T> : IComparable<T>
{
ISet<T> Worse { get; set; }
ISet<T> Better { get; set; }
}
public static ISet<IList<T>> Cycles<T>(this ISet<T> input) where T : ICyclic<T>
{
input = input.ToHashSet();
Prepare(input);
var output = new HashSet<IList<T>>(new CircleEqualityComparer<T>());
foreach (var item in input)
{
bool detected;
Visit(item, new List<T> { item }, item.Worse, output, out detected);
}
return output;
}
static void Prepare<T>(ISet<T> input) where T : ICyclic<T>
{
foreach (var item in input)
{
item.Worse = input.Where(t => t.CompareTo(item) < 0).ToHashSet();
item.Better = input.Where(t => t.CompareTo(item) > 0).ToHashSet();
}
Action<Func<T, ISet<T>>> exceptionsRemover = x =>
{
var exceptions = new HashSet<T>();
foreach (var item in input.OrderBy(t => x(t).Count))
{
x(item).ExceptWith(exceptions);
if (!x(item).Any())
exceptions.Add(item);
}
input.ExceptWith(exceptions);
};
exceptionsRemover(t => t.Worse);
exceptionsRemover(t => t.Better);
}
static void Visit<T>(T item, List<T> visited, ISet<T> worse, ISet<IList<T>> output,
out bool detected) where T : ICyclic<T>
{
detected = false;
foreach (var bad in worse)
{
Func<T, T, bool> comparer = (t1, t2) => t1.CompareTo(t2) > 0;
if (comparer(visited.Last(), visited.First()))
{
detected = true;
var cycle = visited.ToList();
output.Add(cycle);
}
if (visited.Contains(bad))
{
var cycle = visited.SkipWhile(x => !x.Equals(bad)).ToList();
if (cycle.Count >= 3)
{
detected = true;
output.Add(cycle);
}
continue;
}
if (bad.Equals(item) || comparer(bad, visited.Last()))
continue;
visited.Add(bad);
Visit(item, visited, bad.Worse, output, out detected);
if (detected)
visited.Remove(bad);
}
}
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
{
return new HashSet<T>(source);
}
public class CircleEqualityComparer<T> : IEqualityComparer<ICollection<T>>
{
public bool Equals(ICollection<T> x, ICollection<T> y)
{
if (x.Count != y.Count)
return false;
return Enumerable.Range(1, x.Count)
.Any(i => x.SequenceEqual(y.Skip(i).Concat(y.Take(i))));
}
public int GetHashCode(ICollection<T> obj)
{
return unchecked(obj.Aggregate(0, (x, y) => x + y.GetHashCode()));
}
}
原始回答 (来自OP)
在正面,这更简洁。此外,由于它不依赖于递归,因此不需要ICyclic<T>
约束,任何IComparable<T>
都应该有效。 在负面,它在1月的糖蜜中很慢。
public static IEnumerable<ICollection<T>> Cycles<T>(this ISet<T> input) where T : IComparable<T>
{
if (input.Count < 3)
return Enumerable.Empty<ICollection<T>>();
Func<T, T, bool> comparer = (t1, t2) => t1.CompareTo(t2) > 0;
return Enumerable.Range(3, input.Count - 3 + 1)
.Select(x => input.Permutations(x))
.SelectMany(x => x)
.Select(x => x.ToList())
.Where(l => l.Zip(l.Skip(1), (t1, t2) => new { t1, t2 }).All(x => comparer(x.t1, x.t2))
&& comparer(l.Last(), l.First()))
.Distinct(new CircleEqualityComparer<T>());
}
public static IEnumerable<IEnumerable<T>> Permutations<T>(this IEnumerable<T> list, int length)
{
if (length == 1)
return list.Select(t => new[] { t });
return Permutations(list, length - 1)
.SelectMany(t => list.Where(e => !t.Contains(e)), (t1, t2) => t1.Concat(new[] { t2 }));
}
public class CircleEqualityComparer<T> : IEqualityComparer<ICollection<T>>
{
public bool Equals(ICollection<T> x, ICollection<T> y)
{
if (x.Count != y.Count)
return false;
return Enumerable.Range(1, x.Count)
.Any(i => x.SequenceEqual(y.Skip(i).Concat(y.Take(i))));
}
public int GetHashCode(ICollection<T> obj)
{
return unchecked(obj.Aggregate(0, (x, y) => x + y.GetHashCode()));
}
}
很少有事情需要注意:
我使用了ISet<T>
和HashSet<T>
来代替更传统的List<T>
,但这只是为了让意图更清晰,没有重复的项目是允许。列表应该可以正常工作。
.NET确实没有插入顺序保留集(即不允许重复),因此必须在许多地方使用List<T>
。一组可能会略微提高性能,但更重要的是可以互换地使用set和list导致混淆。
第一种方法在第二种方法上提供了100次的性能跳跃。
可以使用Prepare
方法加快第二种方法。逻辑也存在,即收集中较小的成员意味着产生较少的排列。但仍然非常非常痛苦。
我已经使方法通用,但解决方案可以更通用。例如,在我的情况下,基于某个比较逻辑来检测循环。这可以作为参数传递,即,集合中的项目不需要仅仅是可比较的,它可以是任何顶点确定逻辑。但是这留给了读者&#39;锻炼。
在我的代码(两个示例)中,仅考虑最小尺寸3的循环,即,像A>的循环; B> C>答:它没有考虑像A&gt;这样的周期。 B,B>一种情况。如果您需要它,请将代码中3
的所有实例更改为您喜欢的任何内容。更好的是将它传递给函数。