简体   繁体   English

C#算法重构将数组分成3部分?

[英]C# algorithm refactor splitting an array into 3 parts?

I have an IEnumerable and I wanted to split the data across 3 columns using the following business logic. 我有一个IEnumerable,我想使用以下业务逻辑将数据分成3列。 if 3 or less items, 1 item per column, anything else I wanted to divide the total items by 3 split the leftovers (either 1 or 2 items) between the first two columns. 如果3个或更少的项目,每列1个项目,我希望将总项目除以3的任何其他东西在前两个列之间分割剩余物(1或2个项目)。 Now this is pretty ugly but it does the job. 现在这非常难看,但它确实起了作用。 I'm looking for tips to leverage linq a little better or possibly eliminate the switch statement. 我正在寻找更好地利用linq或可能消除switch语句的技巧。 Any advice or tips that improve the code are appreciated. 任何改进代码的建议或提示都表示赞赏。

var numItems = items.Count;

            IEnumerable<JToken> col1Items,
                                col2Items, 
                                col3Items;


            if(numItems <=3)
            {
                col1Items = items.Take(1);
                col2Items = items.Skip(1).Take(1);
                col3Items = items.Skip(2).Take(1);

            } else {

                int remainder = numItems % 3,
                    take = numItems / 3,
                    col1Take, 
                    col2Take, 
                    col3Take;

                switch(remainder)
                {
                    case 1:
                        col1Take = take + 1;
                        col2Take = take;
                        col3Take = take;
                        break;
                    case 2:
                        col1Take = take + 1;
                        col2Take = take + 1;
                        col3Take = take;
                        break;
                    default:
                        col1Take = take;
                        col2Take = take;
                        col3Take = take;
                        break;

                }

                col1Items = items.Take(col1Take);
                col2Items = items.Skip(col1Take).Take(col2Take);
                col3Items = items.Skip(col1Take + col2Take).Take(col3Take);

Ultimately I am using these in a mvc Razor view 最终我在mvc Razor视图中使用它们

<div class="widgetColumn">
                @Html.DisplayFor(m => col1Items, "MenuColumn")                       
            </div> 

            <div class="widgetColumn">
                @Html.DisplayFor(m => col2Items, "MenuColumn")                       
            </div> 

            <div class="widgetColumn">
                @Html.DisplayFor(m => col3Items, "MenuColumn")                       
            </div>  

In my first attempt I want to get rid of the colNItems and colNTake variables but i can't figure out the correct algorithm to make it work the same. 在我的第一次尝试中,我想摆脱colNItems和colNTake变量,但我无法弄清楚正确的算法,使其工作相同。

for (int i = 1; i <= 3; i++ )
            {
                IEnumerable<JToken> widgets = new List<JToken>();
                var col = i;
                switch(col)
                {
                    case 1:
                       break;
                    case 2:
                        break;
                    case 3:
                        break;
                }
            }

Are the columns fixed-width? 列是固定宽度的吗? If so, then there's no need to do anything special with your collection. 如果是这样,那么就没有必要对你的收藏做任何特别的事情了。 Just rely on the browser to do it for you. 只需依靠浏览器为您完成。 Have an outer container that has the overall width of the 3 columns, then just fill it with a div for each item (and float left). 有一个外部容器,其总宽度为3列,然后只为每个项目填充一个div(并向左浮动)。 Set your inner containers to have a width exactly 1/3 of the outer container. 将内部容器设置为宽度恰好为外部容器的1/3。

Here's a quick fiddle 这是一个快速的小提琴

Here's a quick hint at the style 这是对风格的快速暗示

div#outer{
    width:300px;    
}

div#outer > div{
    width:100px;
    float:left;    
}

Can't you just do something like? 你能不能做点什么?

int len = numItems / 3;
int rem = numItems % 3;

int col1Take = len + (rem > 0 ? 1 : 0);
int col2Take = len + (rem > 1 ? 1 : 0);
int col3Take = len;

Edit: 编辑:

A more generic solution that works for any number of columns ( COLUMNS ) would be: 适用于任意数量列( COLUMNS )的更通用的解决方案是:

int len = numItems / COLUMNS;
int rem = numItems % COLUMNS;

foreach (var i in Enumerable.Range(0, COLUMNS)) {
  colTake[i] = len + (rem > i ? 1 : 0);
}

You could generalize: 你可以概括:

int cols = 3;
IEnumerable<JToken> colItems[3]; // you can make this dynamic of course

int rem = numItems % cols;
int len = numItems / cols;

for (int col=0; col<cols; col++){
    int colTake = len;
    if (col < rem) colTake++;
    colItems[col] = items.Skip(col*len).Take(colTake);
}

Haven't tested, but this should work for any number of columns. 尚未测试,但这适用于任意数量的列。

Also whenever you need variables col1, col2, col3 think of col[0], col[1], col[2]. 此外,每当需要变量col1,col2,col3时,可以考虑col [0],col [1],col [2]。

So you want the first n/3 items in the first column, next n/3 items in the 2nd column, etc. 所以你想要第一列中的前n / 3项,第二列中的下一个n / 3项等。

var concreteList = items.ToList();
var count = concreteList.Count;
var take1 = count/3 + (count % 3 > 0 ? 1 : 0);
var take2 = count/3 + (count % 3 > 1 ? 1 : 0);

var col1 = concreteList.Take(take1);
var col2 = concreteList.Skip(take1).Take(take2);
var col3 = concreteList.Skip(take1 + take2);

I make a concrete list in order to avoid iterating the Enumerable multiple times. 我制作了一个具体的列表,以避免多次迭代Enumerable For example, if you had: 例如,如果你有:

items = File.ReadLines("foo.txt");

Then you wouldn't be able to iterate it multiple times. 那么你将无法多次迭代它。

If you want to fill the columns round-robin you can use: 如果要循环填充列,可以使用:

int numColumns = 3;

var result = Enumerable.Range(1,numColumns).Select(c =>
      items.Where((x,ix) => ix % numColumns == c-1).ToArray()
   );

it might help 它可能有所帮助

        IEnumerable<object> items = new Object[]{ "1", "2", "3", "4", "5", "6", "7","8", "9", "10", "11", "12","13", "14" };

        IEnumerable<object> col1Items = new List<object>(),
                            col2Items = new List<object>(), 
                            col3Items = new List<object>();

        Object[] list = new Object[]{col1Items, col2Items, col3Items};
        int limit = items.Count()/3;
        int len = items.Count();
        int col;            

        for (int i = 0; i < items.Count(); i++ )
        {                
            if (len == 3) col = i;
            else col = i / limit;

            if (col >= 3) col = i%limit ;

            ((IList<object>)(list[col])).Add( items.ElementAt(i));

        }

This isn't fast, but it'll do the trick: 这不是很快,但它会做到这一点:

var col1Items = items.Select((obj, index) => new { Value = obj, Index = index })
    .Where(o => o.Index % 3 == 0).Select(o => o.Value);
var col2Items = items.Select((obj, index) => new { Value = obj, Index = index })
    .Where(o => o.Index % 3 == 1).Select(o => o.Value);
var col3Items = items.Select((obj, index) => new { Value = obj, Index = index })
    .Where(o => o.Index % 3 == 2).Select(o => o.Value);

It uses the version of Select that includes an index parameter. 它使用包含索引参数的Select版本。 You could use a GroupBy to speed this up a bit at the cost of a few lines of code. 您可以使用GroupBy以几行代码为代价来加快速度。

If you want to go across then down see answer below this one, if you want to go down then across you can do it like this (use this code with the test below to see it working instead of the var result line there.: 如果你想要翻阅然后再看下面这个答案,如果你想要往下走,你就可以这样做(使用下面的测试代码看看它的工作而不是那里的var result行:

var curCol = 0;
var iPer = items.Count() / 3;
var iLeft = items.Count() % 3;
var result = items.Aggregate(
               // object that will hold items
               new {  
                      cols = new List<ItemElement>[3] { new List<ItemElement>(), 
                                                        new List<ItemElement>(), 
                                                        new List<ItemElement>(), },
                          },
               (o, n) => {
                 o.cols[curCol].Add(n);

                 if (o.cols[curCol].Count() > iPer + (iLeft > (curCol+1) ? 1:0))
                   curCol++;

                 return new {
                   cols = o.cols
                };
             });

You can do this with aggregate. 你可以用聚合做到这一点。 It would look like this: 它看起来像这样:

void Main()
{
  List<ItemElement> items = new List<ItemElement>() { 
           new ItemElement() { aField = 1 },
           new ItemElement() { aField = 2 },
           new ItemElement() { aField = 3 },
           new ItemElement() { aField = 4 },
           new ItemElement() { aField = 5 },
           new ItemElement() { aField = 6 },
           new ItemElement() { aField = 7 },
           new ItemElement() { aField = 8 },
           new ItemElement() { aField = 9 }
  };

  var result = 
    items.Aggregate(
      // object that will hold items
      new {  
        cols = new List<ItemElement>[3] { new List<ItemElement>(), 
                                          new List<ItemElement>(), 
                                          new List<ItemElement>(), },
        next = 0 },
     // aggregate
     (o, n) => {
       o.cols[o.next].Add(n);

       return new {
         cols = o.cols,
         next = (o.next + 1) % 3
       };
    });
  result.Dump();
}

public class ItemElement 
{
   public int aField { get; set; }
}

You end up with an object with an array of 3 lists (one for each column). 最终得到一个包含3个列表数组的对象(每列一个)。

This example will run as is in linqPad. 此示例将在linqPad中运行。 I recomment linqPad for these kind of POC tests. 我推荐使用linqPad进行这类POC测试。 (linqPad.com) (linqPad.com)

LinqLib (nuget: LinqExtLibrary) has an overload of ToArray() that does it: LinqLib(nuget:LinqExtLibrary)有ToArray()的重载,它执行它:

using System.Collections.Generic;
using System.Linq;
using LinqLib.Array;

...

    public void TakeEm(IEnumerable<int> data)
    {
        var dataAry = data as int[] ?? data.ToArray();
        var rows = (dataAry.Length/3) + 1;
        //var columns = Enumerable.Empty<int>().ToArray(3, rows);
        // vvv These two lines are the ones that re-arrange your array
        var columns = dataAry.ToArray(3, rows);
        var menus = columns.Slice();
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM