Append an item to an existing list - c#

I am working with a link list. I have set my constructor to take an array named ax with a set of already defined items. I also decided to have an input box which through a BtnAddTree_Click appends the new item to the list ax. But instead of appending to the list ax it creates a whole new separate list. How can I append items to the array list ax through my AddTree function?
public ListForTrees(IEnumerable<fruit_trees> trees)
{
foreach (fruit_trees t in trees)
{
this.AddTree(t);
}
}
public void AddTree(fruit_trees new_tree)
{
fruit_trees current = first_tree;
if (count == 0)
{
first_tree = new_tree;
last_tree = new_tree;
count = 1;
}
else if (count != 0)
{
if (new_tree.tree_price <= first_tree.tree_price)
{
new_tree.next_tree = first_tree;
first_tree = new_tree;
}
else if (new_tree.tree_price >= last_tree.tree_price)
{
last_tree.next_tree = new_tree;
last_tree = new_tree;
}
else
{
while (new_tree.tree_price > current.next_tree.tree_price)
{
current = current.next_tree;
}
new_tree.next_tree = current.next_tree;
current.next_tree = new_tree;
}
count++;
}
}
}
ListForTrees mainlist = new ListForTrees();
private void BtnGo_Click(object sender, EventArgs e)
{
fruit_trees[] ax = { new fruit_trees("cherry", 48, 12.95, 3),
new fruit_trees("pine", 36, 9.95, 8),
new fruit_trees("oak", 60, 14.95, 2),
new fruit_trees("peach", 54, 19.95, 3),
new fruit_trees("pear", 36, 11.85, 2),
new fruit_trees("apple", 62, 13.45, 5)
};
mainlist = new ListForTrees(ax);
fruit_trees current = mainlist.first_tree;
while (current != null)
{
current = current.next_tree;
}
}
}
}

It doesn't seem to be creating a new separate list. I tested out the following code with yours:
public class TreeTester
{
public static void Main(string[] args)
{
var list = new ListForTrees(
new[] { new fruit_trees("tree10",10,10,10), new fruit_trees("tree2",2,2,2) });
list.AddTree( new fruit_trees("tree3",3,3,3) ); // middle
list.AddTree( new fruit_trees("tree1",1,1,1) ); // first
list.AddTree( new fruit_trees("tree50",50,50,50) ); // last
list.AddTree( new fruit_trees("tree5",5,5,5) ); // middle
Console.Write(list);
}
}
And got the following output, which seems correct.
tree1 1 1 1
tree2 2 2 2
tree3 3 3 3
tree5 5 5 5
tree10 10 10 10
tree50 50 50 50
What is the expected behavior, if this is not correct? Clearly these items are all being added to the original list, since they're present when I iterate through the list.
By the way, I also added the following ToString function to your ListForTrees class; it makes debugging easier.
public override string ToString()
{
string s = "";
for (var tree=first_tree; tree!=null; tree = tree.next_tree)
s += tree + "\n";
return s;
}
Edit: I must comment that you may find it helpful to cleanup your code a bit in trying to understand where it is going wrong. For example, your ListForTrees(fruit_trees new_tree) constructor does the same exact thing as calling Add(new_tree) would. Also, think about three cases you have in Add, under else if (count != 0) -- perhaps there's a way they could elegantly combined into one general while loop? It makes it easier to analyze, and (potentially) less error-prone.

Related

c# List doesn't add items

Solution: Had multiple calls to the method and therefore had more called remove more times, than items were existing.
In my programm I need to remove Items out of a List. But once I get to the RemoveAt() command, I get an ArgumentOutOfRangeException and it tells me that all of my Lists have a count of 0, or in other words, are empty. But though it says they are empty, the object were created and all of the methods and events are working just fine. Here's some parts of my code:
My Lists:
measure = new List<Messen>(maxAblaufe);
steuern = new List<Steuern>(maxAblaufe);
model = new List<Model>(maxAblaufe);
measureReflector = new List<EventReflector>(maxAblaufe);
steuernReflector = new List<EventReflector>(maxAblaufe);
Creating the Lists:
if (nextSet < maxAblaufe)
{
neuerAblauf na = new neuerAblauf();
na.Show();
//if (model.Count == nextSet)
model.Add(new Model());
na.Model = model.ElementAt(nextSet);
model.ElementAt(nextSet).Index = nextSet;
na.eventStartAblauf += this.startAblauf;
}
And:
public void startAblauf(object sender, EventArgs e) {
EventReflector ers = new EventReflector();
EventReflector erm = new EventReflector();
steuernReflector.Add(ers);
measureReflector.Add(erm);
if (nextSet > 0)
{
measure.ElementAt(nextSet-1).eventNextMeasure += measureReflector.ElementAt(nextSet).reflectEvent;
steuern.ElementAt(nextSet-1).eventNextSteuern += steuernReflector.ElementAt(nextSet).reflectEvent;
}
else if (nextSet == 0) {
timingMessen.eventRefreshData += measureReflector.ElementAt(nextSet).reflectEvent;
timingSteuerung.eventRefreshSteuerung += steuernReflector.ElementAt(nextSet).reflectEvent;
}
model.ElementAt(nextSet).MesstabellePath = "C:\\Users\\myuser\\Documents\\Privat\\MessTest\\Messung" + nextSet + ".csv";
Messen mess = new Messen(model.ElementAt(nextSet), myPLC, 60 + nextSet * 20, measureReflector.ElementAt(nextSet));
measure.Add(mess);
Steuern str = new Steuern(model.ElementAt(nextSet), steuertakt, myPLC, 60 + nextSet * 20, mess, steuernReflector.ElementAt(nextSet));
steuern.Add(str);
steuern.ElementAt(nextSet).eventDisconnectAblauf += this.disconnectAblauf;
nextSet++;
}
The part where I (try) to delete the items is this:
public void disconnectAblauf(object sender, EventArgs e) {
SteuernArgs es = (SteuernArgs)e;
int index = es.index;
int indexbefore = index;
indexbefore--;
int indexafter = index;
indexafter++;
if (index > 0 && nextSet > (indexafter))
{
measure.ElementAt(indexbefore).eventNextMeasure += measureReflector.ElementAt(indexafter).reflectEvent;
steuern.ElementAt(indexbefore).eventNextSteuern += steuernReflector.ElementAt(indexafter).reflectEvent;
}
else if (index == 0 && nextSet > (indexafter)) {
timingMessen.eventRefreshData += measureReflector.ElementAt(indexafter).reflectEvent;
timingMessen.eventRefreshData -= measureReflector.ElementAt(index).reflectEvent;
timingSteuerung.eventRefreshSteuerung += steuernReflector.ElementAt(indexafter).reflectEvent;
timingSteuerung.eventRefreshSteuerung -= steuernReflector.ElementAt(index).reflectEvent;
}
steuernReflector.RemoveAt(index);
steuern.RemoveAt(index);
measure.RemoveAt(index);
measureReflector.RemoveAt(index);
model.RemoveAt(index);
I already tried a lot of things so there may be some commented lines or "useless" lines, just try to ignore those, thanks!
EDIT:
Trimmed Version of the (in my opinion) relevent code:
measure = new List<Messen>(maxAblaufe);
steuern = new List<Steuern>(maxAblaufe);
model = new List<Model>(maxAblaufe);
measureReflector = new List<EventReflector>(maxAblaufe);
steuernReflector = new List<EventReflector>(maxAblaufe);
EventReflector ers = new EventReflector();
EventReflector erm = new EventReflector();
steuernReflector.Add(ers);
measureReflector.Add(erm);
model.ElementAt(nextSet).MesstabellePath = "C:\\Users\\myuser\\Documents\\Privat\\MessTest\\Messung" + nextSet + ".csv";
Messen mess = new Messen(model.ElementAt(nextSet), myPLC, 60 + nextSet * 20, measureReflector.ElementAt(nextSet));
measure.Add(mess);
Steuern str = new Steuern(model.ElementAt(nextSet), steuertakt, myPLC, 60 + nextSet * 20, mess, steuernReflector.ElementAt(nextSet));
steuern.Add(str);
steuernReflector.RemoveAt(index);
steuern.RemoveAt(index);
measure.RemoveAt(index);
measureReflector.RemoveAt(index);
model.RemoveAt(index);
I think the problem is that you are storing the index in the Index property. Suppose you have three items in the collection, and first you remove the one with index 0. This will shorten the List and item with Index property of 2 will move to index 1. This however means that if you now run the remove method, the item will no longer be on the position 2 and the attempt to access it will throw ArgumentOutOfRangeException.

C# - Merge list items into one item based on some matching values

I have a list of items (List<Tasks> tasks), like this one:
Id Action Source Target
------- --------- -------- ---------
1 Save 12 18
4 Save 18 21
7 Save 21 23
6 Save 23 25
10 Save 25 27
16 Save 29 31
0 Edit 31 37
What I want to do, is to merge the rows that have the same (Source and Target) and the same Action. For example, what I need at the end should look like this:
Id Action Source Target
------- --------- -------- ---------
22 Save 12 27
16 Save 29 31
0 Edit 31 37
Which means, all the items that have the same Action(in my case here Save) should be merged to one row/item but only in case the Target value of the upper item is equal to the Source value of the follower item. A follower is the lower item.
For example, upper is item Id = 1 and lower/follower is item Id = 4. So the record that follows.
Is there any way to do this with linq avoiding the too many foreach loops?
Maybe something like Hierarchies with CTE in SQL. But I'm still not finding the correct syntax, so that's why I didn't paste any code.
Thanks in advance!
If you want "LINQ" solution, you can use Aggregate like this:
var result = tasks.Aggregate(new List<Item>(), (acc, current) =>
{
if (acc.Count > 0)
{
var prev = acc[acc.Count - 1];
if (prev.Action == current.Action && prev.Target == current.Source)
{
// update previous target
prev.Target = current.Target;
}
// otherwise just add
else acc.Add(current);
}
else acc.Add(current);
return acc;
});
It starts with empty List as accumulator, and feeds items one by one. Then we just add items to accumulator if they do not match criteria, and if they do match - we update previous item instead.
Take a look at MoreLinq. There is a function named Segment which splits the sequence into subsequences based on some condition:
var grouped = tasks
.GroupBy(t => t.Action, (k, g) => g
.Segment((s, f, a) => s.Source != f.Target)
.Select(c => new
{
c.First().Source,
c.Last().Target,
Action = k
})));
So the sequence is divided and a new subsequence is created on each adjacent pair when s.Source != f.Target (f is first element and s is second in a pair).
Something like a CTE query. First select seed nodes (no records pointing to the SOURCE) then in the do-wile loop change Targets to get the end target of each chain. No previous order is required.
public class Tasks
{
public int Id;
public string Action;
public int Source;
public int Target;
}
static void Main(string[] args)
{
List<Tasks> tasks = new List<Tasks>{
new Tasks{Id=1,Action="Save",Source= 12,Target=18},
new Tasks{Id=4,Action="Save",Source= 18,Target=21},
new Tasks{Id=7,Action="Save",Source= 21,Target=23},
new Tasks{Id=6,Action="Save",Source= 23,Target=25},
new Tasks{Id=10,Action="Save",Source= 25,Target=27},
new Tasks{Id=16,Action="Save",Source= 29,Target=31},
new Tasks{Id=0,Action="Edit",Source= 31,Target=37},
};
var collectTasks = (from t in tasks
where !tasks.Any(t1 => (t1.Target == t.Source)&&(t1.Action == t.Action)&&(t1.Id!=t.Id))
select t).ToList();
foreach (var ct in collectTasks)
{
do{
var t1 = from t in tasks where ((ct.Target == t.Source)&&(ct.Action == t.Action)&&(ct.Id!=t.Id)) select t;
if (t1.Count() == 0) { break; }
ct.Target = t1.First().Target;
} while (true);
}
foreach (var t in collectTasks)
{
Console.WriteLine("Action = {0}, Source = {1}, Target = {2}", t.Action, t.Source, t.Target);
}
}
You need just one loop using index to access list items, starting from 1 and combining current item with previous if their Action match and Target of previous item is equal to Source of current:
for (int i = 1; i < items.Count; i++)
if (items[i].Action == items[i - 1].Action && items[i].Source == items[i - 1].Target)
{
items[i - 1].Target = items[i].Target;
items.RemoveAt(i);
i--; // to have same index after i++
}
That would not change Id, so it will be 1 and not 22 as you wrote.
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication33
{
class Program
{
static void Main(string[] args)
{
List<Task> tasks = new List<Task>() {
new Task() { Id = 1, Action = "Save", Source = 12, Target = 18},
new Task() { Id = 4, Action = "Save", Source = 18, Target = 21},
new Task() { Id = 7, Action = "Save", Source = 21, Target = 23},
new Task() { Id = 6, Action = "Save", Source = 23, Target = 25},
new Task() { Id = 10, Action = "Save", Source = 25, Target = 27},
new Task() { Id = 16, Action = "Save", Source = 29, Target = 31},
new Task() { Id = 0, Action = "Edit", Source = 31, Target = 37}
};
for(int i = tasks.Count - 1; i >= 0; i--)
{
int source = tasks[i].Source;
List<int> match = tasks.Select((x, index) => new { x = x, i = index }).Where(x => (x.x.Target == source) && (tasks[i].Action == tasks[x.i].Action)).Select(x => x.i).ToList();
if (match.Count > 0)
{
tasks[match[0]].Target = tasks[i].Target;
tasks.RemoveAt(i);
}
}
}
}
public class Task
{
public int Id { get; set; }
public string Action { get; set; }
public int Source { get; set; }
public int Target { get; set; }
}
}
This procedure will do what you need in one-pass, preserving the "Id" values of items that can't be merged.
You'll see it works on an ordered list so strictly speaking there is some LINQ-hidden activity there; but generally speaking you'll find that LINQ queries incur an overhead which make then always slower than for-loops over arrays.
private List<Task> Merge(List<Task> list)
{
var result = new List<Task>();
var maxId = list.Max(x => x.Id) + 1;
// order list for this algo to work
var listO = list.OrderByDescending(x => x.Action).ThenBy(x => x.Source).ToArray();
// create seed and counter
Task seed = listO[0];
var ctr = 0;
for (var i = 0; i < listO.Length - 1; i++)
{
if (listO[i + 1].Source == listO[i].Target && listO[i + 1].Action == listO[i].Action && listO[i + 1].Action == seed.Action)
{
// if the next is the last, merge it now
if (i + 1 == listO.Length - 1)
result.Add(new Task() { Id = maxId++, Action = seed.Action, Source = seed.Source, Target = listO[i].Target });
// the next item qualifies for merge, move to next
ctr++;
continue;
}
// next item does not qualify for merge, merge what we have or just add the item if ctr == 0
result.Add(ctr == 0 ? seed : new Task() { Id = maxId++, Action = seed.Action, Source = seed.Source, Target = listO[i].Target });
// reset seed record + counter
seed = listO[i+1];
ctr = 0;
// if the next item is the last, it belongs in the list as is
if (i + 1 == listO.Length - 1) result.Add(seed);
}
return result;
}

How to create a conditional-OR if statement that runs multiple times if multiple conditions are true

Right now my code has 52 checkboxes that each return their own value.
if (checkedListBox1.GetItemCheckState(0) == CheckState.Checked)
{
x += 1;
}
if (checkedListBox1.GetItemCheckState(1) == CheckState.Checked)
{
x += 2;
}
if (checkedListBox1.GetItemCheckState(2) == CheckState.Checked)
{
x += 1;
}
I want to group if statements that do the same thing into a single statement, something like
if (checkedListBox1.GetItemCheckState(0) == CheckState.Checked ||
checkedListBox1.GetItemCheckState(2) == CheckState.Checked ||
checkedListBox1.GetItemCheckState(17) == CheckState.Checked )
{
x += 1;
}
However such a code would only run once. Is there an operator that would help in this situation or would I have to just write 52 if statements.
I would create an int[] array of scores with one entry for each possible checkbox:
var scores = new []
{
1,
2,
1,
4,
2,
1,
// Etc up to 52 items
};
Then you can just loop though all the checkboxes and add up all the scores:
for (int i = 0; i < checkedListBox1.Items.Count; ++i)
if (checkedListBox1.GetItemCheckState(i)) == CheckState.Checked)
x += scores[i];
You could also use CheckedListBox.CheckedIndices to iterate through the checked items, which would look like this:
x = checkedListBox1.CheckedIndices.Cast<int>().Sum(i=> scores[i]);
A much better way to approach this, IMO, is to write a special Yaku class which is used to hold information about each item in the list. This would include the name and the score (han). It would also override ToString() so that the name would be displayed in the list.
It could look a bit like this:
public class Yaku
{
public string Name { get; }
public int Han { get; }
public Yaku(string name, int han)
{
Name = name;
Han = han;
}
public override string ToString()
{
return Name;
}
}
Then you could initialise the checked list box somewhere like this:
public Form1()
{
InitializeComponent();
checkedListBox1.Items.Add(new Yaku("Little three dragons", 4));
checkedListBox1.Items.Add(new Yaku("Terminal in each set", 3));
checkedListBox1.Items.Add(new Yaku("Three closed triplets", 3));
}
And add up the scores like this:
private void button1_Click(object sender, EventArgs e)
{
int score = checkedListBox1.CheckedItems.OfType<Yaku>().Sum(item => item.Han);
MessageBox.Show(score.ToString());
}
Put the indices of checkboxes in a list / array:
using System.Linq;
...
var checkboxIndices = { 0, 2, 17 };
x += checkboxIndices.Count(index =>
checkedListBox1.GetItemCheckState(index) == CheckState.Checked);
Edit: Sigh, I though it was obvious but here's the same thing with more details:
class Yaku
{
public Yaku(List<int> indices, int han)
{
Indices = indices;
HanValue = han;
}
public List<int> Indices;
public int HanValue;
public int ComputeValue(CheckedListBox checkedListBox)
{
return HanValue * Indices.Count(index =>
checkedListBox.GetItemCheckState(index) == CheckState.Checked);
}
}
...
var yakus = [
new Yaku({0, 2, 17 }, 1),
new Yaku({1}, 2)
...
];
var totalYakuValue = yakus.Aggregate(yaku => yaku.ComputeValue());

Creating a two-dimensional array

I am trying to create a two dimensional array and I am getting so confused. I was told by a coworker that I need to create a dictionary within a dictionary for the array list but he couldn't stick around to help me.
I have been able to create the first array that lists the the programs like this
+ project 1
+ project 2
+ project 3
+ project 4
The code that accomplishes this task is below-
var PGList = from x in db.month_mapping
where x.PG_SUB_PROGRAM == SP
select x;
//select x.PG.Distinct().ToArray();
var PGRow = PGList.Select(x => new { x.PG }).Distinct().ToArray();
So that takes care of my vertical array and now I need to add my horizontal array so that I can see the total amount spent in each accounting period. So the final output would look like this but without the dashes of course.
+ program 1-------100---200---300---400---500---600---700---800---900---1000---1100---1200
+ program 2-------100---200---300---400---500---600---700---800---900---1000---1100---1200
+ program 3-------100---200---300---400---500---600---700---800---900---1000---1100---1200
+ program 4-------100---200---300---400---500---600---700---800---900---1000---1100---1200
I have tried to use a foreach to cycle through the accounting periods but it doesn't work. I think I might be on the right track and I was hoping SO could provide some guidance or at the very least a tutorial for me to follow. I have posted the code that I written so far on the second array below. I am using C# and MVC 3. You might notice that their is no dictionary within a dictionary. If my coworker is correct how would I do something like that, I took a look at this question using dictionary as a key in other dictionary but I don't understand how I would use it in this situation.
Dictionary<string, double[]> MonthRow = new Dictionary<string, double[]>();
double[] PGContent = new double[12];
string lastPG = null;
foreach (var item in PGRow)
{
if (lastPG != item.PG)
{
PGContent = new double[12];
}
var MonthList = from x in db.Month_Web
where x.PG == PG
group x by new { x.ACCOUNTING_PERIOD, x.PG, x.Amount } into pggroup
select new { accounting_period = pggroup.Key.ACCOUNTING_PERIOD, amount = pggroup.Sum(x => x.Amount) };
foreach (var P in MonthList)
{
int accounting_period = int.Parse(P.accounting_period) - 1;
PAContent[accounting_period] = (double)P.amount;
MonthRow[item.PG] = PGContent;
lastPG = item.PG;
}
I hope I have clearly explained my issue, please feel free to ask for any clarification needed as I need to solve this problem and will be checking back often. Thanks for your help!
hope this helps.
// sample data
var data = new Dictionary<string, List<int>>();
data.Add("program-1", new List<int>() { 100, 110, 130 });
data.Add("program-2", new List<int>() { 200, 210, 230 });
data.Add("brogram-3", new List<int>() { 300, 310, 330 });
// query data
var newData = (from x in data
where x.Key.Contains("pro")
select x).ToDictionary(v => v.Key, v=>v.Value);
// display selected data
foreach (var kv in newData)
{
Console.Write(kv.Key);
foreach (var val in kv.Value)
{
Console.Write(" ");
Console.Write(val.ToString());
}
Console.WriteLine();
}
output is:
program-1 100 110 130
program-2 200 210 230
Don't try to use anonymous types or LINQ projection to create new data types, especially if you're a beginner, you will just get confused. If you want a specialized data type, define one; e.g.:
public class Account
{
public string Name { get; private set; }
public decimal[] MonthAmount { get; private set; }
readonly int maxMonths = 12;
public Account(string name, ICollection<decimal> monthAmounts)
{
if (name == null)
throw new ArgumentNullException("name");
if (monthAmounts == null)
throw new ArgumentNullException("monthAmounts");
if (monthAmounts.Count > maxMonths)
throw new ArgumentOutOfRangeException(string.Format(" monthAmounts must be <= {0}", maxMonths));
this.Name = name;
this.MonthAmount = new decimal[maxMonths];
int i = 0;
foreach (decimal d in monthAmounts)
{
this.MonthAmount[i] = d;
i++;
}
}
}
Use instances of this type directly, you do not have to convert them to arrays, dictionaries, lists, or anything else:
var accountPeriods = new List<Account>();
accountPeriods.Add(new Account("program-1", new decimal[] { 1, 2, 3, 4 }));
You can use LINQ or whatever to query or alter instances of your new type:
foreach (Account a in accountPeriods)
foreach (decimal d in a.MonthAmount)
DoSomethingWith(d);
That should be enough to get you started.
I want to thank #Ray Cheng and #Dour High Arch for their help but I have figured out another way to accomplish this task and I wanted to post my code so that the next person that is having the same trouble can figure out their problem faster.
Above I split my code into more managable sections to explain my problem as clearly as I could and the code below has all those parts combined so you can see the big picture. This code returns an array that contains the program and the amounts for every month.
public virtual ActionResult getAjaxPGs(string SP = null)
{
if (SP != null)
{
var PGList = from x in db.month_mapping
where x.PG_SUB_PROGRAM == SP
select x;
var PGRow = PGList.Select(x => new { x.PG }).Distinct().ToArray();
float[] PGContent = new float[12];
Dictionary<string,float[]> MonthRow = new Dictionary<string, float[]>();
foreach (var item in PGRow)
{
PGContent = new float[12];
var MonthList = from x in db.month_Web
where x.PG == item.PG
group x by new { x.ACCOUNTING_PERIOD, x.PG, x.Amount } into pggroup
select new { accounting_period = pggroup.Key.ACCOUNTING_PERIOD, amount = pggroup.Sum(x => x.Amount) };
foreach (var mon in MonthList)
{
int accounting_period = int.Parse(mon.accounting_period) - 1;
PGContent[accounting_period] = (float)mon.amount/1000000;
}
MonthRow[item.PG] = PGContent;
}
return Json(MonthRow, JsonRequestBehavior.AllowGet);
}
return View();
}
This code worked great for me since I am pulling from a Linq to SQL query instead of adding data directly into the code. My problems stemmed from mainly putting the data pulls outside of the foreach loops so it only pulled 1 piece of data from the SQL instead of all twelve months. I hope this helps some one else who is trying to pull data in from SQL data sources into multidimensional arrays.

Strange array behavior

I observe a very strange behavior, maybe could you help me to see what happen.
Here the class:
public sealed class Sudoku
{
private SudokuCell[] _grid = new SudokuCell[81];
// ctor {}
private IEnumerable<SudokuCell> Grid
{
get { return _grid; }
}
private SudokuRow[] _rows;
public IEnumerable<SudokuRow> Rows
{
get
{
if (_rows == null)
{
_rows = new SudokuRow[9];
for (int i = 0, length = 9; i < length; i++)
{
_rows[i] = new SudokuRow(from cell in Grid
where cell.Row == i
select cell);
// Always print 9 (GOOD)
Trace.WriteLine("First Loop " + i + " : " + _rows[i].Cells.Count());
}
}
for (int i = 0; i < 9; i++)
{
// Always print 0 ! Huh !?
Trace.WriteLine("Second Loop " + i + " : " + _rows[i].Cells.Count());
}
return _rows;
}
}
}
public abstract class SudokuPart
{
public SudokuPart(IEnumerable<SudokuCell> cells)
{
Cells = cells;
}
public int Index
{ get; protected set; }
public IEnumerable<SudokuCell> Cells
{ get; protected set; }
}
public sealed class SudokuRow : SudokuPart
{
public SudokuRow(IEnumerable<SudokuCell> cells)
: base(cells)
{
base.Index = cells.First().Row;
}
}
Could anyone tell me why in the second loop it trace 0 instead of 9 !? I changed nothing between both loops !!!
Thanks...
This is the problem:
_rows[i] = new SudokuRow(from cell in Grid
where cell.Row == i
select cell);
That's capturing the loop variable (i)... within the loop, it has a sensible value, which is why you're seeing 9 matches.
However, when you count the matching values in the second loop, that single captured variable will have the value 9. Now no cell.Row has a value of 9, so you're not getting any matches. For more information on this, see Eric Lippert's great blog post, "Closing over the loop variable considered harmful."
Three fixes:
Capture a copy of the loop variable:
int copy = i;
_rows[i] = new SudokuRow(from cell in Grid
where cell.Row == copy
select cell);
Each iteration of the loop will get a separate copy.
Materialize the query in the loop:
_rows[i] = new SudokuRow((from cell in Grid
where cell.Row == i
select cell).ToList());
Or even:
_rows[i] = new SudokuRow(Grid.Where(cell => cell.Row == i).ToList());
Don't use LINQ at all! Why not just have an array of arrays to represent the grid? That's a much more natural approach, IMO.
I think Jon Skeet answer is great, but I just wanted to add a bit to it with an example of Deferred LINQ Queries. Once I saw this in action, it helped me understand a bit more about some of the nuances of this kind of code problem you ran into.
Try this code.
var numbers = new List<int> {1, 2, 3, 4, 5};
//Lets create an IEnumerable<int> with the values in our numbers list greater then 3.
var bignumbers = numbers.Where(n => n > 3);
//You may assume our variable bignumbers now contains the numbers 4 and 5
//now lets add another number to our original list that fits the criteria of our LINQ Where statement
numbers.Add(6);
foreach (var big in bignumbers) {
Console.WriteLine(big.ToString());
}
Our output from our foreach loop is going to be 4,5,6! This is because our query doesn't run until the foreach causes the enumeration of the items in our bignumbers variable.
Just something else to consider when your building lists within loops and your querying those lists outside of the loops. Your often going to get something other than what your expecting.

Categories