最均匀地将字母表中的字母按顺序排列

本文关键字:顺序 排列 字母表 | 更新日期: 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));