I have scoured the internet for all the various versions of quicksort implementation to translate into JavaScript and many of them do not successfully port.
I haven't been able to figure out if this is due to me not knowing a nuance about Java or C++, or the examples that people posted are broken.
I am not optimizing for performance, but how readable and logical it is to me.
I have arrived at this implementation, but I noticed that it does not work.
Outputs are random (likely due to the Math.random()), but as I follow the algo, I get frustrated with this following test case.
Outputs range from 999, 3, 100, 2, and 1000. I cannot follow the logic and would really appreciate someone explaining what's happening to give such erratic results.
function swap(array, idxA, idxB) {
var temp = array[idxA]
array[idxA] = array[idxB]
array[idxB] = temp
}
function partitionStart(arr, left, right, pivotIdx) {
var storeIdx = left, pivotVal = arr[pivotIdx];
swap(arr, pivotIdx, right)
for (var i = left; i <right; i++) {
if (arr[i] < pivotVal) {
swap(arr, storeIdx, i)
storeIdx++
}
}
swap(arr, pivotIdx, right);
return storeIdx;
}
function quickSelectLoop(arr, k) {
var pivotIndex, pivotNewIdx, pivotDist,
left = 0, right = arr.length-1
while(true) {
pivotIndex = Math.floor(Math.random()*arr.length)
pivotNewIdx = partitionStart(arr, left, right, pivotIndex)
pivotDist = pivotNewIdx - left
if (pivotDist === k) {
return arr[pivotNewIdx-1]
} else if (k < pivotDist) {
right = pivotNewIdx -1
} else {
k -= pivotDist+1
left = pivotNewIdx + 1
}
}
}
var test2 = [1000,999,1,2,3,100,105]
console.log(quickSelectLoop(test2, 4))
expected output from quickSelect(test2, 4) => 100 since 100 is the 4th smallest element in the collection
Your current implementation has multiple flaws. I don't really understand what is the idea of your current code, so I'll try to explain how I understood your code, and then provide a corrected one.
partitionStart - partitions part of array from left to right indices using item at pivotIdx as parts separator. Returns index of separation sepIdx, such that every item before sepIdx is less than pivot item, and every item after it is greater or equal to it.
quickSelectLoop - selects k-th smallest item from the given array.
Function relies on invariant that all items between left and right, while being in arbitrary order, are array's left..right smallest items, e.g.
if left = 0, right = 2, initial array = {0,1,2,3,4}, then
arr = [A,B,C,x,x], where {A,B,C} = {0,1,2}, so arr = [2,1,0,4,3] and arr = [0,1,2,3,4] are both correct.
Corrected code with commentaries:
function partitionStart(arr, left, right) {
// You were passing pivotIdx here, I think that selecting pivotIdx
// should be this method's responsibility, so I moved the code here
// Also you were taking pivotIdx ignoring left and right - fixed that
var pivotIdx = Math.floor(Math.random() * (right - left + 1)) + left;
var storeIdx = left, pivotVal = arr[pivotIdx]
// You had a swap of pivot with the right here,
// which allowed you to traverse 1 item less in a cycle,
// but with the cost of one line of code - removed that
for (var i = left; i <= right; i++) {
if (arr[i] < pivotVal) {
swap(arr, storeIdx, i)
storeIdx++
}
}
// Here was a strange swap of pivot back from right to its position,
// now it is not needed.
return storeIdx;
}
function quickSelectLoop(arr, k) {
var pivotDist;
var left = 0, right = arr.length - 1;
while(right !== left) {
// Maintaining: left <= k <= right, while enclosing left to right
pivotDist = partitionStart(arr, left, right)
// You were returning arr[k] here if pivotDist == k,
// but that is incorrect considering function's invariant -
// we can't make such a conclusion unless left == right.
// I corrected that check - it is now located in the while loop.
if (k < pivotDist) {
right = pivotDist - 1;
} else {
// You were adding 1 here, which is incorrect,
// because item at pivotDist may be the answer as well.
left = pivotDist;
}
}
// left == right, and we maintained 'left <= k <= right', so we have an answer
return arr[k]
}
jsfiddle
Related
I am trying to work out how this function operates,
function mergeSort(a) {
if (a.length === 1) return a;
const mid = Math.trunc(a.length / 2);
const left = mergeSort(a.slice(0, mid));
const right = mergeSort(a.slice(mid));
const result = [];
((l, r) => {
let i = 0,
j = 0;
while (i < l.length && j < r.length) {
l[i] < r[j] ? result.push(l[i++]) : result.push(r[j++]);
}
while (i < l.length) result.push(l[i++]);
while (j < r.length) result.push(r[j++]);
})(left, right);
return result;
}
const random = [10, 5, 2, 7, 3, 4];
I dont understand what is keeping the left side / right side in its memory.
on the first iteration the Immediately Invoked Function Expression (IIFE) has the parameters [5,2] because [1] was returned. It then sorts [5,2]. why then does the IIFE run again sorting left[10], right[2,5]? what causes this action?
I dont understand what is keeping the left side / right side in its memory
The left/right side is kept in the left and right variables for each function call. Since variables are function-scoped that means that each call can have their own version of left and right
Merge sort is a classic example of the "Divide and conquer" method. The input array is split into smaller and smaller fractions through recursion. The recursion happens until we're left with 1 element units.
After that it's just a matter of merging 2 sorted arrays on the way up out of the recursion.
There are very neat websites showcasing the visualization of algorithms. Take a look at this website here
Edit: As Dai mentioned in his comment. Try to run this code through a debugger. It'll give you a better overview on how exactly your code runs line by line.
Edit 2:
((l, r) => {
let i = 0,
j = 0;
while (i < l.length && j < r.length) {
l[i] < r[j] ? result.push(l[i++]) : result.push(r[j++]);
}
while (i < l.length) result.push(l[i++]);
while (j < r.length) result.push(r[j++]);
})(left, right);
This code is what merges 2 sorted arrays and sorts them while merging.
The first while compares the first first element in the left side and the first in the right. It adds the smaller one to the results array and continues comparing.
After there's nothing left to compare (we've exhausted either of the lists) we just shove what remained of the other list into the results array.
I find the visibility algorithm in one great website
Since it is quiet long ago article, I am not sure that the author will give the answer.
As you can see, the interactive playground(try it yourself) does not work well in right upper side, especially overlapping edges.
// Pseudocode of main algorithm
var endpoints; # list of endpoints, sorted by angle
var open = []; # list of walls the sweep line intersects
loop over endpoints:
remember which wall is nearest
add any walls that BEGIN at this endpoint to 'walls'
remove any walls that END at this endpoint from 'walls'
figure out which wall is now nearest
if the nearest wall changed:
fill the current triangle and begin a new one
Here is also part of working JavaScript code
// Main algorithm with working Javascript code
export const calculateVisibility = (origin, endpoints) => {
let openSegments = [];
let output = [];
let beginAngle = 0;
endpoints.sort(endpointCompare);
for(let pass = 0; pass < 2; pass += 1) {
for (let i = 0; i < endpoints.length; i += 1) {
let endpoint = endpoints[i];
let openSegment = openSegments[0];
if (endpoint.beginsSegment) {
let index = 0
let segment = openSegments[index];
while (segment && segmentInFrontOf(endpoint.segment, segment, origin)) {
index += 1;
segment = openSegments[index]
}
if (!segment) {
openSegments.push(endpoint.segment);
} else {
openSegments.splice(index, 0, endpoint.segment);
}
} else {
let index = openSegments.indexOf(endpoint.segment)
if (index > -1) openSegments.splice(index, 1);
}
if (openSegment !== openSegments[0]) {
if (pass === 1) {
let trianglePoints = getTrianglePoints(origin, beginAngle, endpoint.angle, openSegment);
output.push(trianglePoints);
}
beginAngle = endpoint.angle;
}
}
}
return output;
};
In my opinion, it happens since the algorithm thinks right edge is shown whole part when it is the closest edge although it can be hidden from whole edges.
I think I can modify the algorithm to use the current closest edge and newer current closest edge but not sure...
How should I fix it?
It's pretty common to want to separate an array into a pair of arrays, one side pass and another side fail. The basic algorithm goes something like this:
function partition(list, cond) {
let left = [];
let right = [];
for (let item of list) {
if (cond(item)) {
left.push(item);
} else {
right.push(item);
}
}
return [left, right];
}
In my case, that condition is a bit more specialized - it's just an equal-length bitset where for each position, set = left, unset = right:
function partition(list, bitset) {
let left = [];
let right = [];
for (let i = 0; i < list.length; i++) {
if ((bitset[i >>> 5] & 1 << (i & 31)) !== 0) {
left.push(list[i]);
} else {
right.push(list[i]);
}
}
return [left, right];
}
Thing is, I want to do this in-place and just have those where the corresponding bit in the bitset is true to the left, those where it's false to the right in the same array. (The entries do need to retain their order - it's important. Otherwise, it'd be pretty obvious.) This is the most efficient version I've come up with so far:
// The return value is the start offset for the rejects
function partition(list, cond) {
let right = [];
let rightStart = 0;
for (let i = 0; i < list.length; i++) {
if ((bitset[i >>> 5] & 1 << (i & 31)) !== 0) {
list[rightStart++] = list[i];
} else {
right.push(list[i]);
}
}
for (let i = 0, j = rightStart; i < list.length; i++) {
list[j] = right[i];
}
return right_start;
}
Is it possible to do this with only O(1) space while still keeping it O(n) time, and if so, what would that algorithm look like?
The paper ...
Katajainen, J., Pasanen, T. Stable minimum space partitioning in linear time. BIT 32, 580–585 (1992).
https://doi.org/10.1007/BF01994842
... presents an algorithm for stable in-place partitioning in O(n) time and O(1) extra space.
The algorithm you are looking for is known as stable partition. In constant space it has O(n log n) time complexity. See for example here (it talks C++, but in this context it doesn't matter).
An implementation may look along the lines of
stable_partition(begin, end, predicate)
if (end - begin == 0)
return begin
if (end - begin == 1)
return predicate(*begin)? begin: end
mid = begin + (end - begin)/2
left_partition_point = stable_partition(begin, mid, predicate)
right_partition_point = stable_partition(mid, end, predicate)
return rotate(left_partition_point, mid, right_partition_point)
where rotate returns a position at which the leftmost element landed.
I recently ran into a problem where I need to figure out how to distribute items into buckets but I need to find all the ways to distribute them.
the input comes in as an array of integers that tell you the maximum each column can hold and there must be N amount of items in the array.
for example:
maxItems = 3
maximums = [4,2,1] # The order of maximums DOES matter meaning
# This means that the results of maximums = [2,4,1] are different from maximums = [1,2,4]
outputs = [[3,0,0],[2,1,0],[1,1,1],[2,0,1],[0,2,1]] # results are in no particular order
# notice how the sum of each result is equal to maxItems and each value in each of the rows are less than the value inside of maximums
I attempted to solve this problem in javascript but I am unable to figure out how to approach this problem. I wanted to start off by filling the first columns with as many numbers as possible and start moving to the right, but as the maximums array gets bigger, this method gets more inaccurate and I don't exactly know how to approach it at all.
If you have any more questions please feel free to ask if you dont understand the problem.
The code I started off with in javascript was
var all_combinations = function(N, maximums){
var empty = maximums.map(function(){return 0;}); // create empty array size of maximums filled with 0s
var s = 0;
for (var i = 0; i < empty.length && s < N;){
if (empty[i] >= maximums[i]){i++;continue;}
empty[i]++;
s++;
} // fill the left side with as many items as possible
// Then i would proceed to move one item at a time to the right side but some how i would need to do it for the whole array and this is where I get stuck.
};
I tried searching up this problem, but I never found out how to do it the way it was set up here. I tried finding similar problems but they were always unrelated to this. Maybe I am searching up the problem wrong. If someone can link a helpful resource that would be great.
If you have any questions please ask them. I will answer to the best of my abilities.
You could use a recursive approach with checking all parts of the constraints.
It works with an index and a temporary array for keeping the count of the items.
At start, the index is zero and the array is empty. With the call of fork, the first exit option is checked, which means the constraints are checked and if greater or equal count, then the recursion stops.
The second exit option is when the sum of the items reaches the wanted count, then the temporary array is pushed to the result set and the recursion ends.
In all other cases, fork is called again with either
same index i and an incremented value of the temporary array at the index, or
incremented index and the actual temporary array.
function getCombination(max, count) {
function fork(index, temp) {
var sum = temp.reduce((a, b) => a + b, 0);
if (max.some((a, i) => (temp[i] || 0) > a) || index === max.length || sum > count) {
return;
}
if (sum === count) {
result.push(temp);
return;
}
fork(index, max.map((a, i) => (temp[i] || 0) + (i === index)));
fork(index + 1, temp);
}
var result = [];
fork(0, []);
return result;
}
console.log(getCombination([4, 2, 1], 3));
.as-console-wrapper { max-height: 100% !important; top: 0; }
An iterative approach with a previous check if the sum plus value is smaller or equal than the wanted count.
function getCombination(max, count) {
function iter(index, sum, temp) {
var i;
if (count === sum) {
result.push(temp);
return;
}
for (i = max[index]; i >= 0; i--) {
if (sum + i <= count) {
iter(index + 1, sum + i, temp.concat(i));
}
}
}
var result = [];
iter(0, 0, []);
return result;
}
console.log(getCombination([4, 2, 1], 3));
.as-console-wrapper { max-height: 100% !important; top: 0; }
An easy to understand recursive solution with ECMA 6 generators:
for each i, place i items into the first slot if they fit, then distribute the others among the rest.
function* bucket_distributions(capacities,nItems){
if (capacities.length==1) {
if (capacities[0] >= nItems)
yield [nItems];
}
else if (capacities.length>1) {
for (var i=Math.min(capacities[0],nItems);i>=0;i--) {
for (subdist of
bucket_distributions(capacities.slice(1),nItems-i))
yield [i].concat(subdist);
}
}
}
console.log(Array.from(bucket_distributions([4,2,1],3)))
Here's a well-commented iterative solution with an interactive demo:
// reducer function for calculating sum of array
function sum(prev, next) {
return prev + next;
}
// returns the contextual constraints of a bucket
function bucketMinMax(maxItems, otherItems, bucketMax) {
return {
// minimum values in bucket to meet maxItems
min: Math.max(0, maxItems - otherItems),
// maximum values in bucket to meet maxItems
max: Math.min(maxItems, bucketMax),
};
}
// takes an incomplete combination and expands it with the next bucket
// starting from the left
function expandCombination(maxItems, maximums, combinations) {
// get next combo group to expand
var comboGroup = combinations.shift();
// get index of expansion bucket
var index = comboGroup.length;
// calculate maximum possible otherItems
var otherItems = maximums.slice(index + 1).reduce(sum, 0);
// removes already used spaces from maxItems in combination group being expanded
maxItems -= comboGroup.reduce(sum, 0);
// get constraints for expansion bucket
var {min, max} = bucketMinMax(maxItems, otherItems, maximums[index]);
for (var i = min; i <= max; i++) {
// add combo group expansions to output
combinations.push(comboGroup.concat([i]));
}
}
// main function
function allCombinations(maxItems, maximums) {
// will eventually contain all combinations
var output = [[]];
// loops through array of combinations, expanding each one iteratively
while (output.length > 0 && output[0].length < maximums.length) {
// takes incomplete combination group and expands it with possible values
// for next bucket starting from the left
expandCombination(maxItems, maximums, output);
}
return output;
}
document.addEventListener('change', () => {
var maxes = JSON.parse(maximums.value);
var items = JSON.parse(maxItems.value);
console.log(JSON.stringify(allCombinations(items, maxes)));
});
document.dispatchEvent(new Event('change'));
<label>maxItems
<input id="maxItems" value="3">
</label>
<label>maximums
<input id="maximums" value="[4,2,1]">
</label>
I have prepared 2 Javascript functions to find matching integer pairs that add up to a sum and returns a boolean.
The first function uses a binary search like that:
function find2PairsBySumLog(arr, sum) {
for (var i = 0; i < arr.length; i++) {
for (var x = i + 1; x < arr.length; x++) {
if (arr[i] + arr[x] == sum) {
return true;
}
}
}
return false;
}
For the second function I implemented my own singly Linked List, in where I add the complementary integer to the sum and search for the value in the Linked List. If value is found in the Linked List we know there is a match.
function find2PairsBySumLin(arr, sum) {
var complementList = new LinkedList();
for (var i = 0; i < arr.length; i++) {
if (complementList.find(arr[i])) {
return true;
} else {
complementList.add(sum - arr[i]);
}
}
return false;
}
When I run both functions I clearly see that the Linked List search executes ~75% faster
var arr = [9,2,4,1,3,2,2,8,1,1,6,1,2,8,7,8,2,9];
console.time('For loop search');
console.log(find2PairsBySumLog(arr, 18));
console.timeEnd(‘For loop search’);
console.time('Linked List search');
console.log(find2PairsBySumLin(arr, 18));
console.timeEnd('Linked List search');
true
For loop search: 4.590ms
true
Linked List search: 0.709ms
Here my question: Is the Linked List approach a real linear search? After all I loop through all the nodes, while my outer loop iterates through the initial array.
Here is my LinkedList search function:
LinkedList.prototype.find = function(data) {
var headNode = this.head;
if(headNode === null) {
return false;
}
while(headNode !== null) {
if(headNode.data === data) {
return true;
} else {
headNode = headNode.next;
}
}
return false;
}
UPDATE:
It was a good idea to go back and have another think of the problem based the comments so far.
Thanks to #nem035 comment on small datasets, I ran another test but this time with 100,000 integers between 1 and 8. I assigned 9 to the first and last position and searched for 18 to make sure the entire array will be searched.
I also included the relatively new ES6 Set function for comparison thanks to #Oriol.
Btw #Oriol and #Deepak you are right. The first function is not a binary search but rather a O(n*n) search, which has no logarithmic complexity.
It turns out my Linked List implementation was the slowest of all searches. I ran 10 iterations for each function individually. Here the result:
For loop search: 24.36 ms (avg)
Linked List search: 64328.98 ms (avg)
Set search: 35.63 ms (avg)
Here the same test for a dataset of 10,000,000 integers:
For loop search: 30.78 ms (avg)
Set search: 1557.98 ms (avg)
Summary:
So it seems the Linked List is really fast for smaller dataset up to ~1,000, while ES6 Set is great for larger datasets.
Nevertheless the For loop is the clear winner in all tests.
All 3 methods will scale linearly with the amount of data.
Please note: ES6 Set is not backward compatible with old browsers in case this operation has to be done client side.
Don't use this. Use a set.
function find2PairsBySum(arr, sum) {
var set = new Set();
for(var num of arr) {
if (set.has(num)) return true;
set.add(sum - num);
}
return false;
}
That's all. Both add and has are guaranteed to be sublinear (probably constant) in average.
You can optimize this substantially, by pre-sorting the array and then using a real binary search.
// Find an element in a sorted array.
function includesBinary(arr, elt) {
if (!arr.length) return false;
const middle = Math.floor(arr.length / 2);
switch (Math.sign(elt - arr[middle])) {
case -1: return includesBinary(arr.slice(0, middle - 1), elt);
case 0: return true;
case +1: return includesBinary(arr.slice(middle + 1), elt);
}
}
// Given an array, pre-sort and return a function to detect pairs adding up to a sum.
function makeFinder(arr) {
arr = arr.slice().sort((a, b) => a - b);
return function(sum) {
for (let i = 0; i < arr.length; i++) {
const remaining = sum - arr[i];
if (remaining < 0) return false;
if (includesBinary(arr, remaining)) return true;
}
return false;
};
}
// Test data: 100 random elements between 0 and 99.
const arr = Array.from(Array(100), _ => Math.floor(Math.random() * 100));
const finder = makeFinder(arr);
console.time('test');
for (let i = 0; i < 1000; i++) finder(100);
console.timeEnd('test');
According to this rough benchmark, one lookup into an array of 100 elements costs a few microseconds.
Rewriting includesBinary to avoid recursion would probably provide a further performance win.
first of all find2PairsBySumLog function is not a binary search, it's a kind of brute force method which parses all the elements of array and it's worst case time complexity should be O(n*n), and the second function is a linear search that' why you are getting the second method to run fastly, for the first function i.e. find2PairsBySumLog what you can do is initialize binary HashMap and check for every pair of integers in array kind of like you are doing in the second function probably like
bool isPairsPresent(int arr[], int arr_size, int sum)
{
int i, temp;
bool binMap[MAX] = {0};
for (i = 0; i < arr_size; i++)
{
temp = sum - arr[i];
if (temp >= 0 && binMap[temp] == 1)
return true;
binMap[arr[i]] = 1;
}
}