Is there a Javascript function similar to the Python Counter function? - javascript

I am attempting to change a program of mine from Python to Javascript and I was wondering if there was a JS function like the Counter function from the collections module in Python.
Syntax for Counter
from collection import Counter
list = ['a', 'b', 'c', 'b', 'a', 'b', 'c', 'a', 'a', 'a']
counter = Counter(list)
print counter
output
Counter({'a':5, 'b':3, 'c':2})

DIY JavaScript solution:
var list = ['a', 'b', 'c', 'b', 'a', 'b', 'c', 'a', 'a', 'a'];
function Counter(array) {
var count = {};
array.forEach(val => count[val] = (count[val] || 0) + 1);
return count;
}
console.log(Counter(list));
JSFiddle example
Update:
Alternative that uses a constructor function:
var list = ['a', 'b', 'c', 'b', 'a', 'b', 'c', 'a', 'a', 'a'];
function Counter(array) {
array.forEach(val => this[val] = (this[val] || 0) + 1);
}
console.log(new Counter(list));
JSFiddle example

You can use Lo-Dash's countBy function:
var list = ['a', 'b', 'c', 'b', 'a', 'b', 'c', 'a', 'a', 'a'];
console.log(_.countBy(list));
JSFiddle example

I know I'm late but in case if someone is looking at this in 2020 you can do it using reduce, for example:
const counter = (list) => {
return list.reduce(
(prev, curr) => ({
...prev,
[curr]: 1 + (prev[curr] || 0),
}),
{}
);
};
console.log(counter([1, 2, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 1, 0]));
// output -> { '0': 1, '1': 6, '2': 2, '3': 1, '4': 1, '5': 1, '6': 1, '7': 1 }
more advance example with a callback function and context binding
const data = [1, 2, 3, 4, 5];
const counter = (list, fun, context) => {
fun = context ? fun.bind(context) : fun;
return list.reduce((prev, curr) => {
const key = fun(curr);
return {
...prev,
[key]: 1 + (prev[key] || 0),
};
}, {});
};
console.log(counter(data, (num) => (num % 2 == 0 ? 'even' : 'odd')));
// output -> { odd: 3, even: 2 }

There is also pycollections.js, which works on Node and in client-side JS.
Example:
var collections = require('pycollections');
var counter = new collections.Counter([true, true, 'true', 1, 1, 1]);
counter.mostCommon(); // logs [[1, 3], [true, 2], ['true', 1]]

For those who want a pure JavaScript solution:
function countBy (data, keyGetter) {
var keyResolver = {
'function': function (d) { return keyGetter(d); },
'string': function(d) { return d[keyGetter]; },
'undefined': function (d) { return d; }
};
var result = {};
data.forEach(function (d) {
var keyGetterType = typeof keyGetter;
var key = keyResolver[keyGetterType](d);
if (result.hasOwnProperty(key)) {
result[key] += 1;
} else {
result[key] = 1;
}
});
return result;
}
Therefore:
list1 = ['a', 'b', 'c', 'b', 'a', 'b', 'c', 'a', 'a', 'a'];
console.log(countBy(list1)); // {'a':5, 'b':3, 'c':2}
list2 = ['abc', 'aa', 'b3', 'abcd', 'cd'];
console.log(countBy(list2, 'length')); // {2: 3, 3: 1, 4: 1}
list3 = [1.2, 7.8, 1.9];
console.log(countBy(list3, Math.floor)); // {1: 2, 7: 1}

In Python, the Counter also has add and update methods, which are used quite commonly. So a better solution would be this:
function Counter(array) {
this.add = (val) => {
this[val] = (this[val] || 0) + 1;
};
this.update = (array) => {
array.forEach((val) => this.add(val));
};
this.update(array);
}
// Example usage
let myCounter = new Counter([1, 2, 2])
myCounter.update([3, 3, 3])
myCounter.add(4)
console.log(myCounter)

Here is a simple and easy to read solution:
const word1 = "tangram"
const dict1 = {}
for (let char of word1){
console.log(char)
if (dict1[char]){
dict1[char] += 1
}else{
dict1[char]= 1
}
}

This is my solution with explicit function calls
let list = [4, 6, 5, 3, 3, 1];
function counter(list) {
let count = function(n) {
let cnt = 0;
for (let v of list) {
if (v === n) cnt++
}
return cnt
}
let [...listSet] = new Set(list);
let cntr = {};
for (let v of listSet) {
cntr[v] = count(v)
}
return cntr
}
console.log(counter(list))

Another version ->
s = "naman";
const counter = (s, sMap = {}) => {
[...s].map((el) => {
sMap[el] = sMap[el] ? sMap[el] + 1 : 1;
});
return sMap;
};
const res = counter(s);
console.log(`res`, res);

How about this pure functional way:
let list = ['a', 'b', 'c', 'b', 'a', 'b', 'c', 'a', 'a', 'a'];
function counter(array) {
return array.reduce((acc, value, index) => {
acc[value] = value in acc ? acc[value] + 1: 1
return acc;
}, {});
}
Fiddle Link

Related

How to merge two arrays in JavaScript loop

Is there a better way to do this? Faster or more readable? Please share your approach.
const a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
const b = [ 'a', 'b', 'c', 'd', 'e', 'f' ]
let i = 0
let j = 1
while (true) {
const item = b[i]
if (!item) break
a.splice(j, 0, item)
j += 2
i++
}
// expected output [0, 'a', 1, 'b', 2, 'c', 3, 'd', 4, 'e', 5, 'f', 6, 7, 8, 9]
You could iterate the array by the minimum of both array lengths and take the rest by slicing the arrays from the minimum length.
function merge(a, b) {
const
result = [];
l = Math.min(a.length, b.length);
for (let i = 0; i < l; i++) result.push(a[i], b[i]);
result.push(...a.slice(l), ...b.slice(l));
return result;
}
console.log(...merge([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], ['a', 'b', 'c', 'd', 'e', 'f']));
You can use either recursion:
const Nil =
Symbol();
const intercalate =
([x=Nil, ...xn], [y=Nil, ...yn], ret=[]) =>
x === Nil && y === Nil
? ret
: intercalate(xn, yn, ret.concat( x === Nil ? [] : x
, y === Nil ? [] : y
));
Or Array#flatMap:
const intercalate =
(xn, yn) =>
xn.flatMap((x, i) =>
i >= yn.length
? [x]
: [x, yn[i]]);
My approach:
function mergeBetween(a, b) {
let i = 0
let j = 1
while (true) {
const item = b[i]
if (!item) break
a.splice(j, 0, item)
j += 2
i++
}
return a
}
const a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
const b = [ 'a', 'b', 'c', 'd', 'e', 'f' ]
mergeBetween(a, b) //[0, 'a', 1, 'b', 2, 'c', 3, 'd', 4, 'e', 5, 'f', 6, 7, 8, 9]
This would be my approach, if speed is your game, then you should stick to for loops... But I would suggest avoiding premature optimization in general... Not sure if that is what you meant by "faster" either...
const a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const b = ["a", "b", "c", "d", "e", "f"];
// check which is longer
const longer = a.length > b.length ? a : b;
const shorter = a.length < b.length ? a : b;
// declare a new variable to hold the combined array
let newArray = [];
for (let i = 0; i < longer.length; i++)
newArray =
i < shorter.length
? [...newArray, longer[i], shorter[i]]
: [...newArray, longer[i]];
console.log(newArray)

Remove duplicated combination in array based on index

I have the following data array:
const data = [
{
value: [
'a',
'b',
'a',
'a'
]
},
{
value: [
'c',
'c',
'd',
'c'
]
}
];
So there's is 4 combination here based on index:
combination 1 : a - c (index 0 in each value arrays)
combination 2 : b - c (index 1 in each value arrays)
combination 3 : a - d (index 2 in each value arrays)
combination 4 : a - c (index 3 in each value arrays)
As you can see the first and the last combinations are the same, so i want to remove the second occurrence from each array, the result should be:
[
{
value: [
'a',
'b',
'a'
]
},
{
value: [
'c',
'c',
'd'
]
}
]
You can zip the values arrays from both objects to form an array which looks like:
["a-c", "b-c", ...]
As these are now strings, you can turn this array into a Set using new Set(), which will remove all duplicate occurrences. You can then turn this set back into an array which you can then use .reduce() on to build you array of objects from. For each value you can obtain the list of values by using .split() on the '-', and from that, populate your reduced array.
See example below:
const data = [{ value: [ 'a', 'b', 'a', 'a' ] }, { value: [ 'c', 'c', 'd', 'c' ] } ];
const unq = [...new Set(
data[0].value.map((_,c)=> data.map(({value})=>value[c]).join('-'))
)];
const res = unq.reduce((acc, str) => {
const values = str.split('-');
values.forEach((value, i) => acc[i].value.push(value));
return acc;
}, Array.from({length: data.length}, _ => ({value: []})))
console.log(res);
Limitations of the above method assume that you won't have a - character as your string value. If this is an issue, you can consider using a different delimiter, or find unique values within your array using .filter() instead of a Set.
You could save a lookup object for unique pairs of value based with index
Given your input is, below solution could help you
const data = [
{
value: ["a", "b", "a", "a"],
},
{
value: ["c", "c", "d", "c"],
},
]
const lookup = {}
data[0].value.forEach((_, index) => {
lookup[`${data[0].value[index]}-${data[1].value[index]}`] = true
})
const res = Object.keys(lookup).reduce(
(acc, key) => {
const [val1, val2] = key.split("-")
acc[0].value.push(val1)
acc[1].value.push(val2)
return acc
},
[{ value: [] }, { value: [] }]
)
console.log(res)
Below is a two step solution with a generator function and a single pass.
const data = [ { value: [ 'a', 'b', 'a', 'a' ] }, { value: [ 'c', 'c', 'd', 'c', ] } ];
const zipDataValues = function* (data) {
const iterators = data.map(item => item.value[Symbol.iterator]())
let iterations = iterators.map(iter => iter.next())
while (iterations.some(iteration => !iteration.done)) {
yield iterations.map(iteration => iteration.value)
iterations = iterators.map(iter => iter.next())
}
}
const filterOutDuplicateCombos = function (values) {
const combosSet = new Set(),
resultData = [{ value: [] }, { value: [] }]
for (const [valueA, valueB] of values) {
const setKey = [valueA, valueB].join('')
if (combosSet.has(setKey)) {
continue
}
combosSet.add(setKey)
resultData[0].value.push(valueA)
resultData[1].value.push(valueB)
}
return resultData
}
console.log(
filterOutDuplicateCombos(zipDataValues(data))
) // [ { value: [ 'a', 'b', 'a' ] }, { value: [ 'c', 'c', 'd' ] } ]
Here is a reference on generators and iterators
Filter combinations + sorting by the first occurrence:
const data = [{
value: ['a', 'b', 'a', 'a']
},{
value: ['c', 'c', 'd', 'c']
}];
var res = {}, i, t;
for (i = 0; i < data[0].value.length; ++i) {
res[data[0].value[i]] = res[data[0].value[i]] || {};
res[data[0].value[i]][data[1].value[i]] = true;
}
data[0].value = [];
data[1].value = [];
for (i in res) {
for (t in res[i]) {
data[0].value[data[0].value.length] = i;
data[1].value[data[1].value.length] = t;
}
}
console.log(data);

JavaScript: Iterate through multidimentional array vertically

I've got multidimentional array and I need to count chars vertically. No problem to count in row, but I can't iterate it like vertically. Tip please.
const arrayData = [
['a', 'b', 'c'],
['a', 'f', 'g'],
['b']
];
My code looks like this:
const countChars = (input, direction) => {
if (direction === 'row') {
return input.reduce((acc, curr) => {
acc[curr] = acc[curr] ? ++acc[curr] : 1;
return acc;
}, {});
}
if (direction === 'column') {
for (let row = 0; row < input.length; row++) {
for (let column = 0; column < input[row].length; column++) {
console.log(input[column][row]);
}
console.log('---');
}
}
}
But for columns I'm getting this as result:
a
a
b
---
b
f
undefined
---
c
So I'm losing there a char because of undefined.
The result should be like for columns:
{ 'a': 2, 'b': 1 }
{ 'b': 1, 'f': 1 }
{ 'c': 1, 'g': 1 }
You could iterate the array and collect same values at same index.
const
array = [['a', 'b', 'c'], ['a', 'f', 'g'], ['b']],
result = array.reduce((r, a) => {
a.forEach((v, i) => {
r[i] = r[i] || {};
r[i][v] = (r[i][v] || 0) + 1;
});
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to filter array that include object with an array data?

Here is my data:
const sectionDummy = [
{ floor: '1', data: ['A', 'B', 'C']},
{ floor: '2', data: ['D', 'E', 'F']},
{ floor: '3', data: ['G', 'H', 'I']},
];
I can filter floor and get the correct data:
let filterData = [];
filterData = sectionDummy.filter(item => {
if (item.floor !== undefined) {
return item.floor.trim().toLowerCase().indexOf(inputValue) >= 0;
}
return {};
});
If inputValue is 1, I will get the return data
console.log(filterData); // [{ floor: 1, data: ['A', 'B', 'C']}]
I'm stuck with the data array if I want to filter it too.
I try to use map
let filterData = [];
filterData = sectionDummy.filter(item => {
if (item.floor !== undefined) {
item.data.map((value, index) => {
if(value.trim().toLowerCase().indexOf(inputValue) >=0) {
return value;
} else {
return {};
}
});
}
return {};
});
It looks like filter and map data return is different, so it is not working.
How to filter both of floor and data array ?
Any help would be appreciated.
Do you want to filter by the floor and then the data?
const sectionDummy = [
{ floor: 1, data: ['A', 'B', 'C']},
{ floor: 2, data: ['D', 'E', 'F']},
{ floor: 3, data: ['G', 'H', 'I']},
];
const floorFilter = 2;
const dataFilter = 'E';
const filteredData = sectionDummy
.filter(item => item.floor === floorFilter)
.map(({floor, data}) => ({floor, data: data.filter(d => d === dataFilter)}));
console.log(filteredData); //[ { floor: 2, data: [ 'E' ] } ]
Or by the floor and by the data?
const sectionDummy = [
{ floor: 1, data: ['A', 'B', 'C']},
{ floor: 2, data: ['D', 'E', 'F']},
{ floor: 3, data: ['G', 'H', 'I']},
];
const floorFilter = 2;
const dataFilter = 'E';
const filteredData = sectionDummy
.filter(({floor, data}) => floor === floorFilter && data.some(d => d === dataFilter))
console.log(filteredData) // [ { floor: 2, data: [ 'D', 'E', 'F' ] } ]
You could filter by checking floor and inputValue and take the objects it the two values are equal.
let filterData = searchBuildings.filter(({ floor }) => floor === inputValue);
You can use reduce like this: If the searchText matches floor, then return the entire section. Else if the searchText exists in data, then return a section object with filtered data array.
const sectionDummy = [
{ floor: "1", data: ['A', 'B', 'C']},
{ floor: "2", data: ['D', 'E', 'F']},
{ floor: "3", data: ['G', 'H', 'I']},
];
const filter = (sections, searchText) => {
searchText = searchText.trim().toLowerCase();
return sections.reduce((acc, { floor, data }) => {
if (floor === searchText) {
acc.push({ floor, data })
} else {
const filtered = data.filter(d => d.toLowerCase().includes(searchText))
if (filtered.length > 0)
acc.push({ floor, data: filtered })
}
return acc;
}, [])
}
console.log(filter(sectionDummy, "A"))
console.log(filter(sectionDummy, "G"))
console.log(filter(sectionDummy, "1"))

How to put distribute array into arrays of arrays?

I'm new to node js/express. I'm having the problem cause I need to insert bulk in MySQL. I use body-parser, but to simplify my code this is the analogy.
I have two objects from req.body:
Numbers = { 1, 2, 3 }
Letters = { a, b, c }
Then, I need it to be like this,
Object = [ { '1', 'a' }, { '2', 'b' }, { '3', 'c' } ]
What can I use to do this?
const Numbers = [1, 2, 3]
const Letters = ['a', 'b', 'c']
const result = []
Numbers.forEach((el, i) => {
result.push({[el]: Letters[i]})
})
console.log(result)
or
const Numbers = [1, 2, 3]
const Letters = ['a', 'b', 'c']
const result = Numbers.map((el, i) => ({[el]: Letters[i]}))
console.log(result)

Categories