linq 将列表拆分为n个相等的部分

l3zydbqr  于 2022-12-06  发布在  其他
关注(0)|答案(3)|浏览(262)

给定一个sorted list和一个变量n,我想把list分解成n部分,对于n = 3,,我期望有三个lists,最后一个取overflow
我期望:0,1,2,3,4,56,7,8,9,10,1112,13,14,15,16,17
如果list中的items的个数不是divisiblen,那么就把溢出的(mod n)放到最后的list中。
这不起作用:

static class Program
{
    static void Main(string[] args)
    {
        var input = new List<double>();
        for (int k = 0; k < 18; ++k)
        {
            input.Add(k);
        }
        var result = input.Split(3);
        
        foreach (var resul in result)
        {
            foreach (var res in resul)
            {
                Console.WriteLine(res);
            }
        }
    }
}

static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
        int i = 0;
        var splits = from item in list
                     group item by i++ % parts into part
                     select part.AsEnumerable();
        return splits;
    }
}
eeq64g8w

eeq64g8w1#

我认为您会从Linq的.Chunk()方法中受益。
如果您首先计算有多少部分将包含相等的项目计数,您可以将listyield return各分块,然后将yield return分块list的剩余部分(如果list不能被n整除)。
正如Enigmativity所指出的,list应该具体化为ICollection<T>以避免可能的多重枚举。具体化可以通过尝试将list强制转换为ICollection<T>来获得,如果不成功,则回退到调用list.ToList()
因此,扩展方法的一个可能实现是:

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
{
    var collection = list is ICollection<T> c
        ? c
        : list.ToList();
    
    var itemCount = collection.Count;
    
    // return all items if source list is too short to split up
    if (itemCount < parts)
    {
        yield return collection;
        yield break;
    }
    
    var itemsInEachChunk = itemCount / parts;
    
    var chunks = itemCount % parts == 0
        ? parts
        : parts - 1;
    
    var itemsToChunk = chunks * itemsInEachChunk;
    
    foreach (var chunk in collection.Take(itemsToChunk).Chunk(itemsInEachChunk))
    {
        yield return chunk;
    }
    
    if (itemsToChunk < itemCount)
    {
        yield return collection.Skip(itemsToChunk);
    }
}

例如小提琴here

nbewdwxp

nbewdwxp2#

我发现你的代码有两个问题。首先,你输出结果的方式,不可能区分值的分组,因为你只是在单独的行上输出每个值。
这个问题可以通过对一组中的每个值使用Console.Write来解决,然后在分组完成时添加一个Console.WriteLine()。这样每组的值都显示在单独的一行中。我们还可以通过获取最大值的长度并将其传递给PadRight方法来填充这些值,使它们整齐排列:

static void Main(string[] args)
{
    var numItems = 18;
    var splitBy = 3;

    var input = Enumerable.Range(0, numItems).ToList();
    var results = input.Split(splitBy);

    // Get the length of the largest value to use for padding smaller values, 
    // so all the columns will line up when we display the results
    var padValue = input.Max().ToString().Length + 1;

    foreach (var group in results)
    {
        foreach (var item in group)
        {
            Console.Write($"{item}".PadRight(padValue));
        }

        Console.WriteLine();
    }

    Console.Write("\n\nDone. Press any key to exit...");
    Console.ReadKey();
}

现在,您的结果看起来相当不错,只是我们可以看到数字没有按预期进行分组:

0  3  6  9  12 15
1  4  7  10 13 16
2  5  8  11 14 17

这样做的原因是,我们是按照每个项目除以部分数的余数进行分组的,所以,第一组包含除以3后余数为0的所有数字,第二组包含余数为1的所有项目,依此类推。
要解决这个问题,我们应该用一行中的项数(列数)除项目的索引。
换句话说,18项除以3行将得到每行6项。使用整数除法,从05的所有索引在除以6时将具有余数0,从611的所有索引在被6除时将具有余数1,并且从x1M17N1x到x1M18N1x的所有索引在被x1M20N1x除时将具有余数x1M19N1x。
然而,我们也必须能够处理溢出的数字。一种方法是检查索引是否大于或等于rows * columns(即它将在新的一行结束,而不是在最后一行)。如果这是真的,那么我们将它设置为最后一行。
我不擅长linq,所以可能有更好的方法来写这个,但是我们可以这样修改我们的扩展方法:

public static IEnumerable<IEnumerable<T>> Split<T>(
    this IEnumerable<T> list, int parts)
{
    int numItems = list.Count();
    int columns = numItems / parts;
    int overflow = numItems % parts;

    int index = 0;

    return from item in list
           group item by
               index++ >= (parts * columns) ? parts - 1 : (index - 1) / columns
           into part
           select part.AsEnumerable();
}

现在我们的结果看起来更好:

// For 18 items split into 3
0  1  2  3  4  5
6  7  8  9  10 11
12 13 14 15 16 17

// For 25 items split into 7
0  1  2
3  4  5
6  7  8
9  10 11
12 13 14
15 16 17
18 19 20 21 22 23 24
2uluyalo

2uluyalo3#

这应该可以
因此,每个列表应该(理想情况下)有 x/n 个元素,其中x=〉列表中的元素数& n=〉它必须拆分成的列表数
如果x不能被n整除,那么每个列表都应该有 x/n(向下舍入到最接近的整数)。令该数字为'y'。而最后一个列表应该有 * x-y (n - 1)。令该数字为'z'。
第一个for循环所做的是重复创建一个列表的过程,该列表包含适当数量的元素n次。
if-else代码块是用来检查创建的列表是否是最后一个列表,如果是最后一个列表,则有z个条目,如果不是,则有y个条目。
嵌套的for循环将列表项添加到“子列表”(List)中,然后将其添加到要返回的主列表(List)中。
此解决方案(明显)与您的签名和提供的其他解决方案不同。(可以说)更容易理解,虽然更长。当我过去寻找解决方案时,我过去常常采用能让我确切理解发生了什么的解决方案,但我无法完全理解这个问题的其他解决方案(还没有掌握编程的正确窍门)所以我在下面介绍了我写的一个,以防你最终陷入同样的困境。
如果需要更改,请告诉我。

static class Program
    {
    static void Main(string[] args)
        {
        var input = new List<String>();
        for (int k = 0; k < 18; ++k)
            {
            input.Add(k.ToString());
            }
        var result = SplitList(input, 5);//I've used 5 but it can be any number
        
        foreach (var resul in result)
            {
            foreach (var res in result)
                {
                Console.WriteLine(res);
                }
            }
        }

    public static List<List<string>> SplitList (List<string> origList, int n)
        {//"n" is the number of parts you want to split your list into
        int splitLength = origList.Count / n;    //splitLength is no. of items in each list bar the last one. (In case of overflow)
        List<List<string>> listCollection = new List<List<string>>();

        for ( int i = 0; i < n; i++ )
            {
            List<string> tempStrList = new List<string>();

            if ( i < n - 1 )
                {
                for ( int j = i * splitLength; j < (i + 1) * splitLength; j++ )
                    {
                    tempStrList.Add(origList[j]);
                    }
                }
            else
                {
                for ( int j = i * splitLength; j < origList.Count; j++ )
                    {
                    tempStrList.Add(origList[j]);
                    }
                }

            listCollection.Add(tempStrList);
            }

        return listCollection;
        }
    }

相关问题