I am looking for an algorithm name, preferably a library that can take an array and split it between functions in the best way possible.
I don't care about the complexity (it's for a very low data set).
Recursive check would suffice.
A nice example would be array:
const list = [{gender: "female"}, {gender: "female"}, {gender: "female"}, {gender: "male"}]
So if we run it with the special function
specialFunc(list, [(val) => val === 'female', (val) => val === 'male']);
We would be getting this
[
[{gender: "female"}, {gender: "female"}, {gender: "female"}],
[{gender: "male"}]
]
Because this is the best possible split we can get.
However, if we run it by this function:
specialFunc(list, [(val) => !!val, (val) => val === 'male']);
I would be getting this:
[
[{gender: "female"}, {gender: "female"}, {gender: "female"}],
[{gender: "male"}]
]
"the best way possible" means that the number distance (of array length) between each array should be the lowest, and the number of records in each array should be the maximum possible.
I have searched npmjs and github a lot but couldn't find anything.
Thank you very very much!
I think I understand these requirements. You have a number of predicate function which you want to use to group your items. Multiple predicates might return true for the same item, so there are various groupings available. You want to find a grouping that minimizes the variation in sizes of the results.
I don't find your examples very compelling. I will try my own. If your items are 8, 6, 7, 5, 3, 0, 9] and you have three predicates: (n) => n < 7, (n) => n > 3, and (n) => n % 2 == 1, then the 8 can only go in the second group (it's greater than 3, but not less than 7 and not odd.) The 6 can go in either of the first two groups, the 5 could be in any of them, and so on, like this:
8 6 7 5 3 0 9
[[1], [0, 1], [1, 2], [0, 1, 2], [0, 2], [0], [1, 2]]
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
| | | | | | | | | | | | |
| +--|----|--|----+--|--|----+--|----+----|--|------> Group 0 (n => n < 7)
| | | | | | | | |
+-------+----+--|-------+--|-------|---------+--|------> Group 1 (n => n > 3)
| | | |
+----------+-------+------------+------> Group 2 (n => n % 2 == 1)
Since there is one choice for the first, two for the second, two for the third, and so on, the number of possible partitions is 1 * 2 * 2 * 3 * 2 * 1 * 2, or 48. They might look like this:
[// < 7 > 3 odd
[ [6, 5, 3, 0], [8, 7, 9], [] ],
[ [6, 5, 3, 0], [8, 7], [9] ],
[ [6, 5, 0], [8, 7, 9], [3] ],
[ [6, 5, 0], [8, 7], [3, 9] ],
// ... (42 rows elided)
[ [0], [8, 6, 9], [7, 5, 3] ],
[ [0], [8, 6], [7, 5, 3, 9] ]
]
Then, from these, we need to pick the ones with the smallest variation in partition size. We can use statistical variance for this1, the sum of the squares of the distances of the values from their mean, so [[6, 5, 3, 0], [8, 7, 9], []], with lengths 4, 3, and 0; this has a variance of 8.667. The second one has lengths 4, 2, and 1 with a variance of 4.667. Our best possibility is 3, 2 and 2, with variance of 0.667. So an answer like [[6, 5, 0], [8, 7], [3, 9]] would be reasonable. There are probably quite a few with similar behavior; the implementation below simply chooses the first one.
If this correctly describes the problem, then here is some code that I think will handle it:
const range = (lo, hi) => Array .from ({length: hi - lo}, (_, i) => i + lo)
const sum = (ns) => ns .reduce ((a, b) => a + b, 0)
const filterMap = (f, m) => (xs) =>
xs .flatMap ((x, i, a) => f (x, i, a) ? [m (x, i, a)] : [])
const cartesian = ([xs, ...xss]) =>
xs == undefined
? [[]]
: xs .flatMap (x => cartesian (xss) .map (ys => [x, ...ys]))
const variance = (ns, avg = sum (ns) / (ns .length || 1)) =>
sum (ns .map (n => (n - avg) * (n - avg)))
const groupIndices = (count) => (xs) =>
Object .values (xs .reduce (
(a, x, i) => ((a [x] .push (i)), a),
Object .fromEntries (range (0, count) .map (n => [n, []]))
))
const specialFunc = (xs, preds) =>
cartesian (xs .map ((x) => filterMap ((pred, i) => pred (x), (_, i) => i) (preds)))
.map (groupIndices (preds .length))
.reduce (
({min, v}, xs, _, __, v2 = variance (xs .map (x => x .length))) =>
v2 < v ? {min: xs, v: v2} : {min, v},
{min: [], v: Infinity}
) .min .map (ys => ys .map (i => xs [i]))
console .log (specialFunc (
[8, 6, 7, 5, 3, 0, 9],
[n => n < 7, n => n > 3, n => n % 2 == 1]
)) //=> [[6, 5, 0], [8, 7], [3, 9]]
.as-console-wrapper {max-height: 100% !important; top: 0}
We start with some fairly standard utility functions. range calculates an integer range inclusive at the bottom, exclusive at the top, so that, for instance, range (3, 12) returns [3, 4, 5, 6, 7, 8, 9 ,10, 11]. sum simply totals an array of numbers, filterMap combines filtering with mapping, first testing whether an input matches a filter, and if so, transforming the result before putting it in the result. This implementation is unusual, in that the filter and mapping functions take more than just the value, but also the index and array properties found in things like map and filter. We need that as we'll use it to collect indices that match. (There are plenty of other ways to do that bit, but filterMap is a useful, reusable function.) cartesian returns the cartesian product of an array of arrays. For instance, cartesian ([1, 2, 3], [true], ['a', 'b']]) will return [[1, true, 'a'], [1, true, 'b'], [2, true, 'a'], [2, true, 'b'], [3, true, 'a'], [3, true, 'b']]. And finally variance calculate the statistical variance of a list of numbers.
Then we have a helper function, groupIndices. This might be easiest to show with an example. One of the 48 results from our cartesian product will be [1, 0, 1, 0, 2, 0, 1], which means that our original numbers (8, 6, 7, 5, 3, 0, 9], recall) are in groups 1, 0, 1, 0, 2, 0, and 1, respectively. groupIndices takes the number of groups and then takes that cartesian combination, and transforms it into [[1, 3, 5], [0, 2, 6], [4]], giving the indices of the values that are mapped to each group. (If I wasn't out of time, I'm sure we could skip this working with the indices and go directly against the values, but this works.)
Now we hit the main function, that I haven't tried to find a good name for, so is still called specialFunc. That uses filterMap to turn our list into [[1], [0, 1], [1, 2], [0, 1, 2], [0, 2], [0], [1, 2]], calls cartesian on the result, maps groupIndices over these values, then uses reduce to find (the first) one that is minimal in its variance. Finally it maps the resulting indices back to the actual values.
Again, we can probably clean this up and work with values not indices, but I would first want to know if this is the sort of behavior you're looking for.
1The standard deviation has a clearer meaning, but as it's just the square root of the variance, it will be ordered the same way as the variance, and won't involve calculating square roots.
function splitGroups<T>(items: T[], filters: ((x: T) => boolean)[]) {
let options = filters.map(f => items.filter(f));
let groups = filters.map((_, index) => ({ data: [] as T[], index }));
let res: T[][] = [];
while (options.reduce((partial_sum, a) => partial_sum + a.length, 0) > 0) {
groups.sort((a, b) => a.data.length - b.data.length);
let smallGroup = groups[0];
const smallGroups = groups.filter(g => g.data.length === smallGroup.data.length);
if (smallGroups.length > 1) {
smallGroup = smallGroups[Math.floor(Math.random() * (smallGroups.length - 1))];
}
if (options[smallGroup.index].length === 0) {
res.push(smallGroup.data);
groups = groups.filter(x => x !== smallGroup);
continue;
}
const item = options[smallGroup.index][0];
options = options.map(x => x.filter(y => y !== item));
smallGroup.data.push(item);
}
res = [...res, ...groups.map(x => x.data)];
return res;
}
function accurateSplitGroups<T>(items: T[], filters: ((x: T) => boolean)[], times: number) {
const options: { data: T[][]; diff: number }[] = [];
for (let i = 0; i < times; i++) {
const res = splitGroups(items, filters);
let diffBetweenGroups = 0;
const groupsLens = res.map(x => x.length);
for (let i = 0; i < groupsLens.length; i++) {
for (let j = 0; j < groupsLens.length; j++) {
diffBetweenGroups += Math.abs(groupsLens[i] - groupsLens[j]);
}
}
options.push({ data: res, diff: diffBetweenGroups });
}
return options.sort((a, b) => a.diff - b.diff)[0].data;
}
const items = [{ gender: 'female' }, { gender: 'female' }, { gender: 'female' }, { gender: 'male' }];
const filters = [(x: any) => !!x.gender, (x: any) => !!x.gender];
const res = accurateSplitGroups(items, filters, 100);
const b = res;
So I'm building a diet tracking app, and within my component i'm calculating the user's remaining macronutrients. I thought an efficient way to do this was to store the three variables into one object, so I can iterate through it to calculate the remaining values. Before implementing typescript, the code worked fine, but now that I've converted the file to typescript, it's throwing the following error, which I can't seem to work out:
Error:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ f: number; c: number | null; d: number | null; k: number | null; p: number; e: number; w: number | null; }'.
No index signature with a parameter of type 'string' was found on type '{ f: number; c: number | null; d: number | null; k: number | null; p: number; e: number; w: number | null; }'.
The object that stores the values:
const values: Mapper = {
consumed: {
f: entry.m.f,
c: entry.m.c,
d: entry.m.d,
k: entry.m.k,
p: entry.m.p,
e: entry.m.e,
w: entry.w.t,
},
goals: {
f: entry.g.s.f,
c: entry.g.s.c,
d: entry.g.s.d,
k: entry.g.s.k,
p: entry.g.s.p,
e: entry.g.s.e,
w: entry.g.s.w,
},
sum: {
f: 0,
c: 0,
d: 0,
k: 0,
p: 0,
e: 0,
w: 0,
},
};
The Interface used
interface Mapper {
consumed: {
f: number;
c: number | null;
d: number | null;
k: number | null;
p: number;
e: number;
w: number | null;
};
goals: {
f: number;
c: number | null;
d: number | null;
k: number | null;
p: number;
e: number;
w: number | null;
};
sum: {
f: number;
c: number | null;
d: number | null;
k: number | null;
p: number;
e: number;
w: number | null;
};
}
My .forEach:
goals.forEach((goal) => {
if (values.goals[goal] !== null) {
values.sum[goal] = +(
values.goals[goal] - values.consumed[goal]
).toFixed(1);
}
});
I don't get it. The forEach even makes sure the assignment to values.sum[goal] is a number. I've consoled logged the type and verified number assignments. So why is it giving me such a hard time? I've also tried Object.keys(values).forEach[...] but with no luck.
Can anyone help me out here? I'm very new to typescript, so I guess I'm probably just making a simple mistake here...
Thanks for taking the time to read and assist!
For those who are in a similar problem, I have the solution:
you have to define the index type with [index: string]: any; where you intend to iterate over
interface Mapper {
[index: string]: any;
consumed: {
[index: string]: any;
f: number;
c: number | null;
d: number | null;
k: number | null;
p: number;
e: number;
w: number | null;
};
goals: {
[index: string]: any;
f: number;
c: number | null;
d: number | null;
k: number | null;
p: number;
e: number;
w: number | null;
};
sum: {
[index: string]: any;
f: number;
c: number | null;
d: number | null;
k: number | null;
p: number;
e: number;
w: number | null;
};
}
Consider this problem:
Create a function zipmap that takes in two sequences, and creates a dictionary from the elements of the first sequence to the elements of the second.
zipmap([1, 2, 3], [4, 5, 6]) => {1: 4, 2: 5, 3: 6}
My solution is below as an answer, can anyone come up with a better way of doing it?
This is already built into Ramda, as zipObj:
console .log (
R.zipObj ([1, 2, 3], [4, 5, 6])
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
And it's also now a language feature, maybe not yet quite widely enough supported, but getting close: Object.fromEntries.
const zipmap = (arr1 ,arr2) => arr1.reduce((p,c,i) => {
p[c] = arr2[i];
return p;
},{});
Here's a simple recursive implementation -
// None : symbol
const None =
Symbol()
// zipMap : ('k array, 'v array) -> ('k, 'v) object
const zipMap = ([ k = None, ...keys ] = [], [ v = None, ...values ] = []) =>
k === None || v === None
? {}
: { [k]: v, ...zipMap(keys, values) }
console.log(zipMap([ 1, 2, 3 ], [ 4, 5, 6 ]))
// { 1: 4, 2: 5, 3: 6 }
But it's not much of a "mapping" function; it always returns an Object. What if you wanted a different result?
// None : symbol
const None =
Symbol()
// identity : 'a -> 'a
const identity = x =>
x
// zipMap : (('k, 'v) -> ('kk, 'vv), 'k array, 'v array) -> ('kk, 'vv) array
const zipMap =
( f = identity // ('k, v') -> ('kk, 'vv)
, [ k = None, ...keys ] = [] // 'k array
, [ v = None, ...values ] = [] // 'v array
) => // ('kk, 'vv) array
k === None || v === None
? []
: [ f ([ k, v ]), ...zipMap(f, keys, values) ]
// result : (number, number) array
const result =
zipMap
( identity
, [ 1, 2, 3 ]
, [ 4, 5, 6 ]
)
console.log(result)
// [ [ 1, 4 ], [ 2, 5 ], [ 3, 6 ] ]
console.log(Object.fromEntries(result))
// { 1: 4, 2: 5, 3: 6 }
// result2 : (number, number) array
const result2 =
zipMap
( ([ k, v ]) => [ k * 10, v * 100 ]
, [ 1, 2, 3 ]
, [ 4, 5, 6 ]
)
console.log(Object.fromEntries(result2))
// { 10: 400, 20: 500, 30: 600 }
Instead of creating an Object using Object.fromEntries, you could just as easily create a Map too -
// result2 : (number, number) array
const result2 =
zipMap
( ([ k, v ]) => [ k * 10, v * 100 ]
, [ 1, 2, 3 ]
, [ 4, 5, 6 ]
)
// m : (number, number) map
const m =
new Map(result2)
// Map { 10 => 400, 20 => 500, 30 => 600 }
const R = require('ramda')
const zipmapSeparate = (...arr) => arr[0].map((zeroEntry, index) => {
const item = {}
item[arr[0][index]] = arr[1][index]
return item
})
const zipmapReduce = (zipmap1) => zipmap1.reduce((accumulator, current) => {
const key = Object.keys(current)[0]
const value = Object.values(current)[0]
accumulator[key]=value
return accumulator
}, {})
const zipmap = R.compose(zipmapReduce, zipmapSeparate)
console.log(zipmap([1, 2, 3], [4, 5, 6]))
I have tried to write a function to do this:
return the number of occurances of each letter of a string in an object case insensitive
// eg. numOfOccurances('This is great') => { t: 2, h: 1, i: 2, s: 2, g: 1, r: 1, e: 1, a: 1 }
function numOfOccurances(string) {
const stringLower = string.replace(/ /g, '').toLocaleLowerCase();
const counts = {};
let ch;
let count;
let i;
for (i = 0; i < stringLower.length; i++) {
ch = stringLower.charAt(i);
count = counts[ch];
counts[ch] = count ? count + 1: 1;
}
console.log(stringLower);
console.log(counts);
}
(it currently outputs console so i can see the output).
the output i get is:
Object {t: 2, h: 1, i: 2, s: 2, g: 1…}
a:1
e:1
g:1
h:1
i:2
r:1
s:2
t:2
You can do a simple reduce on the array of characters
const toLowerCase = str =>
str.toLowerCase()
const numOccurrences = str =>
Array.from(str, toLowerCase).reduce((acc, c) =>
Object.assign(acc, { [c]: c in acc ? acc[c] + 1 : 1 }), {})
console.log(numOccurrences('This is great'))
// { t: 2, h: 1, i: 2, s: 2, ' ': 2, g: 1, r: 1, e: 1, a: 1 }
What you're getting should be the expected result. I get the same thing in chrome devtools.
Object {t: 2, h: 1, i: 2, s: 2, g: 1…} Objects with many properties get abbreviated, thus the ellipsis. You are then able to click to expand the object and view the full list of properties.
I've tried to sort an array of integers ascending.
For understanding the behaviour of the sort-method I've put a console.log in the callback-function:
var field = [50, 90, 1, 10, 2];
console.log(field.join(' | '));
var i = 0
field.sort(function(a, b) {
console.log(++i + 'a: ' + a + ', b: ' + b);
if (a === b) {
return 0;
} else if (a > b) {
return 1;
} else {
return -1;
}
});
console.log(field.join(' | '));
Result of the console.log:
PROTOKOLL: 50 | 90 | 1 | 10 | 2
PROTOKOLL: 1 a: 90, b: 50
PROTOKOLL: 2 a: 1, b: 90
PROTOKOLL: 3 a: 1, b: 50
PROTOKOLL: 4 a: 10, b: 90
PROTOKOLL: 5 a: 10, b: 50
PROTOKOLL: 6 a: 10, b: 1
PROTOKOLL: 7 a: 2, b: 90
PROTOKOLL: 8 a: 2, b: 10
PROTOKOLL: 9 a: 2, b: 1
PROTOKOLL: 1 | 2 | 10 | 50 | 90
After all I know about the Array.sort() method the result should be:
a: 50, b: 90
... but it's the other way.
Can anyone explain it?
It works like that.
Always take the second number and check if it in the right place:
Diagram:
1) 90 > 50 ----> No change
2) now check the second and third number 90 < 1 ---> 1 and 90 swap.
3) check if the first number is lesser than the new second number(1) - 50 < 1 - swap again 50 and 1.
4) 1 is the first number in the array, so go back to 90 and check the number after 90.
And it is continue like this. OK?
It's depends of implementation. In IE 11 you get output as:
50 | 90 | 1 | 10 | 2
1a: 90, b: 50
2a: 1, b: 90
3a: 1, b: 50
4a: 10, b: 90
5a: 10, b: 50
6a: 10, b: 1
7a: 2, b: 90
8a: 2, b: 10
9a: 2, b: 1
1 | 2 | 10 | 50 | 90
In Chrome you'll get:
50 | 90 | 1 | 10 | 2
1a: 50, b: 90
2a: 90, b: 1
3a: 50, b: 1
4a: 90, b: 10
5a: 50, b: 10
6a: 1, b: 10
7a: 90, b: 2
8a: 50, b: 2
9a: 10, b: 2
10a: 1, b: 2
1 | 2 | 10 | 50 | 90
But in Firefox you'll get:
50 | 90 | 1 | 10 | 2
1a: 50, b: 90
2a: 90, b: 1
3a: 50, b: 1
4a: 10, b: 2
5a: 90, b: 2
6a: 1, b: 2
7a: 50, b: 2
8a: 50, b: 10
1 | 2 | 10 | 50 | 90
Pay attention to number of steps. Final results are the same. The internal implementation is different.