[英]C# algorithm refactor splitting an array into 3 parts?
我有一个IEnumerable,我想使用以下业务逻辑将数据分成3列。 如果3个或更少的项目,每列1个项目,我希望将总项目除以3的任何其他东西在前两个列之间分割剩余物(1或2个项目)。 现在这非常难看,但它确实起了作用。 我正在寻找更好地利用linq或可能消除switch语句的技巧。 任何改进代码的建议或提示都表示赞赏。
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);
最终我在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>
在我的第一次尝试中,我想摆脱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;
}
}
列是固定宽度的吗? 如果是这样,那么就没有必要对你的收藏做任何特别的事情了。 只需依靠浏览器为您完成。 有一个外部容器,其总宽度为3列,然后只为每个项目填充一个div(并向左浮动)。 将内部容器设置为宽度恰好为外部容器的1/3。
这是一个快速的小提琴
这是对风格的快速暗示
div#outer{
width:300px;
}
div#outer > div{
width:100px;
float:left;
}
你能不能做点什么?
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;
编辑:
适用于任意数量列( COLUMNS
)的更通用的解决方案是:
int len = numItems / COLUMNS;
int rem = numItems % COLUMNS;
foreach (var i in Enumerable.Range(0, COLUMNS)) {
colTake[i] = len + (rem > i ? 1 : 0);
}
你可以概括:
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);
}
尚未测试,但这适用于任意数量的列。
此外,每当需要变量col1,col2,col3时,可以考虑col [0],col [1],col [2]。
所以你想要第一列中的前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);
我制作了一个具体的列表,以避免多次迭代Enumerable
。 例如,如果你有:
items = File.ReadLines("foo.txt");
那么你将无法多次迭代它。
如果要循环填充列,可以使用:
int numColumns = 3;
var result = Enumerable.Range(1,numColumns).Select(c =>
items.Where((x,ix) => ix % numColumns == c-1).ToArray()
);
它可能有所帮助
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));
}
这不是很快,但它会做到这一点:
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);
它使用包含索引参数的Select版本。 您可以使用GroupBy以几行代码为代价来加快速度。
如果你想要翻阅然后再看下面这个答案,如果你想要往下走,你就可以这样做(使用下面的测试代码看看它的工作而不是那里的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
};
});
你可以用聚合做到这一点。 它看起来像这样:
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; }
}
最终得到一个包含3个列表数组的对象(每列一个)。
此示例将在linqPad中运行。 我推荐使用linqPad进行这类POC测试。 (linqPad.com)
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.