How to dynamically set the key in a reduce function - javascript

I have a .txt file with all the scrabble letter quantities in:
A-9, B-2, C-2, D-4, E-12, F-2, G-3, H-2, I-9, J-1, K-1, L-4, M-2, N-6, O-8, P-2, Q-1, R-6, S-4, T-6, U-4, V-2, W-2, X-1, Y-2, Z-1
I am trying to get an array of objects from this, where the key is the letter, and the value is the number.
The problem I am having is where I am trying to set the key to be the first item in the array (that I split earlier) [A, 9].
The code I have is as follows. Any tips would be gratefully received :)
import fs from 'fs'
var output = fs.readFileSync('scrabble-quantities.txt', 'utf-8')
.trim()
.split(', ')
.map(item => item.split('-'))
.reduce((quantities, item) => {
quantities.push({
item[0]: item[1]
})
return quantities
}, [])
Thanks

I would use an object instead of an array. It's easier and more natural how JS works:
// ...
.reduce((quantities, item) => {
quantities[item[0]] = item[1];
return quantities;
}, {});
The resulting output object is then (in JSON notation):
{
"A": 9,
"B": 2,
// ...
"Z": 1
}
EDIT: mind the value type
If you want the value to be an actual number you will have to parse it in the assignment with:
parseInt(item[1], 10)

To create a key (property name) dynamically, you need to do one of two things:
In ES5 and earlier, you have to create the object and then set the property:
var o = {};
o[item[0]] = item[1];
In ES2015 (aka ES6) and later, you can use a dynamic property name in a property initializer via []:
// ES2015 (ES6) ONLY
quantities.push({
[item[0]]: item[1]
})
That said, I'd come at the problem differently, using regex and ending up with an object keyed by the letter rather than an array of objects:
var str = "A-9, B-2, C-2, D-4, E-12, F-2, G-3, H-2, I-9, J-1, K-1, L-4, M-2, N-6, O-8, P-2, Q-1, R-6, S-4, T-6, U-4, V-2, W-2, X-1, Y-2, Z-1";
var quantities = {};
str.match(/[A-Z]-\d/g).forEach(function(entry) {
quantities[entry.charAt(0)] = +entry.replace(/.-/, '');
});
console.log(quantities);
Then, looking up the quantity of a letter becomes quantities.A (or quantities[letter] if letter is a variable containing "A"):
console.log(quantities.A); // 9

Related

Why do i get just a number from console.log(Array.push()); in Javascript? [duplicate]

Are there any substantial reasons why modifying Array.push() to return the object pushed rather than the length of the new array might be a bad idea?
I don't know if this has already been proposed or asked before; Google searches returned only a myriad number of questions related to the current functionality of Array.push().
Here's an example implementation of this functionality, feel free to correct it:
;(function() {
var _push = Array.prototype.push;
Array.prototype.push = function() {
return this[_push.apply(this, arguments) - 1];
}
}());
You would then be able to do something like this:
var someArray = [],
value = "hello world";
function someFunction(value, obj) {
obj["someKey"] = value;
}
someFunction(value, someArray.push({}));
Where someFunction modifies the object passed in as the second parameter, for example. Now the contents of someArray are [{"someKey": "hello world"}].
Are there any drawbacks to this approach?
See my detailed answer here
TLDR;
You can get the return value of the mutated array, when you instead add an element using array.concat[].
concat is a way of "adding" or "joining" two arrays together. The awesome thing about this method, is that it has a return value of the resultant array, so it can be chained.
newArray = oldArray.concat[newItem];
This also allows you to chain functions together
updatedArray = oldArray.filter((item) => {
item.id !== updatedItem.id).concat[updatedItem]};
Where item = {id: someID, value: someUpdatedValue}
The main thing to notice is, that you need to pass an array to concat.
So make sure that you put your value to be "pushed" inside a couple of square brackets, and you're good to go.
This will give you the functionality you expected from push()
You can use the + operator to "add" two arrays together, or by passing the arrays to join as parameters to concat().
let arrayAB = arrayA + arrayB;
let arrayCD = concat(arrayC, arrayD);
Note that by using the concat method, you can take advantage of "chaining" commands before and after concat.
Are there any substantial reasons why modifying Array.push() to return the object pushed rather than the length of the new array might be a bad idea?
Of course there is one: Other code will expect Array::push to behave as defined in the specification, i.e. to return the new length. And other developers will find your code incomprehensible if you did redefine builtin functions to behave unexpectedly.
At least choose a different name for the method.
You would then be able to do something like this: someFunction(value, someArray.push({}));
Uh, what? Yeah, my second point already strikes :-)
However, even if you didn't use push this does not get across what you want to do. The composition that you should express is "add an object which consist of a key and a value to an array". With a more functional style, let someFunction return this object, and you can write
var someArray = [],
value = "hello world";
function someFunction(value, obj) {
obj["someKey"] = value;
return obj;
}
someArray.push(someFunction(value, {}));
Just as a historical note -- There was an older version of JavaScript -- JavaScript version 1.2 -- that handled a number of array functions quite differently.
In particular to this question, Array.push did return the item, not the length of the array.
That said, 1.2 has been not been used for decades now -- but some very old references might still refer to this behavior.
http://web.archive.org/web/20010408055419/developer.netscape.com/docs/manuals/communicator/jsguide/js1_2.htm
By the coming of ES6, it is recommended to extend array class in the proper way , then , override push method :
class XArray extends Array {
push() {
super.push(...arguments);
return (arguments.length === 1) ? arguments[0] : arguments;
}
}
//---- Application
let list = [1, 3, 7,5];
list = new XArray(...list);
console.log(
'Push one item : ',list.push(4)
);
console.log(
'Push multi-items :', list.push(-9, 2)
);
console.log(
'Check length :' , list.length
)
Method push() returns the last element added, which makes it very inconvenient when creating short functions/reducers. Also, push() - is a rather archaic stuff in JS. On ahother hand we have spread operator [...] which is faster and does what you needs: it exactly returns an array.
// to concat arrays
const a = [1,2,3];
const b = [...a, 4, 5];
console.log(b) // [1, 2, 3, 4, 5];
// to concat and get a length
const arrA = [1,2,3,4,5];
const arrB = [6,7,8];
console.log([0, ...arrA, ...arrB, 9].length); // 10
// to reduce
const arr = ["red", "green", "blue"];
const liArr = arr.reduce( (acc,cur) => [...acc, `<li style='color:${cur}'>${cur}</li>`],[]);
console.log(liArr);
//[ "<li style='color:red'>red</li>",
//"<li style='color:green'>green</li>",
//"<li style='color:blue'>blue</li>" ]
var arr = [];
var element = Math.random();
assert(element === arr[arr.push(element)-1]);
How about doing someArray[someArray.length]={} instead of someArray.push({})? The value of an assignment is the value being assigned.
var someArray = [],
value = "hello world";
function someFunction(value, obj) {
obj["someKey"] = value;
}
someFunction(value, someArray[someArray.length]={});
console.log(someArray)

How can i understand this topic better, to be a better Programmer

i have a Question. I was thinking long Time about it, but poorly i don´t find a answer.
I know the every method.
My Question is about this code section:
var tr = order.every((i) => stock[i[0]] >= i[1]);
My Questions are:
stock is an Object. Why i must write as an array?
Why it is i[0] in stock and then i[1] ?
Why this code checks the nested Arrays in const order ?
const order = [
["shirt", 5],
["shoes", 2]
];
const stock = {
shirt: 50,
height: 172,
mass: 120,
shoes: 6
};
var tr = order.every((i) => stock[i[0]] >= i[1]); /// return true
console.log(`tr:`,tr)
So, the square brackets can be used to access element inside the array by passing it's index e.g:
const arr = ["first", "second"];
const secondElement = arr[1] // index 1 means seconds element
and also square brackets can be used to access element inside the object by passing it's key e.g:
const obj = { first: 1, second: 2 };
const secondElement = object.second // Normal way to access value in object
const secondElementWithAnotherSyntax = object['second'] // another syntax, same thing
the cool thing about the other syntax shown is that you can pass variable to it, e.g :
const objKey = 'second'
const secondElement = obj[objKey]
Now let's look at your example, i is one element of the array order, which carries arrays itself, so i is also one of the two small arrays, i[0] is the string word in the beginning of the small arrays, so:
i[0] // is either 'shirt' or 'shoes'
and since stocks is an object that has those keys, you can access for example the value 50 by saying stocks['shirt'] or as in your case, stock[i[0]] ;)
now your second question: why should it be >= i[1] ?
because the order second item , aka i[1] is the number of items required/ordered, so this should always be less that your stock, you can't by 5 shirts from a place that has only 3 in the stock :)
1. stock is an Object. Why i must write as an array?
You can access properties of objects using brackets [].
Why do we need this?
To be able to access properties of objects dynamically, e.g. when you are looping though keys and want to get the values
Object.keys(data).forEach(function(key) {
console.log('Key : ' + key + ', Value : ' + data[key])
})
Sometimes there is no other way to access the value:
const json = {
"id":"1",
"some key with spaces": "48593"
};
console.log(json.some key with spaces); // obviously throws error
console.log(json['some key with spaces']); // prints "48593"
2. Why it is i[0] in stock and then i[1] ?
3. Why this code checks the nested Arrays in const order ?
The code goes through the orders, each order is an array so i[0] is the type of the order and i[1] is the quantity. the code checks if there are enough items in stock. To check if there are enough shirts you would do:
console.log(stock["shirts"] >= 5
Thats what the code in your example does, it just passes the key ("shirts") and quantity (5) dynamically.
May I suggest to try to use more expressive naming of the variables ?
An object property can be accessed through bracket notation, as in stock[orderedProductName] when using a variable - Property accessors
A concise but imho more readable version can be written using destructuring assignment
const order = [
["shirt", 5],
["shoes", 2]
];
const stock = {
shirt: 50,
height: 172,
mass: 120,
shoes: 6,
};
// original version
let inStock = order.every((i) => stock[i[0]] >= i[1]); /// return true
// more verbose version
// check if every item in array order satisfies the condition
// let's cycle over the array calling the element we're working on
// orderItem
inStock = order.every( orderItem => {
const orderedProductName = orderItem[0];
const orderedProductQuantity = orderItem[1];
// to access an object property we can use bracket notation
const stockProductQuantity = stock[orderedProductName];
// the condition to check: do we have enough products in stock ?
return stockProductQuantity >= orderedProductQuantity;
});
// a concise variation could make use of destructuring assignment.
// Here, when we take the order item array, we immediately assign
// each of its elements to the appropriate variable
//
// orderItem[0] or first array element -> productName
// orderItem[1] or second array element -> orderedQuantity
inStock = order.every(([productName, orderedQuantity]) =>
stock[productName] >= orderedQuantity
);
if(inStock) {
console.log('pack and ship');
}
else {
console.log('need to restock');
}
The every() method tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value. If you want to read more Array.prototype.every()
In your code snippet you are checking that every item in order array has quantity less than the quantity available in stock.
To access the properties of a object you can use square notation also like arrays. To read more Bracket Notation
If you assigned more meaningful variables to the code you'd probably understand how this works better.
In one order (an array) we have two nested arrays. The first describes shirt/value, the other shoes/value. every is going to see if there is enough stock for both shirt and shoes by checking that the stockValue >= the items in the order.
When you every over the order array the callback for each iteration is one orderItem (['shirt', 5] first, then ['shoes', 2] for the second). We can assign the first element of each array to a variable called itemType, and the second to a variable called itemQty.
So when you see stock[i][0] we can translate that in the new code as stock[orderType] which is using bracket notation to locate the property value associated by that key in the stock object. We then check to see if that value >= than the itemQty.
const order=[["shirt",5],["shoes",2]],stock={shirt:50,height:172,mass:120,shoes:6};
const result = order.every(orderItem => {
const itemType = orderItem[0];
const itemQty = orderItem[1];
return stock[itemType] >= itemQty;
});
console.log(result);

what does the square brackets do in the parameter of array.map?

not sure what is happening in .map(([key, value]) => [key, value * 2]) I have a gist idea of what the square brackets do in that parameter. But can someone clarify the rules of the syntax, or explain whats happening?
Here's my belief of how the code works:
Object.entries converts object prices to array. returns something like [[banana, 1], [orange, 2], [meat, 4]]
map is an array-method and the function doubles the prices of each item.
the chagnes are then reconverted back to object.
if map's going through the items; would it make more sense to write .map((item) => [item[0], item[1] * 2])
going through a lesson from javascript.info
let prices = {
banana: 1,
orange: 2,
meat: 4,
};
let doublePrices = Object.fromEntries(
// convert to array, map, and then fromEntries gives back the object
Object.entries(prices).map(([key, value]) => [key, value * 2])
);
console.log(doublePrices.meat); // 8
In the code snippet you're asking about the left side is quite different from the right side. Let's break it down:
.map(([key, value]) => [key, value * 2])
First, map takes a function as its parameter. Here the function is essentially doing this:
(entry) => {
const key = entry[0];
const value = entry[1];
const modified_entry = [ key, value * 2 ];
return modified_entry;
}
A shorter way to write that is:
(entry) => [ entry[0], entry[1] * 2 ]
That works the same because without curly brackets around the function body, it uses the value as the return value.
Now the code you're asking about isn't using a single variable for the input parameter to the function. It's a single parameter, but it sets 2 variables by using something called deconstruction or destructuring. This is a feature in Javascript that lets you break apart an object into multiple variables in a simple way. For example look at this:
var entry = [ 'meat', 1]
var [ item, price ] = entry
// now item is 'meat', and price is 1, which is nicer than having to do this:
item = entry[0]
price = entry[1]
So getting back to your code, they've expressed entry as [key,value] so that they can be used in the function easily, and on the right hand side of the => we have the return value which is a new array with the doubled price.
Remember that on the left side we're breaking an object into new variables which get assigned values, while on the right side we're creating a new Array object that's being returned. It can be a bit confusing because they both have the same form of looking like an array in square brackets.
Note also that deconstruction works for objects as well as arrays, so this is valid code too:
var obj = { a:1, b:2, c:3 }
var { a, c } = obj
console.log(a,c) // will print "1 3"
Deconstructing an object lets you take just the parts you want out of the object and creates variables named after the key. Notice that we didn't ask for b so there's no b variable - it's just ignored.

How to add an array of key value pairs to a Map in JavaScript?

I have an array of key value pairs where each key has an another array of constant length (i.e., 2) as the value. How can I add the entire array to a Map() without just doing Map.set(key, value) for every pair?
I know that while creating a Map() instance I could pass an iterable like an array to it ex: let x = new Map(arr); But this is not supported in IE 11 as per the documentation here. So, could anyone help me out with an alternate implementation.
At the end of the day, I just want to be able to access the two values with a string key. If there is an another way I could implement, please guide me.
Here is the example:
I created a key value mapping array as follows:
let arr = [
['ar', ['cl', 'bl']],
['bs', ['kl', 'ml']],
['cs', ['rk', 'uk']],
['da', ['mk', 'ak']]
];
let map = new Map(arr); // This isn't working.
Thank you.
If you need to support browsers where the Map is not implemented, just use ordinary object, where the 1st array item will be keys & 2nd (the sub-array) values:
var arr = [
['ar', ['cl', 'bl']],
['bs', ['kl', 'ml']],
['cs', ['rk', 'uk']],
['da', ['mk', 'ak']]
];
var map = arr.reduce(function (obj, item) {
obj[item[0]] = item[1];
return obj;
}, {});
console.log(map['ar'], map.bs);
Or ES2015 approach (won't work in IE11):
const map = arr.reduce((obj, item) => ({
...obj,
[item[0]]: item[1],
}), {});
If you want to access the subarray by the string key, you'd have to convert it from array anyways.
If you'd want to use Map, then (apart from fixing typos in your array definition) you'll extract the contents by Map.prototype.get function:
var map = new Map(arr);
console.log(map.get('bs'));
If you don't want to create a new object, another approach could be using Array.filter:
arr.filter(function (item) { return item[0] === 'ar' })[0][1];
arr.filter((item) => item[0] === 'ar')[0][1]; // ES2015
(potentially wrapped in a function)
You can use a object
let obj =
{ ar:['cl', 'bl'],
bs:['kl', 'ml'],
cs:['rk', 'uk'],
da:['mk', 'ak']
};
console.log(obj['bs'][1]); // displays 'ml'

JS Variable pulling other variables into it

I know the heading of this questions seems vague - but it's because I simply don't know how to summarize it appropriately.
I'm working on a project where I enter some text, and it's translated into something else.
There's a fiddle here.
If I enter 4, it translates to the word for.
If I enter b4, it should translate to before.
Instead, it translates to bfor, because it's capturing the variable 4 as a separate variable.
I've tried changing the order, but it doesn't work. Is this a regex problem?
My variables are identified in the JS.
var replaceValues = {
'4' : 'for',
'b4' : 'before'
}
$('.bs-text').keyup(function (event) {
newText = event.target.value;
for (var txt in replaceValues) {
var temp = new RegExp(txt, 'gim');
newText = newText.replace(temp, replaceValues[txt]);
}
$('.human-text').text(newText);
});
As I noted in the comments, JS objects does not have defined order of its keys, so it is not a good idea to count on this when you know the dictionary will get much bigger.
More about this in another SO question: Does JavaScript Guarantee Object Property Order?
Instead, use simple array that will have the order you define. Sorting of this dictionary array can be done in JS too, you do not need to handle this by your own.
var replaceValues = [
{key: '4', value: 'for'},
{key: 'b4', value: 'before'},
];
// sort the values so longer keys go first
replaceValues.sort((a, b) => b.key.length - a.key.length);
$('.bs-text').keyup(function (event) {
var newText = event.target.value;
for (var txt in replaceValues) {
var replacement = replaceValues[txt];
var temp = new RegExp(replacement.key, 'gim');
newText = newText.replace(temp, replacement.value);
}
$('.human-text').text(newText);
});
You could also use ES6 Map, it should have order guarantied. But be aware that it is not enough to create Map from Object:
A Map object iterates its elements in insertion order — a for...of loop returns an array of [key, value] for each iteration.
It should be noted that a Map which is a map of an object, especially a dictionary of dictionaries, will only map to the object's insertion order—which is random and not ordered.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Objects_and_maps_compared
As mentioned in the comments, you have to look for the longest match first. One option is to generate a single regular expression from the search words, ordered by length, and use a callback to get the correct replacement value.
var replaceValues = {
'4': 'for',
'b4': 'before'
};
// generates something equivalent to `/b4|4/gmi`
var pattern = new RegExp(
Object.keys(replaceValues)
.sort((a, b) => b.length - a.length)
.join('|'),
'gmi'
);
var newText = '4 me b4 me';
console.log(newText.replace(pattern, match => replaceValues[match]));
This works because the regex engine matches alternatives from left to right (i.e. if b4 matches it won't try to match 4). Not sure how this solution scales with more searchwords, but it might actually better because you are only matching the string once instead of n times, i.e. the regex engine doesn't have to traverse the whole string multiple times.
The object property has ":" character within property value
$('.bs-text').keyup(function (event) {
var newText = event.target.value;
if (replaceValues[newText]) {
$('.human-text').text(replaceValues[newText])
};
});
jsfiddle http://jsfiddle.net/je89deam/5/

Categories