最均匀地将字母表中的字母按顺序排列
本文关键字:顺序 排列 字母表 | 更新日期: 2023-09-27 18:15:54
我想知道是否有一种甜蜜的方式,我可以在LINQ或其他东西中做到这一点,但我正试图将字母表的字母均匀地分布在X部分,其中X是一个整数> 0 &&& lt; = 26。例如,以下是一些可能的输出:
- X = 1: 1分区26
- X = 2: 2个分区13
- x = 3:29分区和一个8分区
- 等等…
最终我不希望有任何分区最终没有得到至少一个,我的目标是让它们实现最均匀的分布,分区大小之间的差异范围尽可能小。
这是我最初尝试的代码:
char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
int pieces = (int)Math.Round((double)alphabet.Count() / numberOfParts);
for (int i = 0; i < numberOfParts.Count; ++i) {
char[] subset = i == numberOfParts.Count - 1 ? alphabet.Skip(i * pieces).ToArray()
: alphabet.Skip(i * pieces).Take(pieces).ToArray();
... // more code following
这似乎工作得很好,但我在测试中意识到,有一个问题,当X是10。根据这个逻辑,我得到8组3和1组2,留下第10组0,这是不好的,因为我要的是最均匀的分布。
在这种情况下,10的最理想分布是6组3和4组2。对如何实现这一点有什么想法吗?
一般来说,实现逻辑的最简单方法是使用模运算符%。熟悉这个算子;它在有帮助的情况下非常有用。有许多方法可以编写实际代码来分配字母,使用数组或不使用您希望的方法等,但这个简短的表达式应该给您一个想法:
"ABCDEFGHIJKLMNOPQRSTUVWXYZ".IndexOf(letter) % partitionCount
这个表达式给出了存放大写字母的分区的从零开始的索引。字符串只是为了方便而显示,但也可以是数组或其他表示字母表的方式。您可以循环遍历字母表,使用类似于上面的逻辑来选择存放每个字母的位置。在哪里放置逻辑由你决定:在循环中,在方法中,等等。
模运算没有什么神奇之处;它只是在达到可用数字集的末尾后"绕着"。我们都在除法中遇到过这种情况;%运算符实际上就是给出一个除法余数。现在您已经理解了%操作符的作用,您可以很容易地编写自己的代码来做同样的事情,用任何语言都可以。
把所有这些放在一起,您可以编写一个像这样的实用程序、类或扩展方法——注意计算余数的%,并且简单的整数除法会丢弃它:
/// <summary>
/// Returns partition sized which are as close as possible to equal while using the indicated total size available, with any extra distributed to the front
/// </summary>
/// <param name="totalSize">The total number of elements to partition</param>
/// <param name="partitionCount">The number of partitions to size</param>
/// <param name="remainderAtFront">If true, any remainder will be distributed linearly starting at the beginning; if false, backwards from the end</param>
/// <returns>An int[] containing the partition sizes</returns>
public static int[] GetEqualizedPartitionSizes(int totalSize, int partitionCount, bool remainderAtFront = true)
{
if (totalSize < 1)
throw new ArgumentException("Cannot partition a non-positive number (" + totalSize + ")");
else if (partitionCount < 1)
throw new ArgumentException("Invalid partition count (" + partitionCount + ")");
else if (totalSize < partitionCount)
throw new ArgumentException("Cannot partition " + totalSize + " elements into " + partitionCount + " partitions");
int[] partitionSizes = new int[partitionCount];
int basePartitionSize = totalSize / partitionCount;
int remainder = totalSize % partitionCount;
int remainderPartitionSize = basePartitionSize + 1;
int x;
if (remainderAtFront)
{
for (x = 0; x < remainder; x++)
partitionSizes[x] = remainderPartitionSize;
for (x = remainder; x < partitionCount; x++)
partitionSizes[x] = basePartitionSize;
}
else
{
for (x = 0; x < partitionCount - remainder; x++)
partitionSizes[x] = basePartitionSize;
for (x = partitionCount - remainder; x < partitionCount; x++)
partitionSizes[x] = remainderPartitionSize;
}
return partitionSizes;
}
我觉得实现这一点的最简单方法是在每个字母上执行轮询分发。循环遍历字母表中的每个字母并添加,然后重复。有一个运行计数,决定你将把你的项目放在哪个字母,然后当它达到>26,重置回0!
我在一个应用中做了这样的事情我必须把东西分组分发
var numberOfPartitions = GetNumberOfPartitions();
var numberOfElements = GetNumberOfElements();
while (alphabet.Any())
{
var elementsInCurrentPartition = Math.Ceil((double)numberOfPartitions / numberOfElements)
for (int i = 0; i < elementsInCurrentPartition; i++)
{
//fill your partition one element at a time and remove the element from the alphabet
numberOfElements--;
}
numberOfPartitions--;
}
这不会给你你所期望的确切结果(即10个分区的理想结果),但它非常接近。
注。这没有测试:)
我现在已经测试了一个伪代码算法:
Double count = alphabet.Count()
Double exact = count / numberOfParts
Double last = 0.0
Do Until last >= count
Double next = last + exact
ranges.Add alphabet, from:=Round(last), to:=Round(next)
last = next
Loop
ranges.Add
可以忽略空范围:-)
这是一个LinqPad VB。
在Linq中应该是这样的
Double count = alphabet.Count();
Double exact = count / numberOfParts;
var partitions = Enumerable.Range(0, numberOfParts + 1).Select(n => Round((Double)n * exact));
这是一个LinqPad VB。
(对不起,手机)
首先,您需要类似于批处理方法的东西:
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int groupSize)
{
var tempSource = source.Select(n => n);
while (tempSource.Any())
{
yield return tempSource.Take(groupSize);
tempSource = tempSource.Skip(groupSize);
}
}
然后,像这样调用它:
var result = alphabet.Batch((int)Math.Ceiling(x / 26.0));