This question already has answers here:
Fastest way to get matching items from two list c#
(4 answers)
Closed 1 year ago.
public IEnumerable<ComputedData> Compute (IEnumerable<DataSet> data, IEnumerable<Variations> variationData)
{
// I need to create one ComputedData instance for each item inside the IEnumerable<DataSet> data
// DataSet has properties: int ID, int Valu1, int Value2
// ComputedData has properties: int ID, int Result, int Variation
// variationData has properties: int ID, int Variation
var computedDate = data.Select (i => new ComputedData ()
{
ID = i.ID,
Result = i.value1 + i.value2
});
// ISSUE is here
foreach (var item in computedDate )
{
var id = item.ID;
// I need to find the corresponding element
// (with same ID) on IEnumerable<Variations> variationData
// and assign item.Variation =
// But getting Possible Multiple Enumeration warning
// and item.Variation become zero always !
}
}
Issue is the use of foreach loop. Is there any other way to solve this issue. Even though the code is working, item.Variation is always zero. That shouldn't be the case.
Any suggestions ?
The warning is to ensure that every time you iterate through the items in computeDate you don't re-execute this:
var computedDate = data.Select(i => new ComputedData()
{
ID = i.ID,
Result = i.value1 + i.value2
}).
which could, in turn, re-execute whatever method produced IEnumerable<DataSet> data.
There are few ways to deal with that. An easy one is to add this:
var computedDate = data.Select(i => new ComputedData()
{
ID = i.ID,
Result = i.value1 + i.value2
}).ToArray(); // <---- Add this
After this line of code enumerates the items in data the results are stored in an array, which means you can safely enumerate it again and again without executing this Select or anything else unexpected.
Related
I'm currently in the process of trying to figure out a way to match data in a view model to data in a query and pass it conditionally to the view.
I have achieved this so far, however when I try to set a ViewModel value equal to a list in my query, I can't seem to find the right syntax to make it happy.
So far I have this:
public IEnumerable<QuestionViewModel> QuestionRead(int i)
{
IEnumerable<Question> questions = _repository.GetQuestions(i);
foreach (Question q in questions)
{
switch (q.Type)
{
case QuestionType.TextBasic:
return questions.ToList().Select(question =>
new QuestionViewModel
{
QuestionText = question.QuestionText,
QuestionId = question.QuestionId,
OrderIndex = question.OrderIndex + 1,
// ActualAnswer = question.Answer.TextAnswer.
});
case QuestionType.Multi:
return questions.ToList().Select(question =>
new QuestionViewModel
{
QuestionText = question.QuestionText,
QuestionId = question.QuestionId,
OrderIndex = question.OrderIndex + 1,
// ActualAnswer = question.Answer.AnswerOptions.OptionText
});
}
}
return null;
}
When I get to the point of returning the ViewModel object called actual answer, which is a list, I'm trying to reach into the query and grab the list inside of the query. The query returns the data from 3 tables, and question.Answer.TextAnswer is a string, but I want to add it to a list. The reason for this is that I have another object called MultiAnswer that is a list.
Since all answers have to fit into one grid column, I want to set to the type to list so it can accomodate 1 or more items when displaying them. I thought about doing a linq query ActualAnswer = question.Answer.TextAnswer.ToList(), but this doesn't seem to work.
I have a C# program where I have a list (List<string>) of unique strings. These strings represent the name of different cases. It is not important what is is. But they have to be unique.
cases = new List<string> { "case1", "case3", "case4" }
Sometimes I read some cases saved in a text format into my program. Sometime the a case stored on file have the same name as a case in my program.I have to rename this new case. Lets say that the name of the case I load from a file is case1.
But the trouble is. How to rename this without adding a large random string. In my case it should ideally be called case2, I do not find any good algorithm which can do that. I want to find the smalles number I can add which make it unique.
i would use a HashSet that only accepts unique values.
List<string> cases = new List<string>() { "case1", "case3", "case4" };
HashSet<string> hcases = new HashSet<string>(cases);
string Result = Enumerable.Range(1, 100).Select(x => "case" + x).First(x => hcases.Add(x));
// Result is "case2"
in this sample i try to add elements between 1 and 100 to the hashset and determine the first sucessfully Add()
If you have a list of unique strings consider to use a HashSet<string> instead. Since you want incrementing numbers that sounds as if you actually should use a custom class instead of a string. One that contains a name and a number property. Then you can increment the number and if you want the full name (or override ToString) use Name + Number.
Lets say that class is Case you could fill a HashSet<Case>. HashSet.Add returns false on duplicates. Then use a loop which increments the number until it could be added.
Something like this:
var cases = new HashSet<Case>();
// fill it ...
// later you want to add one from file:
while(!cases.Add(caseFromFile))
{
// you will get here if the set already contained one with this name+number
caseFromFile.Number++;
}
A possible implementation:
public class Case
{
public string Name { get; set; }
public int Number { get; set; }
// other properties
public override string ToString()
{
return Name + Number;
}
public override bool Equals(object obj)
{
Case other = obj as Case;
if (other == null) return false;
return other.ToString() == this.ToString();
}
public override int GetHashCode()
{
return (ToString() ?? "").GetHashCode();
}
// other methods
}
The solution is quite simple. Get the max number of case currently stored in the list, increment by one and add the new value:
var max = myList.Max(x => Convert.ToInt32(x.Substring("case".Length))) + 1;
myList.Add("case" + max);
Working fiddle.
EDIT: For filling any "holes" within your collection you may use this:
var tmp = myList;
var firstIndex = Convert.ToInt32(myList[0].Substring("case".Length));
for(int i = firstIndex; i < tmp.Count; i++) {
var curIndex = Convert.ToInt32(myList[i].Substring("case".Length));
if (curIndex != i)
{
myList.Add("case" + (curIndex + 1));
break;
}
}
It checks for every element in your list if its number behind the case is equal to its index in the list. The loop is stopped at the very first element where the condition is broken and therefor you have a hole in the list.
I'm using the Linq OrderBy() function to sort a generic list of Sitecore items by display name, then build a string of pipe-delimited guids, which is then inserted into a Sitecore field. The display name is a model number of a product, generally around 10 digits. At first it seemed like this worked 100% of the time, but the client found a problem with it...
This is one example that we have found so far. The code somehow thinks IC-30R-LH comes after IC-30RID-LH, but the opposite should be true.
I put this into an online alphabetizer like this one and it was able to get it right...
I did try adding StringComparer.InvariantCultureIgnoreCase as a second parameter to the OrderBy() but it did not help.
Here's the code... Let me know if you have any ideas. Note that I am not running this OrderBy() call inside of a loop, at any scope.
private string GetAlphabetizedGuidString(Item i, Field f)
{
List<Item> items = new List<Item>();
StringBuilder scGuidBuilder = new StringBuilder();
if (i != null && f != null)
{
foreach (ID guid in ((MultilistField)f).TargetIDs)
{
Item target = Sitecore.Data.Database.GetDatabase("master").Items.GetItem(guid);
if (target != null && !string.IsNullOrEmpty(target.DisplayName)) items.Add(target);
}
// Sort it by item name.
items = items.OrderBy(o => o.DisplayName, StringComparer.InvariantCultureIgnoreCase).ToList();
// Build a string of pipe-delimited guids.
foreach (Item item in items)
{
scGuidBuilder.Append(item.ID);
scGuidBuilder.Append("|");
}
// Return string which is a list of guids.
return scGuidBuilder.ToString().TrimEnd('|');
}
return string.Empty;
}
I was able to reproduce your problem with the following code:
var strings = new string[] { "IC-30RID-LH", "IC-30RID-RH", "IC-30R-LH", "IC-30R-RH"};
var sorted = strings.OrderBy(s => s);
I was also able to get the desired sort order by adding a comparer to the sort.
var sorted = strings.OrderBy(s => s, StringComparer.OrdinalIgnoreCase);
That forces a character-by-character (technically byte-by-byte) comparison of the two strings, which puts the '-' (45) before the 'I' (73).
I have 2 lists, allCharges and selectedCharges, where each item in selectedCharges is contained in allCharges.
I want to create a third list (called charges) that is a copy of all charges, but with a boolean property of "Selected" for each item set to true if the charge is in the list of selected charges.
This is fine if I am only doing this once; however, I am trying to perform the same operation five times, rewriting "charges" each time while saving "charges" to my "typeSettingsList".
IList<TypeSettings> typeSettingsList = new List<TypeSettings>();
var typeId = 1;
while (typeId < 6)
{
var links = _linkChargeTypeRepo.Query().Where(l => l.TypeId == typeId);
var allCharges = _chargeRepo.GetAll().ToList();
var selectedCharges = links.Select(l => l.ChargeType).ToList();
selectedCharges.ForEach(c => c.Selected = true);
var nonSelectedCharges = allCharges.Except(selectedCharges).ToList();
nonSelectedCharges.ForEach(c => c.Selected = false);
var charges = nonSelectedCharges.Concat(selectedCharges).ToList();
var settingsWithType = new TypeSettings
{
Type = _typeRepo.Get(typeId),
Charges = charges
};
typeSettingsList.Add(settingsWithType);
typeId++;
}
return settingsWithType;
My problem is that each "Charges" object in my typeSettingsList ends up getting overwritten with the charges object that is created on the last iteration, even though the variable is declared inside the while loop (and should therefore be a new object reference with each iteration).
Is this just an incorrect understanding of how variables inside while loops should work?
How can I make it so my "charges" list isn't overwritten with each iteration?
The problem is that you do not "materializecharges`: this
var charges = nonSelectedCharges.Concat(selectedCharges);
is an IEnumerable<Charge> with deferred evaluation. By the time you get to evaluate Charges from the typeSettingsList, the loop is over, so enumerating the IEnumerable<Charge> returns the results for the last value of typeId (i.e. typeId = 5).
Add ToList() to fix this problem:
var charges = nonSelectedCharges.Concat(selectedCharges).ToList();
Edit: Another problem is that links is using typeId, which is modified in the loop. You should define a new variable inside the loop to capture the state of typeId during the specific iteration, like this:
var typeId = 1;
while (typeId < 6)
{
var tmpTypeId = typeId;
var links = _linkChargeTypeRepo.Query().Where(l => l.TypeId == tmpTypeId);
...
}
I've looked into various different ways of array's, arraylist's, dictionaries... but as I'm used to PHP I'm not entirely sure on the best way I could collect the following information.
My program loops through each user, and if their is a location ID, I want to add that to some sort of collection / array. It's expected that different users will have the same location ID.
If the location ID is the same, I need to increase an integer of how many occurrence for that location ID.
Example:
User1 - Location1
User2 - Location3
User3 - Location3
Location1 = 1
Location3 = 2
Also I need to somehow append each user ID to this collection. So Location3 / 2 occurrences / user2/user3
I've been trying to figure out the best way of doing this for about two hours now, and all the different methods of multidimensional arrays, arraylists, dictionaries is all a little confusing as it all seems abstract to my PHP knowledge. I think C# handles arrays in an entirely different way.
Essentially, the collection with unique location ID's / occurrences / and users collection needs to be stored in something that can be passed to somewhere else in my program as an argument.
I've made a PHP script which does exactly what I'm after
foreach($call["data"] as $v)
{
// Foreach USER ($v containing their unique ID and location ID.)
$user_id = $v["id"];
$location_id = $v["location"]["id"];
// This adds the location ID as the key within the array, followed by every user who has it. I don't need a count in this case, as I could just count the number of users.
$collection[$location_id][$user_id] = null;
}
This in return creates this array when printed using print_r
[106078429431815] => Array
(
[620790873] =>
[626276302] =>
[100000152470577] =>
)
(Small part of the output). - Added PHP Example.
Anyone know how I can get C# to collect the same information in the same way my PHP array does?
using System.Linq;
var grouppingByLocation = users.GroupBy(u => u.LocationID);
foreach (var g in grouppingByLocation)
{
Console.WriteLine("Location id: {0}", g.Key);
foreach (var u in g)
{
Console.WriteLine("User id: {0}", u.ID);
}
}
See Enumerable.GroupBy() for more details.
This is an Extension Method over IEnumerable<T> interface implemented by any built-in collection (such as Array T[], List<T>, Dictionary<K,V>, etc.) which accepts a lambda expression pointing to a property of class collection of which you're grouping by.
If you want to build the list looping through initial data, you can create object like this:
var list = new Dictionary<int, Tuple<int, List<int>>();
And fill it in the loop
if(list[locationID]==null) list[locationID] = Tuple.Create(0,new List<int>());
//..
list[locationId].Item1++; // counter
list[locationId].Item2.Add(userId); //list of users
Create an object to hold each item of data.
public Class Model{
public int LocationId {get;set;}
public int Occurences{get;set;}
public IList<User> Users{get;set;}
}
Initialize the container as a list of items.
var container = List<Model>();
Process you list of users.
foreach(var user in userList){
var model = container.SingleOrDefault(x=> x.LocationId == user.LocationId);
if(model != null){
model.Users.Add(user);
} else{
model = new Model{
model.Users = new List<User>.Add(user);
model.LocationId = user.LocationId;
container.Add(model)
}
model.Occruences ++;
}
}
var byLocation = users.Where(u => !string.IsNullOrEmpty(u.Location))
.GroupBy(u => u.Location);
var stats = byLocation.Select(l => string.Format("{0} / {1} occurrences / {2}",
l.Key, l.Count(), string.Join("/", l.Select(u => u.User)));
// And just to print the result
foreach (var location in stats)
Console.WriteLine(location);