Functions Invoking other Functions exercise I'm stumped on - javascript

First-time poster here and have run into a speed bump in my pre-work for a 6-month full-stack boot camp I'm enrolled in for November.
I'm working on some exercises on repl.it and this one is on javascript functions. You're told to write 3 functions called mealMaker, slice, and cook.
You're given an empty array and are told to fill it with objects like so:
const arrayOfFoodObjects = [
{
"food": "beef",
"type": "meat"
},
{
"food": "zucchini",
"type": "vegetable"
},
{
"food": "bacon",
"type": "meat"
},
{
"food": "okra",
"type": "vegetable"
}
];
They want you to have the cook function take all the objects that have "type": "meat" and return a string that says "Cooked ("food": value)" (e.g. "Cooked beef") and similarly with the slice function for "type": "vegetable" they want "("food": value) slices" (e.g. "Okra slices").
Then the mealMaker function takes what those functions spit out and creates an array as such: ["Cooked beef", "Okra slices" ...].
Where I'm stuck is I wrote a .filter() function that just returns a filtered array of those objects which I soon realized wouldn't serve its purpose. I guess I'm trying to figure out how to write a function so I can filter the meat and vegetables separately and then have them spit out the required string.
What's confusing me is how to target the "food" value and plug it into a certain string after filtering with the "type" value.
This is the rest of the code I have written so far which may or may not help.
var redMeat = arrayOfFoodObjects.filter(function(cook) {
return cook.type == "meat";
});
var veggies = arrayOfFoodObjects.filter(function(slice) {
return slice.type == "vegetable";
});
console.log(veggies, redMeat)
With the console just looking like:
[ { food: 'zucchini', type: 'vegetable' },
{ food: 'okra', type: 'vegetable' } ] [ { food: 'beef', type: 'meat' },
{ food: 'bacon', type: 'meat' } ]
I'm probably not tackling this the right way as I've spent a good amount of time trying different things I had found on Google and applying them as best I could but this was the closest I managed to get. Any help is greatly appreciated, thanks.
PS I'm not super familiar with this format of a function as I came up with this through some searches on Google. If someone wouldn't mind explaining how this may differ from the function format I'm used to seeing, that'd be awesome. I'm not sure about which part of it is the "name" of the function. The functions I've worked with so far typically look like:
function nameOfFunction(value(s)) {
*action*;
}

You are not doing what they ask.
They want a cook function and a slice function:
function cook(arr){
//for each element of the array, return its mapped value (they ask a string)
return arr.map( function(foodObject){
return `Cooked ${foodObject.food}`
})
}
function slice(arr){
//do it
}
let cooks = cook(arrayOfFoodObjects)
let slices = slice(arrayOfFoodObjects)
then feed what the function spit out to mealMaker (as instructed):
function mealMaker(cooks, slices){
return cooks.map( function(cook, idxCook){
let slice = slices[idxCook];
//guess what to do with cook and slice
})
}
mealMaker(cooks, slices)

I think something alone these lines is requested:
const cook = product => "cooked " + product.food;
const slice = product => product.food + " slices";
const mealMaker = (products) => {
const meatProducts = products.filter(product => product.type === "meat");
const veggieProducts = products.filter(product => product.type === "vegetable");
return [
...cook(meatProducts),
...slice(veggieProducts)
];
}
mealMaker(arrayOfFoodObjects);
Notice the fat arrow syntax for writing functions. How it is different compared to regular functions, is explained here on Mozilla.

Welcome holdenprkr!
I think you are on the right track! For now, we have a way to get an array of veggies and another one for meats:
var redMeat = arrayOfFoodObjects.filter(function(cook) {
return cook.type == "meat";
});
var veggies = arrayOfFoodObjects.filter(function(slice) {
return slice.type == "vegetable";
});
So far so good, now we want a cook function that takes our readMeat array, and converts it to a array of strings. So, something in the lines of:
function cook(readMeatsArray) {
// convert readMeatsArray to cookedMeatsArray
// [{'food': 'beef', 'type': 'meat'}, ...]
// to
// ['Cooked beef', ...]
}
And then a slice function for the veggies:
function slice(veggiesArray) {
// convert veggiesArray to slicedVeggiesArray
// [{'food': 'okra', 'type': 'vegetable'}, ...]
// to
// ['Okra slices', ...]
}
So, if we combine this in an mealMaker function we now have:
function mealMaker() {
// First we get our arrays
var redMeat = arrayOfFoodObjects.filter(function(cook) {
return cook.type == "meat";
});
var veggies = arrayOfFoodObjects.filter(function(slice) {
return slice.type == "vegetable";
});
// Then we convert our object arrays to string arrays
var cookedMeats = cook(redMeat);
var slicedVeggies = slice(veggies);
// Now we combine the resulting arrays and return it
var mealArray = cookedMeats.concat(slicedVeggies);
return mealArray;
}
This would be one approach, hope it helps.
PD: I left the functions cook and slice empty on purpose, you can get some inspiration from user753642's answer ;)

Related

JavaScript array of huge data (500K record) taking time to search and add

My function is called in loop which returns more than 500k record.
I have to insert that record in a JavaScript array. Before inserting records to array need to check existing array has duplicate records or not. If the record is duplicate then exclude the record.
When array size increases the run time of the function is very high. Please suggest me a way to optimize search.
function AddDataToArray(StdName, currObjectSTD, bufferObject, attributes, bufferSTD) {
var result = false;
var existingObjects = AllDataArray.find(item => {
item.OBJECTID==attributes.OBJECTID
&& item.name == bufferObject.name
&& item.StdName == StdName);
});
if (existingObjects.length == 0) {
var currentObject = {
"ID": 0,
"currObjectSTD": currObjectSTD,
"color": bufferObject.color,
"name": bufferObject.name,
"attributes": attributes,
"StdName": StdName,
"objectID": objectID,
"bufferSTD": bufferSTD,
"shape": null,
"shapeSTD": null
};
AllDataArray.push(currentObject);
result = true;
}
return result;
}
As a speedup workaround I suggest you coming up with some kind of hash map based on your array to avoid continuos looping through array
const dataHashMap = _(AllDataArray)
.keyBy(item => `${item.OBJECTID}-${item.name}-${item.StdName}`)
.mapValues(() => true)
.value();
var existingObjects = dataHashMap[`${attributes.OBJECTID}-${bufferObject.name}-${StdName}`]
or alternative solution
let groupedDataHashMap = {}
AllDataArray.forEach(item => {
_.set(
groupedDataHashMap,
[
item.OBJECTID,
item.name,
item.StdName
],
true
)
})
var existingObjects = _.get(
groupedDataHashMap,
[
attributes.OBJECTID,
bufferObject.name,
StdName
],
false
)
I used lodash methods but if you prefer using native array/object methods you can come up with your own implementation, but the idea is the same
P.S you need to create this hash map once you fetched your array and populate it with new items simultaneously with your array to keep it up-to-date with your array

How can I access multiply nested sub objects in javascript?

I have a larger code which handles and sorts data. In it I want to work with objects to keep it easier and better structured. I have multiple categories and all of them have different nested subobjects, which I have trouble accessing writing/reading.
I searched on the web, w3schools but couldn't find my mistake, so sry for this entry level question!
I wrote a test function to better understand objects!
function test(){
var report, time, name, date, value;
report = 'Income Statement';
time = 'Annually';
name = 'Revenue';
date = '2017';
value = '10000000';
data = {}
data[report] = {}
data[report][time] = {}
data[report][time][name] = {}
data[report][time][name][date] = value;
console.log(data);
}
As to my understanding what this code does is:
-create an empty object data
-create an empty subobject report
-create an empty subsubobject time
-create an empty subsubsubobject name
-gives the subsubsubobject name a key/value pair date:value
(at least that was my intention to do)
First I tried to skip creating empty objects and directly fill data{} with:
data = {}
data[report][time][name][date] = value; but he seems to cannot set properties to this.
So I created like above coded first empty subobjects for all subcategories, is this really necessary or am I falling for a simple syntax mistake?
However he still doesn't log me the desired output which would be:
{ 'Income Statement': { Annually: { Revenue: {2017:10000000} } } }
and instead gives me:
{ 'Income Statement': { Annually: { Revenue: [Object] } } }
Simply put.. what am I doing wrong? :D
Thanks in advance for any kind of help!
Best regards
I don't think you are doing anything wrong. I pasted same code in JS console and it is giving proper result.
Screenshot of console with result of function
Different ways to initialize object
Static Data
let data = {
'Income Statement': {
'Annually': {
'Revenue': {
'2017': '10000000'
}
}
}
}
document.querySelector("#data-result").innerHTML = JSON.stringify(data)
<div id="data-result"></div>
Dynamic Data
var report, time, name, date, value;
report = 'Income Statement';
time = 'Annually';
name = 'Revenue';
date = '2017';
value = '10000000';
let data = {
[report]: {
[time]: {
[name]: {
[date]: value
}
}
}
}
document.querySelector("#object-result").innerHTML = JSON.stringify(data)
<div id="object-result"></div>
You can also consider different ways to store same data.
Example -
let data = [{
report: 'Income Statement'
time: 'Annually'
name: 'Revenue'
date: '2017'
value: '10000000'
}]
So now, if you want data by date in future you can get that by using filter
let data_2017 = data.filter(x => x.date === '2017');
It is correct !! I received { Income Statement: { Annually: { Revenue: {2017:10000000} } } } at console as an output with your given code.
Are u trying to save that data in some variable using test() ??
If yes then you need to use return data at the end of the definition on the function test instead of consol.log(data).

Preventing duplicate objects from being added to array?

I am building a little shop for a client and storing the information as an array of objects. But I want to ensure that I am not creating "duplicate" objects. I have seen similar solutions, but perhaps it is my "newness" to coding preventing me from getting the gist of them to implement in my own code, so I'd like some advice specific to what I have done.
I have tried putting my code in an if look, and if no "part", my variable looking for part number, exists in the code, then add the part, and could not get it to function.
Here is the function I am working on:
function submitButton(something) {
window.scroll(0, 0);
cartData = ($(this).attr("data").split(','));
arrObj.push({
part: cartData[0],
description: cartData[1]
});
}
arrObj is defined as a global variable, and is what I am working with here, with a "part" and a "description", which is the data I am trying to save from elsewhere and output to my "#cart". I have that part working, I just want to ensure that the user cannot add the same item twice. (or more times.)
Sorry if my code is shoddy or I look ignorant; I am currently a student trying to figure these things out so most of JS and Jquery is completely new to me. Thank you.
You can create a proxy and use Map to hold and access values, something like this
let cart = new Map([{ id: 1, title: "Dog toy" }, { id: 2, title: "Best of Stackoverflow 2018" }].map(v=>[v.id,v]));
let handler = {
set: function(target,prop, value, reciver){
if(target.has(+prop)){
console.log('already available')
} else{
target.set(prop,value)
}
},
get: function(target,prop){
return target.get(prop)
}
}
let proxied = new Proxy(cart, handler)
proxied['1'] = {id:1,title:'Dog toy'}
proxied['3'] = {id:3,title:'Dog toy new value'}
console.log(proxied['3'])
Assuming the 'part' property is unique on every cartData, I did checking only based on it.
function submitButton(something) {
window.scroll(0, 0);
cartData = ($(this).attr("data").split(','));
if(!isDuplicate(cartData))
arrObj.push({
part: cartData[0],
description: cartData[1]
});
}
const isDuplicate = (arr) => {
for(obj of arrObj){
if(arr[0] === obj.part)
return true;
}
return false;
}
If you want to do the checking on both 'part' and 'description' properties, you may replace the if statement with if(arr[0] === obj.part && arr[1] === obj.description).
Thanks everyone for their suggestions. Using this and help from a friend, this is the solution that worked:
function submitButton(something) {
window.scroll(0,0);
cartData = ($(this).attr("data").split(','));
let cartObj = {
part: cartData[0],
description: cartData[1],
quantity: 1
}
match = false
arrObj.forEach(function(cartObject){
if (cartObject.part == cartData[0]) {
match = true;
}
})
console.log(arrObj);
if (!match) {
arrObj.push(cartObj);
}
Okay, you have multiple possible approaches to this. All of them need you to specify some kind of identifier on the items which the user can add. Usually, this is just an ID integer.
So, if you have that integer you can do the following check to make sure it's not in the array of objects:
let cart = [{ id: 1, title: "Dog toy" }, { id: 2, title: "Best of Stackoverflow 2018" }];
function isInCart(id) {
return cart.some(obj => obj.id === id);
}
console.log(isInCart(1));
console.log(isInCart(3));
Another approach is saving the items by their id in an object:
let cart = { 1: { title: "Dog toy" }, 2: { title: "Best of Stackoverflow 2018" } };
function isInCart(id) {
if(cart[id]) return true;
return false;
}
Try to use indexOf to check if the object exists, for example:
var beasts = ['ant', 'bison', 'camel', 'duck', 'bison'];
console.log(beasts.indexOf('aaa'));
// expected output: -1

Making a Search Filter with JQuery?

So I have a Table made from some json data...
{
"AKH":{
"name": "Amonkhet",
"code": "AKH"
"cards": [
{
"artist": "Izzy",
"cmc": 3,
"colorIdentity": [
"W"
],
"colors": [
"White"
],
"id": "df3a6e0336684c901358f3ff53ec82ff5d7cdb9d",
"imageName": "gideon of the trials",
"layout": "normal",
"loyalty": 3,
"manaCost": "{1}{W}{W}",
"multiverseid": 426716,
"name": "Gideon of the Trials",
"number": "14",
"rarity": "Mythic Rare",
"subtypes": [
"Gideon"
],
"text": "+1: Until your next turn, prevent all damage target permanent would deal.\n0: Until end of turn, Gideon of the Trials becomes a 4/4 Human Soldier creature with indestructible that's still a planeswalker. Prevent all damage that would be dealt to him this turn.\n0: You get an emblem with \"As long as you control a Gideon planeswalker, you can't lose the game and your opponents can't win the game.\"",
"type": "Planeswalker — Gideon",
"types": [
"Planeswalker"
]
},
The Table row ends up looking like this for each of the cards. at the moment I only Attach the ID, Card name, and Mana Cost to each row
<td><a href="#" onclick="showInfo(this.id)"
id="df3a6e0336684c901358f3ff53ec82ff5d7cdb9d">Gideon of the Trials</a></td>
Now I want to search through these cards. (Keep in mind there are over 17,000 different cards that will be on this list) I can get it to find the things.. But I'm having several different issues... Either it finds them all but doesn't hide the rest of the list, or it hides the whole list and only displays one of the found cards.
So question A... What am I missing to make the search work correctly?
$(document).on('change', 'input[type=checkbox]', function() {
var lis = $('.cardsRow')
$('input[type=checkbox]').filter(':checked').each(function(){
filterKeyB = $(this).attr('id')
filterKeyA = $(this).attr('name')
$.each(json, function(setCode, setListing) {
$.each(setListing.cards,function(cardNum, cardListing){
var x = Object.keys(cardListing)
var y = Object.keys(cardListing).map(function (key){
return cardListing[key]
})
for (i = 0; (i < x.length); i++) {
if(x[i] === filterKeyA){
if (y[i] instanceof Array){
var holder = y[i]
var valueArr =[]
for(var k = 0; k < holder.length; k++){
valueArr = holder.join('|').toLowerCase().split('|')
var foundIt = valueArr.includes(filterKeyB)
}
}else{
var stringy = y[i]
var stringyA= stringy.toLowerCase().replace(/\s/g, '')
if (stringyA === filterKeyB){
var foundIt = true
}
}
if(foundIt === true){
$winner = cardListing.name
for (k = 0; (k < lis.length); k++){
if (lis[k].innerText.indexOf($winner) != -1) {
$(lis[k]).show()
}
}
}
}
}
})
Question B... Since you are already here... Would it be better practice to attach the data that can be searched to the element itself? Maybe just the most searched (Like Name and Mana) and have more advanced queries go through the data again?
I don't understand why the code isn't working or even how it works, it looks like it references some functions that aren't defined in the sample. But I can share with you a really simple/intuitive way to filter stuff, I hope you find it useful.
Native filter method is so useful for what you're trying to do, it takes a callback that takes current element as an arg and returns true or false, if true, the element is included in the new array it produces.
But filter only takes one function, and you have many filters, so let's make a function that combines many filter Fns together into one fn, so you can pass them in all at once:
const combineFilters = (...fns) => val => fns.reduce((prev, curr) => prev || curr(val), false);
OK, how about storing the names of the filter functions as keys in an object so we can reference them using a string? That way we could give each checkbox an ID corresponding to the name of the filter function they are supposed to apply, and makes things really easy to implement (and read):
const filterFns = {
startsWithG(card) {
return card.name[0] === 'G';
},
//etc.
};
OK, time to get the IDs of all the checkboxes that are clicked, then map them into an array of functions.
const filters = $('input[type=checkbox]')
.filter(':checked')
.map((e, i) => $(i).attr('id'))
.get()
.map(fnName => filterFns[fnName])
(Assume the relevant data is stored in a var called...data.) We can use combineFilters combined with filters (array of Fns) to activate all of the relevant filters, then map the resulting array of matching objects into the HTML of your choosing.
const matches = data.cards
.filter(combineFilters(...filters))
.map(card => `<div>${card.name}</div>` );
Then time to update DOM with your matches!
As others have noted, if you need to do any more complicated filtering on objects or arrays, lodash library is your friend!

Most efficient way to search a map of arrays in JavaScript

I have a map of arrays of numbers in JavaScript. My goal is to get the key of the value that contains a certain number. I'm also open to a different data structure that might be more efficient.
let bookCategory = {
"fantasy": [10064, 10066, 10071],
"scifi": [10060, 10037, 10061],
"history": [10001, 10003, 10004, 10005],
"biography": [10032, 10006, 10002, 10028, 10009, 10030, 100031],
"educational": [10025]
};
Each number will only ever appear once, but each array can contain close to a hundred numbers and it may grow substantially from there. The arrays could be immutable as my data is static.
Right now I have this, but it doesn't seem terribly efficient.
let category;
let keys = _.keys(categories);
let theNumber = 10032;
for(let j = 0; j < keys.length; j++) {
if(_.includes(categories[keys[j]], theNumber)) {
category = keys[j];
break;
}
}
lodash library has a lot of useful functions. Using it, you have the following options:
1. Binary search
Create a new structure with sorted array of numbers. When looking for a number, apply a binary search.
_.sortedIndexOf() method uses binary search in an array.
var bookCategory = {
"fantasy": [10064, 10066, 10071],
"scifi": [10060, 10037, 10061],
"history": [10001, 10003, 10004, 10005],
"biography": [10032, 10006, 10002, 10028, 10009, 10030, 100031],
"educational": [10025]
};
var binaryMap = _.mapValues(bookCategory, function(category) {
return category.sort(function(num1, num2) {
return num1 - num2;
});
});
//then search using binary algorithm
var number = 10032;
var keyForNumber = _.findKey(binaryMap, function(numbers) {
return _.sortedIndexOf(numbers, number) !== -1;
});
keyForNumber // prints "biography"
Check the working demo.
2. Create a map object
Because the numbers will appear only once, it's easy to create a big hash object, where the key is the number and value is the category. It requires a bit more memory because copies the categories string, but it works quite fast.
This solution doesn't require lodash.
var bookCategory = {
"fantasy": [10064, 10066, 10071],
"scifi": [10060, 10037, 10061],
"history": [10001, 10003, 10004, 10005],
"biography": [10032, 10006, 10002, 10028, 10009, 10030, 100031],
"educational": [10025]
};
var map = _.reduce(bookCategory, function(result, numbers, key) {
_.each(numbers, function(number) {
result[number] = key;
});
return result;
}, {});
// or alternative without lodash
var mapAlternative = Object.keys(bookCategory).reduce(function(result, key) {
bookCategory[key].forEach(function(number) {
result[number] = key;
});
return result;
}, {});
var number = 10003;
map[number]; // prints "history"
Check the working demo.
There are too many what-ifs to answer that question, the biggest one being: How often is the data going to be updated vs read.
If it is going to be read much more often, then iterate through the bookCategory first and create a sparse array/object that links the numbers back to the category.
(I'll go for object here):
// Generate this programatticly, of course.
bookCategoryLinkback = {
10064: "fantasy",
10066: "fantasy",
10071: "fantasy"
};
sort the array and use binary search. You can use lodash lib to do it easily.
I suggest to use a hashtable for the numbers.
var bookCategory = {
"fantasy": [10064, 10066, 10071],
"scifi": [10060, 10037, 10061],
"history": [10001, 10003, 10004, 10005],
"biography": [10032, 10006, 10002, 10028, 10009, 10030, 100031],
"educational": [10025]
},
numbers = function (data) {
var r = Object.create(null);
Object.keys(data).forEach(k => data[k].forEach(a => r[a] = k));
return r;
}(bookCategory)
document.write('<pre>' + JSON.stringify(numbers, 0, 4) + '</pre>');

Categories

Resources