【问题标题】:How can I split a list vertically in n parts with LINQ如何使用 LINQ 将列表垂直拆分为 n 部分
【发布时间】:2012-11-21 21:47:44
【问题描述】:

我想将一个列表分成几部分,不知道该列表中有多少项目。这个问题与那些想将列表拆分成固定大小的块的人不同。

int[] a = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

我希望将值垂直拆分

一分为二:

-------------------
| item 1 | item 6 |
| item 2 | item 7 |
| item 3 | item 8 |
| item 4 | item 9 |
| item 5 |        |

一分为三:

| item 1 | item 4 | item 7 |
| item 2 | item 5 | item 8 |
| item 3 | item 6 | item 9 |

一分为四:

| item 1 | item 4 | item 6 | item 8 |
| item 2 | item 5 | item 7 | item 9 |
| item 3 |        |        |        |

我发现了一些可以做到这一点的 c# 扩展,但它并没有按照我想要的方式分配价值。这是我发现的:

// this technic is an horizontal distribution
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;
}

结果是这样,但我的问题是值是水平分布的

| item 1 | item 2 |
| item 3 | item 4 |
| item 5 | item 6 |
| item 7 | item 8 |
| item 9 |        |

| item 1 | item 2 | item 3 |
| item 4 | item 5 | item 6 |
| item 7 | item 8 | item 9 |

知道如何垂直分配我的价值观并有可能选择我想要的部分数量吗?

现实生活中

对于那些想知道我想在哪种情况下垂直拆分列表的人,这里是我网站部分的屏幕截图:

【问题讨论】:

  • 所以你希望前半部分在一个列表中,后半部分在另一个列表中。您知道列表中有多少元素...也许您可以尝试创建一个列表并将前半部分元素插入到该新列表中...嗯...
  • var list1 = a.Take(a.Length / 2).ToList(); var list2 = a.Skip(list1.Count).ToList();
  • 您是要拆分为 2 个相等(相似)长度的列表、固定页面长度还是按内容值拆分?您还在寻找处理无限集合或固定集合的能力吗? (显然不可能将无限集一分为二)
  • @MatthewWhited 我想分成 2 个相同长度的列表。我已经更新了我的问题以更准确地显示我想要的内容。
  • @Oliver 乍一看似乎如此,但 OP 想要的结果与该问题不同,例如 this question

标签: c# linq list


【解决方案1】:

使用.Take().Skip(),您可以:

int[] a = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

int splitIndex = 4; // or (a.Length / 2) to split in the middle.

var list1 = a.Take(splitIndex).ToArray(); // Returns a specified number of contiguous elements from the start of a sequence.
var list2 = a.Skip(splitIndex).ToArray(); // Bypasses a specified number of elements in a sequence and then returns the remaining elements.

如果您想要List&lt;int&gt;,可以使用.ToList() 而不是.ToArray()


编辑:

在你稍微改变(也许澄清)你的问题之后,我想这就是你需要的:

public static class Extensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int parts)
    {
        var list = new List<T>(source);
        int defaultSize = (int)((double)list.Count / (double)parts);
        int offset = list.Count % parts;
        int position = 0;

        for (int i = 0; i < parts; i++)
        {
            int size = defaultSize;
            if (i < offset)
                size++; // Just add one to the size (it's enough).

            yield return list.GetRange(position, size);

            // Set the new position after creating a part list, so that it always start with position zero on the first yield return above.
            position += size;
        }
    }
}

使用它:

int[] a = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var lists = a.Split(2);

这会产生:

一分为二:a.Split(2);

| item 1 | item 6 |
| item 2 | item 7 |
| item 3 | item 8 |
| item 4 | item 9 |
| item 5 |        |

分成3个:a.Split(3);

| item 1 | item 4 | item 7 |
| item 2 | item 5 | item 8 |
| item 3 | item 6 | item 9 |

分成4个:a.Split(4);

| item 1 | item 4 | item 6 | item 8 |
| item 2 | item 5 | item 7 | item 9 |
| item 3 |        |        |        |

另外,如果你愿意:

int[] b = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // 10 items

并分成 4 份:b.Split(4);

| item 1 | item 4 | item 7 | item 9 |
| item 2 | item 5 | item 8 | item 10|
| item 3 | item 6 |        |        |

【讨论】:

  • 代码很好,但我想要一个函数,可以将列表拆分为我想要的部分数量。我的问题不是很清楚,我已经更新了描述以更准确地显示我想要做什么。很抱歉!
  • @AlexandreJobin 我已经更新了答案。这是你的意思吗?
  • @AlexandreJobin 我必须说我不太清楚在哪里以及为什么会以这种方式拆分列表,但这有点耐人寻味 =)
  • @jessehouwing Yepp,我写它的时候已经是深夜了。我已经更新了该方法,以便它不会多次迭代列表。不过,我相信为了得到想要的结果,至少一次遍历列表是不可避免的。
  • @Mario 我已经更新了我的描述以显示一个真实的场景,我需要将我的列表垂直拆分为多个部分。感谢您的代码,它工作得很好!
【解决方案2】:

这似乎很好地解决了这个问题。它可能可以做得更高效,但这已经足够令人费解了......它更容易做到:

1|4|7|10
2|5|8
3|6|9

比:

1|4|7|9
2|5|8|10
3|6|

我首先忽略了 LINQ 请求,因为我很难理解它。使用普通数组操作的解决方案可能会导致如下结果:

    public static IEnumerable<IEnumerable<TListItem>> Split<TListItem>(this IEnumerable<TListItem> items, int parts)
        where TListItem : struct
    {
        var itemsArray = items.ToArray();
        int itemCount = itemsArray.Length;
        int itemsOnlastRow = itemCount - ((itemCount / parts) * parts);
        int numberOfRows = (int)(itemCount / (decimal)parts) + 1;

        for (int row = 0; row < numberOfRows; row++)
        {
            yield return SplitToRow(itemsArray, parts, itemsOnlastRow, numberOfRows, row);
        }
    }

    private static IEnumerable<TListItem> SplitToRow<TListItem>(TListItem[] items, int itemsOnFirstRows, int itemsOnlastRow,
                                                                int numberOfRows, int row)
    {
        for (int column = 0; column < itemsOnFirstRows; column++)
        {
            // Are we on the last row?
            if (row == numberOfRows - 1)
            {
                // Are we within the number of items on that row?
                if (column < itemsOnlastRow)
                {
                    yield return items[(column + 1) * numberOfRows -1];
                }
            }
            else
            {
                int firstblock = itemsOnlastRow * numberOfRows;
                int index;

                // are we in the first block?
                if (column < itemsOnlastRow)
                {
                    index = column*numberOfRows + ((row + 1)%numberOfRows) - 1;
                }
                else
                {
                    index = firstblock + (column - itemsOnlastRow)*(numberOfRows - 1) + ((row + 1)%numberOfRows) - 1;
                }

                yield return
                    items[index];
            }
        }
    }

LINQ 伪代码是:

//WARNING: DOES NOT WORK
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
{
    int itemOnIndex = 0;
    var splits = from item in list
                 group item by MethodToDefineRow(itemOnIndex++) into row
                 select row.AsEnumerable();
    return splits;
}

但是在不知道物品数量的情况下,无法计算放置的位置。

因此,通过做一些预先计算,您可以使用 LINQ 来实现与上述相同的事情,这需要通过 IEnumerable 两次,似乎没有办法解决这个问题。诀窍是计算每个值将分配到的行。

    //WARNING: Iterates the IEnumerable twice
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
        int itemOnIndex = 0;
        int itemCount = list.Count();
        int itemsOnlastRow = itemCount - ((itemCount / parts) * parts);
        int numberOfRows = (int)(itemCount / (decimal)parts) + 1;
        int firstblock = (numberOfRows*itemsOnlastRow);

        var splits = from item in list
                     group item by (itemOnIndex++ < firstblock) ? ((itemOnIndex -1) % numberOfRows) : ((itemOnIndex - 1 - firstblock) % (numberOfRows - 1)) into row
                     orderby row.Key
                     select row.AsEnumerable();
        return splits;
    }

【讨论】:

    【解决方案3】:

    使用.Take(#OfElements) 指定您要采用的元素数量。

    您也可以使用.First.Last

    【讨论】:

    • .First 只会返回一个值(在这种情况下,1 如果没有谓词),.Last 也是如此(除非它会是 9,如果没有谓词)。
    • 这太完美了,我亲爱的沃森!
    猜你喜欢
    • 2010-10-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-29
    • 1970-01-01
    • 1970-01-01
    • 2011-03-12
    相关资源
    最近更新 更多