Algolia query by INTERSECTION COUNT with two lists - algolia

I need to query some users based on "similar" interests. If a user A has 2 similar interests as user B then it's a match. I want to know if the following query is possible in Algolia.
Lets say I have two objects in some Algolia index with a list property on them (interests):
obj1 = {
interests: ['A', 'B', 'C'],
}
obj2 = {
interests: ['B', 'C', 'D'],
}
And I want to query all objects with interests having at least 2 of the following:
interests: ['A', 'B', 'E']
This should return me just obj1 since is the one having 2 interests alike.
Any ideas?

I'm not sure of an easier way to do this. But let me show my findings
interests: ['A', 'B', 'E']
now to capture your requirement the filters statement should be something like
'(interests:A AND interests:B) OR (interests:A AND interests:E) OR (interests:B AND interests:E)'
which if take as a boolean expression is in the form of AB+AC+BC.
But this particular query cannot be used with algolia according to their docs
For performance reasons, we do not support the following boolean combinations:
...
We limit filter expressions to a conjunction (ANDs) of disjunctions
(ORs). For example you can use filter1 AND (filter2 OR filter3)), but
not ORs of ANDs (e.g. filter1 OR (filter2 AND filter3).
But we can convert the AB+AC+BC to a product of sum format. I used https://www.dcode.fr/boolean-expressions-calculator and obtained the equivalent
(A+B).(A+C).(B+C) which would then be
'(interests:A OR interests:B) AND (interests:A OR interests:E) AND (interests:B OR interests:E)'
The query also depends on how many elements are there in the interests array. For example if interests: ['A', 'E', 'C', 'F'] your final filter query would look in the form
'(interests:A OR interests:E OR interests:C) AND (interests:A OR interests:E OR interests:F) AND (interests:A OR interests:C OR interests:F) AND (interests:E OR interests:C OR interests:F)'
Individual product terms have length of interest array-1 terms
TLDR: '(interests:A OR interests:B) AND (interests:A OR interests:E) AND (interests:B OR interests:E)'
you can use a use a combination generating code and get the filter query.
Here is a JS example based on this solution.
const k_combinations = (set, k) => {
let i, j, combs, head, tailcombs;
if (k > set.length || k <= 0) {
return [];
}
if (k == set.length) {
return [set];
}
if (k == 1) {
combs = [];
for (i = 0; i < set.length; i++) {
combs.push([set[i]]);
}
return combs;
}
combs = [];
for (i = 0; i < set.length - k + 1; i++) {
head = set.slice(i, i+1);
tailcombs = k_combinations(set.slice(i + 1), k - 1);
for (j = 0; j < tailcombs.length; j++) {
combs.push(head.concat(tailcombs[j]));
}
}
return combs;
}
const combinations = (set) => {
let k, i, combs, k_combs;
combs = [];
for (k = 1; k <= set.length; k++) {
k_combs = k_combinations(set, k);
for (i = 0; i < k_combs.length; i++) {
combs.push(k_combs[i]);
}
}
return combs;
}
const generateFilterQuery = (array) => {
const combinationSize = array.length - 1
const combinations = k_combinations(array, combinationSize)
return combinations.map((comb) => `(${comb.map(c => `interests:${c}`).join(" OR ")})`).join(" AND ")
}
console.log(generateFilterQuery(["A","B","E"]))
console.log(generateFilterQuery(["A","B","C","D"]))
console.log(generateFilterQuery(["A","B","C","D","E"]))
After generating the filter query pass it as the value of filters parameter
index.search('',{
filters: generatedQuery
}
).then(({hits}) => console.log(hits))

Related

Is there a better way to calculate the moving sum of a list in flutter

Is there a better way to calculate a moving sum of a list?
List<double?> rollingSum({int window = 3, List data = const []}) {
List<double?> sum = [];
int i = 0;
int maxLength = data.length - window + 1;
while (i < maxLength) {
List tmpData = data.getRange(i, i + window).toList();
double tmpSum = tmpData.reduce((a, b) => a + b);
sum.add(tmpSum);
i++;
}
// filling the first n values with null
i = 0;
while (i < window - 1) {
sum.insert(0, null);
i++;
}
return sum;
}
Well, the code is already clean for what you need. Maybe just some improvements like:
Use a for loop
You can use the method sublist which creates a "view" of a list, which is more efficient
To insert some values in the left/right of a list, there is a specific Dart method called padLeft, where you specify the lenght of the list which you want it to become (first parameter), then the value you want to use to fill it (second parameter). For example, if you have an array of N elements, and you want to fill it with X "null"s to the left, use padLeft(N+X, null).
List<double?> rollingSum({int window = 3, List data = const []}) {
List<double?> sum = [];
for (int i = 0; i < data.length - window + 1; i++) {
List tmpData = data.sublist(i, i + window);
double tmpSum = tmpData.reduce((a, b) => a + b);
sum.add(tmpSum);
}
sum.padLeft(window - 1, null);
return sum;
}
if I understand your problem correctly you can just calculate the window one time and in one loop you can for each iteration you can add the current element to the sum and subtract i - (window - 1)
so for an input like this
data = [1,2,3,4,5,6]
window = 3
the below code will result in [6,9,12,15]
int sum = 0;
List<double> res = [];
for (int i = 0;i<data.length;i++) {
sum += data[i];
if (i < window - 1) {
continue;
}
res.add(sum);
sum -= data[i - (window - 1)]; // remove element that got out of the window size
}
this way you won't have to use getRange nor sublist nor reduce as all of those are expensive functions in terms of time and space complexity

Iterating over a list in groups of two or more

I like to iterate over a list and split them up in couples like this:
List<String> list = [1,2,3,4,5,6,7,8];
List<Tuple2> listOfTuples = list.take2((value1,value2) => Tuple2(value1,value2));
print(listOfTuples.toString()); // output => [[1,2],[3,4],[5,6],[7,8]]
I know there is a take(count) in dart but I did not find a good example.
I know I can do it with a for loop etc. but I am wondering if there us a more elegant way.
There is nothing built in. The way I'd write this today is:
var list = [1, 2, 3, 4, 5, 6, 7, 8];
var tuples = [
for (int i = 0; i < list.length - 1; i += 2) Tuple2(list[i], list[i + 1]),
];
You could write an extension that gives an api take2 on List that could be used in the way you describe.
extension Take2<T> on List<T> {
List<R> take2<R>(R Function(T, T) transform) => [
for (int i = 0; i < this.length - 1; i += 2)
transform(this[i], this[i + 1]),
];
}

merge sort performance compared to insertion sort

For any array of length greater than 10, is it safe to say that merge sort performs fewer comparisons among the array's elements than does insertion sort on the same array because the best case for the run time of merge sort is O(N log N) while for insertion sort, its O(N)?
My take on this. First off, you are talking about comparisons, but there are swaps as well that matter.
In insertion sort in the worst case (an array sorted in opposite direction) you have to do n^2 - n comparisons and swaps (11^2 - 11 = 121 - 11 = 110 for 11 elements, for example). But if the array is even partially sorted in needed order (I mean many elements already stay at correct positions or even not far from them), the number of swaps&comparisons may significantly drop. The right position for the element will be found pretty soon and there will be no need for performing as many actions as in case of an array sorted in opposite order. So, as you can see for arr2, which is almost sorted, the number of actions will become linear (in relation to the input size) - 6.
var arr1 = [11,10,9,8,7,6,5,4,3,2,1];
var arr2 = [1,2,3,4,5,6,7,8,11,10,9];
function InsertionSort(arr) {
var arr = arr, compNum = 0, swapNum = 0;
for(var i = 1; i < arr.length; i++) {
var temp = arr[i], j = i - 1;
while(j >= 0) {
if(temp < arr[j]) { arr[j + 1] = arr[j]; swapNum++; } else break;
j--;
compNum++;
}
arr[j + 1] = temp;
}
console.log(arr, "Number of comparisons: " + compNum, "Number of swaps: " + swapNum);
}
InsertionSort(arr1); // worst case, 11^2 - 11 = 110 actions
InsertionSort(arr2); // almost sorted array, few actions
In merge sort we always do aprox. n*log n actions - the properties of the input array don't matter. So, as you can see in both cases we will get both of our arrays sorted in 39 actions:
var arr1 = [11,10,9,8,7,6,5,4,3,2,1];
var arr2 = [1,2,3,4,5,6,7,8,11,10,9];
var actions = 0;
function mergesort(arr, left, right) {
if(left >= right) return;
var middle = Math.floor((left + right)/2);
mergesort(arr, left, middle);
mergesort(arr, middle + 1, right);
merge(arr, left, middle, right);
}
function merge(arr, left, middle, right) {
var l = middle - left + 1, r = right - middle, temp_l = [], temp_r = [];
for(var i = 0; i < l; i++) temp_l[i] = arr[left + i];
for(var i = 0; i < r; i++) temp_r[i] = arr[middle + i + 1];
var i = 0, j = 0, k = left;
while(i < l && j < r) {
if(temp_l[i] <= temp_r[j]) {
arr[k] = temp_l[i]; i++;
} else {
arr[k] = temp_r[j]; j++;
}
k++; actions++;
}
while(i < l) { arr[k] = temp_l[i]; i++; k++; actions++;}
while(j < r) { arr[k] = temp_r[j]; j++; k++; actions++;}
}
mergesort(arr1, 0, arr1.length - 1);
console.log(arr1, "Number of actions: " + actions); // 11*log11 = 39 (aprox.)
actions = 0;
mergesort(arr2, 0, arr2.length - 1);
console.log(arr2, "Number of actions: " + actions); // 11*log11 = 39 (aprox.)
So, answering your question:
For any array of length greater than 10, is it safe to say that merge sort performs fewer comparisons among the array's elements than does insertion sort on the same array
I would say that no, it isn't safe to say so. Merge sort can perform more actions compared to insertion sort in some cases. The size of an array isn't important here. What is important in this particular case of comparing insertion sort vs. merge sort is how far from the sorted state is your array. I hope it helps :)
BTW, merge sort and insertion sort have been united in a hybrid stable sorting algorithm called Timsort to get the best from both of them. Check it out if interested.

Specman On the Fly Generation: How to constrain a list whose values are differ from each other at least 2

I need to generate a random list values with the next constrain:
my_list[i] not in [my_list[i-1] .. my_list[i-1] + 1]
i.e. all values in the list are different and with at least difference of 2 between each other. All code variations I've tried failed, e.g.:
var prev_val : uint = 0;
gen my_list keeping {
it.size() == LIST_SIZE;
it.all_different(it);
for each (val) in it {
val not in [prev_val .. prev_val + 1];
prev_val = val;
};
};
How such list can be generated? Thank you for your help
I am not sure I fully understand the request but following your code:
gen my_list keeping {
it.size() == LIST_SIZE;
it.all_different(it);
keep for each (val) in it {
val != prev;
val != prev + 1;
};
};
This will generate a list (all items will be generate together) according to your rule:
my_list[i] not in [my_list[i-1] .. my_list[i-1] + 1]
But the following list is a valid solution: 0,2,1,3,5,4,6,8,7,9,11,10,12,...
which doesn't follow "the all values in the list are different and with at least difference of 2 between each other".
To generate a list according to the "text request", you must use double keep for each and abs:
gen my_list keeping {
it.size() == LIST_SIZE;
for each (val1) using index (i1) in it {
for each (val2) using index (i2) in it {
i1 < i2 => abs(val1-val2) >= 2;
};
};
If you want my_list to be sorted (and will be solved faster) :
gen my_list keeping {
it.size() == LIST_SIZE;
it.size() == LIST_SIZE;
it.all_different(it);
for each (val) in it {
val >= prev + 2;
};
};
You could try the following:
gen my_list keeping {
it.size() == 10;
it.all_different(it);
for each (val) in it {
index > 0 =>
val not in [value(it[index - 1]) .. value(it[index - 1]) + 1];
};
};
The solver requires the it[index - 1] expression in the constraint be "fixed" at the point of generation, hence the use of value(...). This means that the list will be generated element by element.
If that's a problem, you could change to:
index > 0 =>
val != it[index - 1] + 1;
This should be equivalent, since the all_different(...) constraint should make sure that an element doesn't have the same value as the previous one. Naturally, this won't work if you have a wider set.

How to find the Intersection of n arrays

I have n arrays or variable length
arr1 = [1,2,3]
arr2 = [1,3,5,8]
....
How can I compute the intersection of those n arrays ?
Consider checking out underscore.js library. It provides function for what you need and a bunch of other usefull functions.
Example from docs:
_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);
=> [1, 2]
Simple plain JS implementation can be found here. Same idea in CoffeeScript:
intersect_all = (lists) ->
if lists.length is 0
return []
else return lists[0] if lists.length is 1
partialInt = lists[0]
i = 1
while i < lists.length
partialInt = intersection(partialInt, lists[i])
i++
partialInt
The most efficient way is to use hashsets:
function hashset (elements) {
var i, set = {};
if (!Array.isArray(elements)) return elements;
for (i = 0; i < elements.length; i++) {
set[elements[i]] = true;
}
return set;
};
function intersect (a, b) {
var k
, s1 = hashset(a)
, s2 = hashset(b)
, s3 = {}
for (k in s1) {
if (s2[k]) s3[k] = true;
}
return s3;
};
Object.keys(intersect(arr1,arr2));
// ["1", "3"]
You will find CoffeeScript source of this code, benchmarks for it and some additional information in this question.
If you're going to to intersect huge arrays then I strongly recommend you to use this approach.
Either just use something like _.intersection or study the source of that implementation and rewrite it if you like.