How can I convert a ForEach into a For loop - c#

I am working on displaying an amount of reviews after a rating, the code I wrote works, but I realized it ends up displaying the command for each review, so if there are two reviews I see "1 Review 1 Review." I've been working on this for a while and am still a bit new to MVC, C#, etc, and cannot seem to figure out how to change this to a for loop that will count the ReviewCount and display it as a single line with 1 or more "reviews" indicated. I tried spreading this out but then the local variable can't be called, and that's where I am stuck!
#foreach (var review in item.Campaign.Reviews)
{
int ReviewCount = 0;
++ReviewCount;
if (ReviewCount > 1)
{
#Html.Raw(ReviewCount)
#Html.Raw(" Reviews")
}
else
{
#Html.Raw(ReviewCount)
#Html.Raw(" Review")
}
}

If you just want to count reviews, just use the LINQ Count() method:
#{ int ReviewCount = item.Campaign.Reviews.Count(); }
#Html.Raw(ReviewCount)
#if (ReviewCount != 1) {
#Html.Raw(" Reviews")
}
else {
#Html.Raw(" Review")
}
Also, be DRY.

Related

Get last column in Google Sheets API v4 in c#

I have seen many posts about finding the last row of a given column for Google Sheets API v4 in C#, but I can't seem to find anything about finding the last column of a given row. I didn't find any questions about this specifically - but if I'm mistaken please direct me to the right place.
In my sheet, I have headers for each column. Over time, I anticipate I will need to add or remove columns as needed - it would be great to not have to update my code every time this happens.
I'm at the beginning stages of writing my code that gathers my data from Google Sheets - but here is what I have so far. I know that I will need to change the way my variable "range" is written, just don't know what.
static void ReadEntries()
{
var range = $"{sheet}!A1:ET";
var request = service.Spreadsheets.Values.Get(SpreadsheetId, range);
var response = request.Execute();
var values = response.Values;
if(values != null && values.Count>0)
{
foreach (var row in values)
{
System.Diagnostics.Debug.WriteLine("{0} | {1} | {2}", row[0], row[1], row[2]);
}
}
else
{
System.Diagnostics.Debug.WriteLine("No data found.");
}
}
EDIT: SOLVED
I used the pseudo code provided by Nazi A for this. I was having issues with the if(row[col]) piece with casting and other system exceptions. It turns out foreach allows for us to not have to check if that row[col] is in range. Below is my final code in case anyone needs it in the future. I plan to let column "ET" declared in var range = $"{sheet}!A1:ET; be big enough to accommodate any future columns being added to my spreadsheet. Thanks for your help!
static void ReadEntries()
{
var range = $"{sheet}!A1:ET";
var request = service.Spreadsheets.Values.Get(SpreadsheetId, range);
var response = request.Execute();
var values = response.Values;
int max = 0;
int currMax;
if (values != null && values.Count>0)
{
foreach(var row in values)
{
currMax = 0;
foreach(var col in row)
{
currMax++;
}
if (max < currMax)
{
max = currMax;
}
}
}
else
{
System.Diagnostics.Debug.WriteLine("No data found.");
}
System.Diagnostics.Debug.WriteLine(max);
}
So basically, you need to have a nested loop to traverse all rows and columns in values.
I have tested this psuedo code and worked but since I have no any means to run a C# code, this is all I can give to you. This is a pseudo code that should be readable to you.
var max = 0;
foreach(var row in values){
var currMax = 0;
foreach(var col in row){
if(row[col]){ // as long as data exists, currMax will increment
currMax++;
continue;
}
break; // stop loop if last cell being checked is empty
}
if(max < currMax){ // assign the largest currMax to max
max = currMax;
}
}
So in this psuedo code, max will contain the value of the largest column of all rows in the range. this code above should replace your foreach call
If you have any questions, feel free to clarify below.

More efficient and readable nested loop

I've created an algorithm which weighs the relevance of a list of articles against two lists of keywords that correlate to attributes of the article.
It works great and is super efficient... but it's a mess. It's not terribly readable, so it's difficult to discern what's going.
The operation in pseudo code goes something like this:
Loop through every article in a list called articles(List<Article>)
For every article, loop through every role in a list of roles (List<string>)
Check to see if the current article has any roles (Article.Roles = List<string>)
If yes, then loop through each role in the article and try to match a role in the article to the role in the current loop
If a match is found, add weight to the article. If the index of the role on the article and the role in the roles list are both index 0 (in primary position) add extra weight for two matching primaries
Repeat for topics, but with no bonus for primary matches
What would be a better way to write the following code? I can't use foreach except in one or two places, because I need to match indexes to know what value to add on a match.
private static List<Article> WeighArticles(List<Article> articles, List<string> roles, List<string> topics, List<string> industries)
{
var returnList = new List<Article>();
for (int currentArticle = 0; currentArticle < articles.Count; currentArticle++)
{
for (int currentRole = 0; currentRole < roles.Count; currentRole++)
{
if (articles[currentArticle].Roles != null && articles[currentArticle].Roles.Count > 0)
{
for (int currentArticleRole = 0; currentArticleRole < articles[currentArticle].Roles.Count; currentArticleRole++)
{
if (articles[currentArticle].Roles[currentArticleRole].ToLower() == roles[currentRole].ToLower())
{
if (currentArticleRole == 0 && currentRole == 0)
articles[currentArticle].Weight += 3;
else
articles[currentArticle].Weight += 1;
}
}
}
}
for (int currentTopic = 0; currentTopic < topics.Count; currentTopic++)
{
if (articles[currentArticle].Topics != null && articles[currentArticle].Topics.Count > 0)
{
for (int currentArticleTopic = 0; currentArticleTopic < articles[currentArticle].Topics.Count; currentArticleTopic++)
{
if (articles[currentArticle].Topics[currentArticleTopic].ToLower() == topics[currentTopic].ToLower())
{
articles[currentArticle].Weight += 0.8;
}
}
}
}
returnList.Add(articles[currentArticle]);
}
return returnList;
}
//Article Class stub (unused properties left out)
public class Article
{
public List<string> Roles { get; set; }
public List<string> Topics { get; set; }
public double Weight { get; set; }
}
If you'll examine your code, you'll find that you are asking Article class to many times for data. Use Tell, Don't Ask principle and move weight adding logic to Article class, where it should belong. That will increase cohesion of Article, and make your original code much more readable. Here is how your original code will look like:
foreach(var article in articles)
{
article.AddWeights(roles);
article.AddWeights(topics);
}
And Article will look like:
public double Weight { get; private set; } // probably you don't need setter
public void AddWeights(IEnumerable<Role> roles)
{
const double RoleWeight = 1;
const double PrimaryRoleWeight = 3;
if (!roles.Any())
return;
if (Roles == null || !Roles.Any())
return;
var pirmaryRole = roles.First();
var comparison = StringComparison.CurrentCultureIgnoreCase;
if (String.Equals(Roles[0], primaryRole, comparison))
{
Weight += PrimaryRoleWeight;
return;
}
foreach(var role in roles)
if (Roles.Contains(role, StringComparer.CurrentCultureIgnoreCase))
Weight += RoleWeight;
}
Adding topics weights:
public void AddWeights(IEnumerable<Topic> topics)
{
const double TopicWeight = 0.8;
if (Topics == null || !Topics.Any() || !topics.Any())
return;
foreach(var topic in topics)
if (Topics.Contains(topic, StringComparer.CurrentCultureIgnoreCase))
Weight += TopicWeight;
}
Okay, you have several design flaws in your code:
1 - It's too procedural. You need to learn to think to write code to tell the machine "what you want" as opposed to "how to do it", similar to the analogy of going to a bar and instructing the bartender about the exact proportions of everything instead of just asking for a drink.
2 - Collections Should NEVER be null. Which means that checking for articles[x].Roles != null makes no sense at all.
3 - iterating on a List<string> and comparing each with someOtherString makes no sense either. Use List<T>.Contains() instead.
4 - You're grabbing each and every one of the items in the input list and outputting them in a new list. Also nonsense. Either return the input list directly or create a new list by using inputList.ToList()
All in all, here's a more idiomatic C# way of writing that code:
private static List<Article> WeighArticles(List<Article> articles, List<string> roles, List<string> topics, List<string> industries)
{
var firstRole = roles.FirstOrDefault();
var firstArticle = articles.FirstOrDefault();
var firstArticleRole = firstArticle.Roles.FirstOrDefault();
if (firstArticleRole != null && firstRole != null &&
firstRole.ToLower() == firstArticleRole.ToLower())
firstArticle.Weight += 3;
var remaining = from a in articles.Skip(1)
from r in roles.Skip(1)
from ar in a.Roles.Skip(1)
where ar.ToLower() == r.ToLower()
select a;
foreach (var article in remaining)
article.Weight += 1;
var hastopics = from a in articles
from t in topics
from at in a.Topics
where at.ToLower() == t.ToLower()
select a;
foreach (var article in hastopics)
article.Weight += .8;
return articles;
}
There are even better ways to write this, such as using .Take(1) instead of .FirstOrDefault()
Use the Extract Method refactoring on each for loop and give it a semantic name WeightArticlesForRole, WeightArticlesForTopic, etc. this will eliminate the nested loops(they are still there but via function call passing in a list).
It will also make your code self documenting and more readable, as now you have boiled a loop down to a named method that reflects what it accomplishes. those reading your code will be most interested in understanding what it accomplishes first before trying to understand how it accomplishes it. Semantic/conceptual function names will facilitate this. They can use GoTo Definition to determine the how after they udnerstand the what. Provide the summary tag comment for each method with elaborated explanation(similar to your pseudo code) and now others can wrap their head around what your code is doing without having to tediously read code they aren't concerned with the implementation details of.
The refactored methods will likely have some dirty looking parameters, but they will be private methods so I generally don't worry about this. However, sometimes it helps me see what dependencies are there that should probably be removed and restructure the code in the call such that it can be reused from multiple places. I suspect with some params for the weighting and delegate functions you might be able to combine WeightArticlesForRole and WeightArticlesForTopic into a single function to be reused in both places.

foreach where data flips sides every other

Is it possible to say: if the image is on the left the next one should be on the right, repeat until done?
Pseudo code:
#foreach (var item in Model.Items)
{
if (previous class=imageLeft)
{
<div class="imageRight">Right Image</div>
<div>Left Content</div>
}
else
{
<div class="imageLeft">Left Image</div>
<div>Right Content</div>
}
}
Yes, of course. You're pseudo code is almost there. Just use a variable to hold the value and remember to update its value in each iteration:
#int c = 0;
#foreach (var item in Model.Items)
{
if (c == 0)
{
<div class="imageRight">Right Image</div>
<div>Left Content</div>
c = 1;
}
else
{
<div class="imageLeft">Left Image</div>
<div>Right Content</div>
c = 0;
}
}
alternatively, you can use #foreach (int i = 0; i < Model.Items.Count; i++) loop and then use i % 2 to get the even/odd row numbers and apply the class as you like.
Personally, I would create a helper for this. The helper keeps the code clean and makes inner loops much easier to handle. Starts to save you some time once your applications grow in size, especially if you copy a working example. Here's mine:
public class LooperHelper
{
private int Iterations { get; set; } = 0;
public void AddTick() {
Iterations++;
}
public int GetTicks()
{
return Iterations;
}
public bool DivisibleBy(int divisor)
{
return (Iterations % divisor == 0);
}
public string ConditionalOutput(string stringoutput, int divisor, bool reverse = false)
{
return ((Iterations % divisor == 0) == !reverse ? stringoutput : "");
}
}
This allows you to do nested conditional formatting based on the number of iterations:
var outerloophelper = new LooperHelper();
var innerloophelper = new LooperHelper();
foreach (var product in products)
{
outerloophelper.AddTick();
<div class="row #(outerloophelper.ConditionalOuput("alternative", 2, true))">
#foreach (var subproduct in product.SubProducts)
{
innerloophelper.AddTick();
<div class="4u#(innerloophelper.ConditionalOutput("$", 3)) 12u$(small)">
#subproduct.ToString()
</div>
}
</div>
}
In the above example, every second product is decorated with the class "alternative" (where the first is marked alternative as reverse is set true). The nested sub-product items are further decorated with the addition of a $ sign to the class to denote the change of the row for every third column. But you can do also other nifty things, such as making sure that a hr-tag is added in between, but not after the final looped element, like in the following example:
#if (!outerloophelper.DivisibleBy(products.Count())) { <hr />}
In short, when initialising the helper, it begins with 0 and positioning the AddTick() method within the code allows you to adjust whether iterations begin with a one or a zero.
GetTicks() is great if you want to e.g. number your results,
DivisibleBy() returns a boolean result that you can use it in your own logic, and
ConditionalOuput() will print out the text should the statement hold. Use reverse so the output is when the row is not divisible.

Selected column in For Loop ASP.NET MVC3

I have a following function in a controller. It is called from index.cshtml. var relays works fine and I got the replays.count(). PRESETDETAILS has some columns from which I want to access specific columns in a loop (like I have mentioned in response.write).
Kindly tell me how I can get the specific columns in the for loop.
#region "Apply Preset Handlers"
public ActionResult btnApply_Click(int? id)
{
var relays = db.PRESETDETAILS.ToList().Where(t => (t.PRESETID).Equals(id));
for (int k = 0; k < relays.Count(); k++)
{
Response.Write(relays.Select(s => new relays{PRESETDETAILID = s.PRESETDETAILID }).ToString());
}
return View("Index");
}
#endregion
you need to loop through them, you're simply select the same things over and over...
var relays = db.PRESETDETAILS.Where(t => t.PRESETID == id).ToList();
foreach (var replay in replays)
{
Response.Write(string.Format("{0}", relay.ID));
}
NOW... looking at your code:
always use ToList() at the end of the query;
ToList() actually makes the call to the database, until then, it's just a promise
Don't use Response.Write in your Controller, send the data to the View instead
your code should be:
public ActionResult btnApply_Click(int? id)
{
var model = db.PRESETDETAILS.Where(t => t.PRESETID == id).ToList();
return View(model);
}
in your View you can loop through the records
#model List<YOUR_CLASS>
<ul>
#foreach(var replay in Model)
{
<li>#replay.ID</li>
}
</ul>
I can see that some MVC conventions are not yet in place with you, and I know it can be somewhat overwhelm from someone that comes from WebForms or a linear code approach, but why don't you take some time and check the FREE available course about ASP.MVC in the ASP.NET Website?
See the videos in the right side of this page: http://www.asp.net/mvc

ASP.NET MVC 3 - Search with multiple terms

I have a method where by I'm able to search in a database for a specific customer(s). At the moment it only takes 1 term, but i'd like to be able to search with multiple terms (for example the customer's account number and their name). Below is my method:
public List<AXCustomer> allCustomers(string id)
{
string[] searchstring = id.Split(' ');
List<AXCustomer> customer = new List<AXCustomer>();
// if 3 terms are entered
if (searchstring.Length > 2)
{
}
// if 2 terms are entered
else if (searchstring.Length > 1)
{
}
// revert back to default search
else
{
customer = context.AXCustomers.Where(x => x.ACCOUNTNUM.Contains(id) ||
x.NAME.Contains(id) || x.ZIPCODE.Contains(id)).ToList();
}
return customer;
}
As you can see, i've decided to split each term entered (I assume each term will be seperated by a space) but I'm not sure how my LINQ query should be for terms longer than one. Any help would be appreciated
Since you don't know what will be entered or how long it will be, I would suggest doing the following:
public List<AXCustomer> allCustomers(string id)
{
string[] searchstring = id.Split(' ');
List<List<AXCustomer>> customerlists = new List<List<AXCustomer>>();
foreach (string word in searchstring)
{
customerlists.Add(context.AXCustomers.Where(x => x.ACCOUNTNUM.Contains(word) || x.NAME.Contains(word) || x.ZIPCODE.Contains(word)).ToList());
}
//Then you just need to see if you want ANY matches or COMPLETE matches.
//Throw your results together in a List<AXCustomer> and return it.
return mycombinedlist;
}
Any matches = throw all lists together, then take the distinct ones.
Complete matches = you'll have to check for items which occur in all customerlists.
It will work fine. I am using a similar type of query in my project and it seems to work great. Following is the code snippet
PagedList.IPagedList<Product> PagedProducts = dbStore.Products.Where(p => p.Name.Contains(query) || p.MetaKeywords.Contains(query)).ToList().ToPagedList(pageIndex, PageSize);
BTW, its running on a live server too.
You can dynamically attach as many conditions as you wish, in the following manner:
customer = context.AXCustomers.Where(x => x.ACCOUNTNUM.Contains(id));
customer = customer.Where(Condition 2);
customer = customer.Where(Condition 3);
And so on. You have full control over the criteria : just make sure that it resolves to a sequel query.

Categories