Loop through Object 2 values to find if it match with Object 1 min variable - algorithm

So i am really stuck with this exercise for a few hours. The idea is to find the friends name who is also studying the weakest subject. I have this piece of code here:
const reportCard = {
Biology: 85,
English: 75,
Economics: 90,
History: 67,
Philosophy: 98,
};
const subjects = {
Dylan: 'Biology',
Jen: 'English',
Emily: 'Economics',
Amy: 'History',
Lenny: 'Philosophy',
};
console.log(studyBuddy(reportCard, subjects));
I have implemented a min variable to find the lowest grades in first object like so:
val = Object.values(reportCard)
const min = Math.min(...val)
How can i match the second object values with the first one that match the min variable. Is it anyway that i can implement that?
I just want a hint i don't want that somebody to solve it for me.

I will try to help you without actually solving the task.
I would first take keys of the friends object, go through them and use as a key to access reportCard object. Let me give you a quick example:
const studentNames = Object.keys(subjects); // all student names
studentNames.forEach(student => {
const subjectValue = reportCard[student]; // now you have the value for every student
});
Assuming the max value for subject is 100, you can define variable equal to 101 at first step, and check it in the loop if any of the student has less value. I hope that will help.

I implemented it like this:
const studyBuddy = (reportCard, subjects) => {
// Write code here
val = Object.values(reportCard)
const min = Math.min(...val)
const weakestSubject = Object.keys(reportCard).find(key => reportCard[key] === min);
for (let val in subjects){
if (subjects[val] === weakestSubject){
return val
} else {
return 'Me'
}
}
};
The challenge criteria is actually to return the friends name, else to return string 'Me'. I don't know why it is returning me? When i delete the else statement it actually returns the right name. What's wrong?

Idea is to get the weakest subject name from the object 'reportCard'; you can loop through the object, mark the subject which has the lowest marks.
Now, after getting the lowest marks from the object 'reportCard', again loop through the second object 'subjects' to check who is assigned to the subject, which has lowest marks we obtained by a loop through the object 'reportCard'.
const reportCard = {
Biology: 85,
English: 75,
Economics: 90,
History: 67,
Philosophy: 98,
};
const subjects = {
Dylan: 'Biology',
Jen: 'English',
Emily: 'Economics',
Amy: 'History',
Lenny: 'Philosophy',
};
function studyBuddy(reportCard, subjects){
var weakestSubjectName = null;
var min = Number.MAX_VALUE;
for(var key in reportCard){
// check every marks whether it is lowest than the prev lowest marks or not
// if so, assign the subject name which has currently lowest marks
if(reportCard[key] < min){
min = reportCard[key];
weakestSubjectName = key;
}
}
// now you can loop through the second object, to get the name of person/friend who's studying the subject
for(var key in subjects){
if(subjects[key] === weakestSubjectName) return key;
}
return null;
}
console.log(studyBuddy(reportCard, subjects));

Related

App Script For Loop is Slow Need to optimize code for faster update

This code works perfectly but its slow like turtle. Generally I don't take this approach but I was not able to find any other option.
Well my requirement is to where ever in the column code finds 1 get the key and value of the that index defined range and go to To sheet find the key and paste the value in the required columns.
If I can jump to cells where 1 is in selected column and get the index to find the key and value and then jump to To page key column where key is instead of going to every cell and checking for it whether it is there or not.
I am new to app script and little help would be great.
Thank you in advance.
function Data_Update(){
//assigning sheet name to variables
var ss = SpreadsheetApp.getActive()
var from = ss.getSheetByName("From Sheet")//update here from sheet name(Updated)
var To= ss.getSheetByName("To Sheet")//update here to sheet name(Updated)
// Creating Loop
for (var i = 4;i<=7000;i++){//Update here from which row to start
//assigning values to check if there is 1 in column K
var udaterang = from .getRange("K"+i).getValue()//update here from which column to check for
Logger.log(i)
// Checking condition if the value is diffrent from the value already is
if (udaterang == 1) {
//creating key to find the value
var name1 = from .getRange("A"+i).getValue()//update here the key column 1
var name2 = from .getRange("B"+i).getValue()//update here the key column 2
var name3 = from .getRange("C"+i).getValue()//update here the key column 3
var name = name1.trim()+name2.trim()+name3.trim()
var rng = from .getSheetValues(i,4,1,7)//start row, start column, # rows, # columns
// Looping through each cell to check if the data needs update
for(var j=2;j<=12500;j++){
var key = To.getRange("AP"+j).getValue()
if(key == name){ //[1] because column B
To.getRange("AI"+j+":"+"AO"+j).setValues(rng)
break
}
}
}
}
}
Try this:
function Data_Update() {
const ss = SpreadsheetApp.getActive();
const fsh = ss.getSheetByName("From Sheet");
const fkvs = fsh.getRange(4, 11, fsh.getLastRow() - 3).getValues().flat();
const fvs = fsh.getRange(4, 1, fsh.getLastRow() - 3, fsh.getLastColumn()).getValues();
const tapvs = tsh.getRange(2, 42, tsh.getLastRow() - 1).getValues().flat();
fkvs.forEach((k, i) => {
let udaterang = k;
if (k == 1) {
let name = fvs[i][0].toString().trim() + fvs[i][1].toString().trim() + fvs[i][2].toString().trim();
let idx = tapvs.indexOf(name);
if (~idx) {
tsh.getRange(idx + 2, 35, 1, 7).setValues(tsh.getRange(i + 2, 4, 1, 7).getValues());
}
}
});
}
This may take some tweaking because I have not tested this as I don't have the data to do so.

For loop to check values from two spreadsheets

I have two spreadsheets:
Column A on sheet 6th&7thRoster lists all IDs in a sample, contains 853 items.
Column C on sheet alreadySubmitted contains the IDs of users who've completed a task. Contains 632 items.
I'm trying to parse through both columns. If a user from Column A of sheet 6th&7thRoster matches a user from Column C of sheet sandboxAlreadySubmitted, I want to write the word "Yes" on Column I of the current row of sheet 6th&7thRoster. When using the code below, I'm not seeing not seeing any instances of the word "Yes" on Column I of 6th&7thRoster, even though I know there's multiple places where that should be the case.
function checkRoster() {
var mainSheet = SpreadsheetApp.openById('XXXXXXX');
var roster = mainSheet.getSheetByName('6th&7thRoster');
var submissions = mainSheet.getSheetByName('alreadySubmitted');
var rosterLastRow = roster.getLastRow();
var submissionsLastRow = submissions.getLastRow();
var rosterArray = roster.getRange('A2:A853').getValues();
var submissionsArray = submissions.getRange('C2:C632').getValues;
var i;
var x;
for (i = 1; i < 853; i++) {
for (x = 1; x < 632; x++){
if (rosterArray[i] == submissionsArray[x]){
roster.getRange(i, 9).setValue("Yes");
}
}
}
}
Feedback on how to solve and achieve this task will be much appreciated. For confidentiality, I cannot share the original sheets.
You want to compate the values of A2:A853 of 6th&7thRoster and C2:C632 of alreadySubmitted.
When the values of C2:C632 of alreadySubmitted are the same with the values of A2:A853 of 6th&7thRoster, you want to put Yes to the column "I".
If my understanding is correct, how about this modification? Please think of this as just one of several possible answers.
Modified script:
function checkRoster() {
var mainSheet = SpreadsheetApp.openById('XXXXXXX');
var roster = mainSheet.getSheetByName('6th&7thRoster');
var submissions = mainSheet.getSheetByName('alreadySubmitted');
var rosterLastRow = roster.getLastRow();
var submissionsLastRow = submissions.getLastRow();
var rosterArray = roster.getRange('A2:A853').getValues();
var submissionsArray = submissions.getRange('C2:C632').getValues(); // Modified
// I modified below script.
var obj = submissionsArray.reduce(function(o, [v]) {
if (v) o[v] = true;
return o;
}, {});
var values = rosterArray.map(function([v]) {return [obj[v] ? "Yes" : ""]});
roster.getRange(2, 9, values.length, values[0].length).setValues(values);
}
Flow:
Retrieve values from A2:A853 of 6th&7thRoster and C2:C632 of alreadySubmitted.
Create an object for searching the values from the values of alreadySubmitted.
Create the row values for putting to 6th&7thRoster.
References:
reduce()
map()
If I misunderstood your question and this was not the direction you want, I apologize.

Using Linq to check if generic list in Dictionary contains a value

How do I check if a dictionary already contains a set of coordinates? I am using SelectManager and Where but the count is still wrong. I don't think its checked the values in the the generic list.
I want it to iterate through the matchTile and check every other elements in the generic list for a condition (not part of question).
If it meets that condition, it get adds it to the same group. When the next iteration comes, it should check if that element has already been added to a group. If yes, it skips it. If not, it creates a new group (List).
Can anyone help?
private void groupMatches(List<int[]> matchTile){
Dictionary<int, List<int[]>> groups = new Dictionary<int, List<int[]>>();
int i = 0;
foreach(int[] coord in matchTile){
var alreadyCounted = groups.SelectMany(x => x.Value).Where(x => x[0] == coord[0] && x[1] == coord[1]);
Debug.Log ("counted: " + alreadyCounted.Count ());
if(alreadyCounted.Count() > 0) continue;
// Create new group
groups.Add(i, new List<int[]>());
groups[i].Add(coord);
foreach(int[] nextCoord in matchTile){
if (...)
{
groups[i].Add(nextCoord);
}
}
i++;
}
Debug.Log ("groups: " + groups.Count);
}
Try next code:
private void groupMatches(List<int[]> matchTile)
{
Dictionary<int, List<int[]>> groups
= matchTile.GroupBy(line => new{x = line[0], y = line[1]})
.Select((grp, index) => new{index, grp})
.ToDictionary(info => info.index, info => info.grp.ToList());
Debug.Log("groups: " + groups.Count);
}

Row number in LINQ

I have a linq query like this:
var accounts =
from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new AccountsReport
{
recordIndex = ?
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
}
I want to populate recordIndex with the value of current row number in collection returned by the LINQ. How can I get row number ?
Row number is not supported in linq-to-entities. You must first retrieve records from database without row number and then add row number by linq-to-objects. Something like:
var accounts =
(from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new
{
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
})
.AsEnumerable() // Moving to linq-to-objects
.Select((r, i) => new AccountReport
{
RecordIndex = i,
CreditRegistryId = r.CreditRegistryId,
AccountNumber = r.AccountNo,
});
LINQ to objects has this builtin for any enumerator:
http://weblogs.asp.net/fmarguerie/archive/2008/11/10/using-the-select-linq-query-operator-with-indexes.aspx
Edit: Although IQueryable supports it too (here and here) it has been mentioned that this does unfortunately not work for LINQ to SQL/Entities.
new []{"aap", "noot", "mies"}
.Select( (element, index) => new { element, index });
Will result in:
{ { element = aap, index = 0 },
{ element = noot, index = 1 },
{ element = mies, index = 2 } }
There are other LINQ Extension methods (like .Where) with the extra index parameter overload
Try using let like this:
int[] ints = new[] { 1, 2, 3, 4, 5 };
int counter = 0;
var result = from i in ints
where i % 2 == 0
let number = ++counter
select new { I = i, Number = number };
foreach (var r in result)
{
Console.WriteLine(r.Number + ": " + r.I);
}
I cannot test it with actual LINQ to SQL or Entity Framework right now. Note that the above code will retain the value of the counter between multiple executions of the query.
If this is not supported with your specific provider you can always foreach (thus forcing the execution of the query) and assign the number manually in code.
Because the query inside the question filters by a single id, I think the answers given wont help out. Ofcourse you can do it all in memory client side, but depending how large the dataset is, and whether network is involved, this could be an issue.
If you need a SQL ROW_NUMBER [..] OVER [..] equivalent, the only way I know is to create a view in your SQL server and query against that.
This Tested and Works:
Amend your code as follows:
int counter = 0;
var accounts =
from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new AccountsReport
{
recordIndex = counter++
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
}
Hope this helps.. Though its late:)

Linq query for data aggregation

I have this class
public class Line
{
public string ConnectionsIndex{get;set;}
}
my Linq problem is that I have to aggregate these Lines
var l1 = new Line{ ConnectionsIndex="01,02"};
var l2 = new Line{ ConnectionsIndex="02,03"};
var l3 = new Line{ ConnectionsIndex="01,03"};
into this
var l4 = new Line{ ConnectionsIndex="01,02,03"};
It's possible to do with Linq?
DETAIL:
The thing is more complicate (at least for me) when I add the other items that I have in my collection.
var l5 = new Line (ConnectionsIndex = "02,04");
var l6 = new Line (ConnectionsIndex = "03,06");
because do not exist other lines with the pairs 03,04 , 01,04 , 01,06 and 02,06
I do not know if I have explained it well ...
in practice, imagine you have all the points of a polygon, I want to get a line of all the items from the query by giving a list of connections between all points of each polygon.
(my list contains more than one polygon)
One point should not be included in result if not connected to all others.
This is an example of my list content:
ConnectionsIndex="166,171"
ConnectionsIndex="166,174"
ConnectionsIndex="166,333"
ConnectionsIndex="169,170"
ConnectionsIndex="171,175"
ConnectionsIndex="171,334"
ConnectionsIndex="167,174"
ConnectionsIndex="172,174"
ConnectionsIndex="174,335"
ConnectionsIndex="177,341"
ConnectionsIndex="180,200"
ConnectionsIndex="181,183"
ConnectionsIndex="182,199"
ConnectionsIndex="184,185"
ConnectionsIndex="186,188"
ConnectionsIndex="189,192"
ConnectionsIndex="190,230"
ConnectionsIndex="191,375"
In this List you have for example a triangle between 166, 171 and 334
More detail:
var group = lines.Where(x => x.ConnectionsIndex.Split(',').Contains(line. ConnectionsIndex.Split(',')[0]) ||
x. ConnectionsIndex.Split(',').Contains(line. ConnectionsIndex.Split(',')[1])).ToList();
if (group.Count()==1)
{
straight_lines.Add(line);
}
else
{
//Here I have a "group" with all the lines between point.. I want to get distinc points
}
Something like:
var connections = (from line in lines
from connection in line.Split(',')
select connection).Distinct()
.ToArray();
Line line = new Line { ConnectionsIndex = string.Join(",", connections) };
This doesn't order the connections, but you can easily add that if you need it.
This would all be cleaner if you were happy to have ConnectionsIndex as a collection of strings instead of a single delimited string, of course :)
I used this:
var l4 = new Line{
ConnectionsIndex =
string.Join(",", (lines.SelectMany(x => x.ConnectionsIndex.Split(','))
.Distinct()
.OrderBy(s => s)).ToArray())
};
This is the bad way I have found... and it works!
var l = linee.Distinct(
(a, b) => a.ConnectionsIndex == b.ConnectionsIndex,x=>x.ConnectionsIndex.GetHashCode())
.ToList();
var single_lines = new List<Linea>();
var multiple_lines = new List<Linea>();
foreach (var linea in l)
{
var group = l
.Where(x => x.ConnectionsIndex.Split(',').Contains(linea.ConnectionsIndex.Split(',')[0]) ||
x.ConnectionsIndex.Split(',').Contains(linea.ConnectionsIndex.Split(',')[1])).ToList();
if (group.Count()==1)
{
single_lines.Add(linea);
}
else
{
var indexes = new List<string>();
var dist = group.Select(x => new {Index = x.ConnectionsIndex.Split(',').ToList()}).ToList();
foreach (var linea1 in dist)
{
indexes=indexes.Concat(linea1.Index).ToList();
}
var indexstring = new StringBuilder();
foreach (var s in indexes.Distinct().OrderBy(x=>Convert.ToInt32(x)))
{
indexstring.Append(s).Append(',');
}
indexstring.Remove(indexstring.Length - 1, 1);
multiple_lines.Add(new Linea() {ConnectionsIndex = indexstring.ToString()});
}
}
var multi_distinct=multiple_lines.Distinct(
(a, b) => a.ConnectionsIndex == b.ConnectionsIndex, x => x.ConnectionsIndex.GetHashCode())
.ToList();
linee = single_lines.Concat(multi_distinct).ToList();
If you find or known better solutions, you are welcome!
Just a note that I think what you're asking for is a way to find maximal cliques (a concept from graph theory). This is known to be an NP-Hard problem. I think your version will work sometimes, and hopeflly for those cases you're interested in. But, not for complicated cases where anything may be connected to anything else. Indeed, if you have a lot of nodes, those cases aren't feasible, even with large CPU cycle budgets (regardless of LINQ).

Resources