Converting an array of numbers into a suitable regular expression - javascript

That's right. Unlike most questions, I am not trying to write a regular expression myself. I am trying to generate a regular expression (JavaScript flavoured, to be used in HTML5's pattern attribute).
Given an array of numbers, give a concise, fast, and correct regular expression that will only match the given input. I have already done part of the job, namely the ones [0-9]:
var ones = [0, 1, 2, 3, 4, 5, 8, 9],
onesRegex = "";
for (var i = 0; i < ones.length; i++) {
e = ones[i];
if (i > 0 && e == ones[i - 1] + 1) {
if (i != ones[i + 1] - 1) {
onesRegex += e + "]";
}
} else {
if (onesRegex != "") onesRegex += "|";
onesRegex += "[" + e + "-";
}
}
// Returns [0-5]|[8-9]
alert(onesRegex);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
This could then be used in an <input> (yes, jQuery is allowed):
$("input").attr("pattern", onesRegex);
The problem I am experiencing is that I am not sure how to continue. Ones are easy enough, as you see above. However, things get increasingly more difficult as soon as you start adding digits because you have to take into account so many things. For instance, you can have [112, 358, 359, 360, 361] which should result in (112|(3(5[8-9]|6[0-1]))) which is already quite extensive for only five numbers.
For my project, the maximum value is 500, so all values < 1000 should be parsable.
I have written quite a bit, but there's a lot to be done -- I need to get the logic behind it. So far my idea is to split the number in ones, tens, and hundreds, and treat them accordingly. Additionally, the appropriate function can waterfall down to other functions. For instance, parsing the number 512 could split it down into 5 and 12, 12 will go down to a function for decimals, and so on. That's the main idea, but the logic and structure is missing.
Here is what I have so far, but I also provide a JSFiddle which is a bit easier to work with.
var arr = [0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 105, 106, 107, 256, 257, 258, 259, 260],
onesArray = [],
tensArray = [],
hundredsArray = [];
// MAIN
function regexGenerator() {
orderSplitter(arr);
// Do stuff
// Should return finished Regex as a string
}
// Split input array in ones (1 digit), tens (2 digits), hundreds (3 digits)
function orderSplitter(numberArray) {
$(numberArray).each(function(index, element) {
if (element < 10) {
onesArray.push(element);
} else if (element < 100 && element > 9) {
tensArray.push(element);
} else if (element < 1000 && element > 99) {
hundredsArray.push(element);
}
});
}
/* Following functions expect an array as input */
function onesToRegex(ones) {
var onesRegex = "";
for (var i = 0; i < ones.length; i++) {
var e = ones[i];
if (i > 0 && e == ones[i - 1] + 1) {
if (i != ones[i + 1] - 1) {
onesRegex += e + "]";
}
} else {
onesRegex += "[" + e + "-";
}
}
return onesRegex;
}
function tensToRegex(tens) {
var tensRegex = "";
for (var j = 0; j < tens.length; j++) {
var f = tens[j],
ten = Math.floor(f / 10),
one = f - (ten * 10);
}
return tensRegex;
}
function hundredsToRegex(hundreds) {
var hundredsRegex = "";
for (var k = 0; k < hundreds.length; k++) {
var g = tens[j],
hundred = Math.floor(g / 100),
ten = Math.floor((g - (hundred * 100)) / 10),
one = g - (ten * 10);
}
return hundredsRegex;
}

As an alternative approach, consider using HTML5 <datalist>. This can be generated in JavaScript too.
var arr = [.......];
var datalist = document.createElement('datalist');
arr.forEach(function(num) {
var option = document.createElement('option');
option.value = num;
datalist.appendChild(option);
});
datalist.id = "numberlist";
document.body.appendChild(datalist);
// apply to input
someInputElement.setAttribute("list","numberlist");
Here's a demo for you: https://jsfiddle.net/960sjuhc/

A proposal with a tree.
Basically it has two parts
build tree with an object, where the length of the stringed numbers are the first key, and the rest are properties with one digit and an object.
build the regular expression string with iterating over the first key (the length) and start iter with the content of the property and the decremented length/depth of the following object.
function getRegex(array) {
function group(array) { // take array [0,1,3,4,5,6,8] return string '013-68'
return array.reduce(function (r, a, i, aa) {
if (!i || aa[i - 1] + 1 !== a) {
return r.concat([[a]]);
}
r[r.length - 1][1] = a;
return r;
}, []).map(function (a) {
return a.join(a[0] + 1 === a[1] ? '' : '-');
}).join('');
}
function iter(o, l) { // iterate an object
// get all keys form the object as sorted numbers
var keys = Object.keys(o).map(Number).sort(function (a, b) { return a - b; });
if (keys.length === 1) { // if just one key return the key and get the next level
return keys[0] + iter(o[keys[0]], l - 1);
}
if (keys.length > 1) { // if more than one key
// test the level
// if next level
// return parenthesis with all keys and their next levels separated with |
// if no level
// return grouped keys with brackets around
return l ?
'(' + keys.map(function (k) { return k + iter(o[k], l - 1); }).join('|') + ')' :
'[' + group(keys) + ']';
}
return '';
}
var tree = {};
array.forEach(function (a) {
var o, s = a.toString();
tree[s.length] = tree[s.length] || {};
o = tree[s.length];
s.split('').forEach(function (b) {
o[b] = o[b] || {};
o = o[b];
});
});
return '(' + Object.keys(tree).map(function (k) { return iter(tree[k], +k - 1); }).join('|') + ')';
}
document.write('<pre>' + getRegex([0, 1, 2, 3, 4, 5, 8, 9]) + '</pre>');
document.write('<pre>' + getRegex([100, 200, 212, 213, 214, 357]) + '</pre>');
document.write('<pre>' + getRegex([112, 358, 359, 360, 361]) + '</pre>');
document.write('<pre>' + getRegex([0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 105, 106, 107, 256, 257, 258, 259, 260]) + '</pre>');

As was pointed out in the comments, I should have had some fun with it -- and I followed your advice and here we are! My solution is probably not as efficient as Nina Scholz's answer (not tested, but that answer just looks more... detailed) but it is better readable in my opinion and it was a lot of fun to make -- and not at all as hard as I had thought, once I got my head around it.
It's on JSFiddle. And also here as a snippet. I did my best commenting the some-what harder parts, but most of it should be quite straightforward, though in retrospect I could've chosen some better variable names. Comments are welcome!
var arr = [0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 105, 106, 107, 256, 257, 258, 259, 260],
onesArray = [],
tensArray = [],
tensMultiArray = {},
hundredsArray = [],
hundredsMultiArray = {};
// MAIN
function regexGenerator(arr) {
orderSplitter(arr);
var onesRegexString = onesToRegex(onesArray);
var tensRegexString = tensToRegex(tensArray);
var hundredsRegexString = hundredsToRegex(hundredsArray);
// Don't forget start/end points ^$
var regex = "(^(" + onesRegexString + ")$)|(^(" + tensRegexString + ")$)|(^(" + hundredsRegexString + ")$)";
$(".result code").text(regex);
}
regexGenerator(arr);
// Split input array in ones (1 digit), tens (2 digits), hundreds (3 digits)
// Can be extended to include others
function orderSplitter(numberArray) {
$(numberArray).each(function(index, element) {
if (element < 10) {
onesArray.push(element);
} else if (element < 100 && element > 9) {
tensArray.push(element);
} else if (element < 1000 && element > 99) {
hundredsArray.push(element);
}
});
}
/* Following functions expect an array as input */
function onesToRegex(ones) {
var onesRegex = "";
for (var i = 0; i < ones.length; i++) {
var e = ones[i];
// If this element is not the first element, and it is equal to
// the previous number + 1
if (i > 0 && e == (ones[i - 1] + 1)) {
// If this element is NOT equal to the next element - 1
// Will also return true if next item does not exist
if (e != (ones[i + 1] - 1)) {
onesRegex += e + "]";
}
}
// If this item is a (new) first item in a list
else {
if (onesRegex != "") onesRegex += "|";
onesRegex += "[" + e + "-";
}
}
return onesRegex;
}
function tensToRegex(tens) {
var tensRegex = "";
// Loop the array and break the number down in digits
// E.g. 13 -> ten = 1; one = 3
$(tens).each(function(index, element) {
var ten = Math.floor(element / 10),
one = element - (ten * 10);
// Push items to associative arrays (objects)
if (!(ten in tensMultiArray)) {
tensMultiArray[ten] = [one];
} else {
tensMultiArray[ten].push(one);
}
});
var i = 0;
for (var ten in tensMultiArray) {
if (tensMultiArray.hasOwnProperty(ten)) {
// Each iteration is a new number, meaning it is an *alternative*
// Hence the pipe
if (i > 0) tensRegex += "|";
tensRegex += ten;
// The `one` digits belonging to ten (e.g. 1 and 2 for 11 and 12) is an array
// Therefore we can send it down to onesToRegex to be processed
if (tensMultiArray[ten].length > 1) {
tensRegex += "(" + onesToRegex(tensMultiArray[ten]) + ")";
} else {
tensRegex += tensMultiArray[ten][0];
}
i++;
}
}
return tensRegex;
}
function hundredsToRegex(hundreds) {
var hundredsRegex = "";
// Loop the array and break the number down in hundreds and rest
// E.g. 128 -> hundred = 1; rest = 28
$(hundreds).each(function(index, element) {
var hundred = Math.floor(element / 100),
rest = element - (hundred * 100);
// Push items to associative arrays (objects)
if (!(hundred in hundredsMultiArray)) {
hundredsMultiArray[hundred] = [rest];
} else {
hundredsMultiArray[hundred].push(rest);
}
});
var i = 0;
for (var hundred in hundredsMultiArray) {
if (hundredsMultiArray.hasOwnProperty(hundred)) {
// Each iteration is a new number, meaning it is an *alternative*
// Hence the pipe
if (i > 0) hundredsRegex += "|";
hundredsRegex += hundred;
// The `rest` digits belonging to hundred (e.g. 28 and 29 for 128 and 129)
// is an array. Therefore we can send it down to tensToRegex to be processed
// In turn, tensToRegex will also send its ones through to onesToRegex
if (hundredsMultiArray[hundred].length > 1) {
hundredsRegex += "(" + tensToRegex(hundredsMultiArray[hundred]) + ")";
} else {
hundredsRegex += hundredsMultiArray[hundred][0];
}
i++;
}
}
return hundredsRegex;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1>
Generate Regular Expression based on an input array
</h1>
<p>
In this example the input is <code>[0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 105, 106, 107, 256, 257, 258, 259, 260]</code>. The result is:
</p>
<p class="result"><code></code></p>

Related

Finding maximum size of range in a sorted integer array

I am looking for an implementation in JavaScript for the following problem.
Consider a sorted array:
[1,2,5,9,10,12,20,21,22,23,24,26,27]
I would like to calculate the length of the maximum range that increased by 1, duplicates are not allowed.
The given example has the following ranges:
1,2
9,10
20,21,22,23,24 // the maximum range
26,27
So the return value for the given example should be 5.
I know how to solve this problem with the obvious solution, but I believe it is possible to solve the problem with more efficient and short algorithm.
A short solution
I don't think this is any more efficient than what pretty much everybody else has suggested, but the code is reasonably short and only loops over the array once, except for the first element. Not sure if it's any help:
var arr = [1, 2, 5, 9, 10, 12, 20, 21, 22, 23, 24, 26, 27];
var streak = 0, best = 0, bestStart;
for (var i = 1; i < arr.length; i++) {
if(arr[i]-arr[i-1] === 1) streak++;
else streak = 0;
if (streak > best) [best, bestStart] = [streak, i - streak];
}
var bestArr = arr.slice(bestStart, bestStart + best + 1);
console.log('Best streak: '+bestArr);
Speeding it up
After looking at the code, I realized that there is a way to speed it up slightly, by not checking the last few elements of the array, based on the previous value of best:
var arr = [1, 2, 5, 9, 10, 12, 20, 21, 22, 23, 24, 26, 27];
var streak = 0, best = 0, bestStart;
for (var i = 1; i < arr.length; i++) {
if(best > arr.length - i + streak) break;
if(arr[i]-arr[i-1] === 1) streak++;
else streak = 0;
if (streak > best) [best, bestStart] = [streak, i - streak];
}
var bestArr = arr.slice(bestStart, bestStart + best + 1);
console.log('Best streak: '+bestArr);
One possible solution would be to iterate the array, keeping the the current range as long as the numbers are successors. If the next number is not a successor of the previous number, close the current range and store its length - by comparing it to the length of the last range.
In this approach, the array is iterated only once and the maximum found length of a range is updated in constant time, yielding an O(n) algorithm where n is the number of elements in the input.
An implementation in C#-like pseudocode could be as follows.
int MaximumLength = minus infinity
int CurrentValue = Input[0];
int CurrentLength = 1;
for(int i = 1; i < Input.Length; i++)
{
if ( CurrentValue + 1 == Input[i] )
{
// same range
CurrentLength = CurrentLength + 1;
}
else
{
// new range
MaximumLength = Math.Max(MaximumLength, CurrentLength);
CurrentLength = 1;
}
CurrentValue = Input[i];
}
// check current length again after loop termination
MaximumLength = Math.Max(MaximumLength, CurrentLength);
It is impossible to obtain better than O(n) because the input cannot be read in less than O(n) time. If that would be possible, it would imply that there are instances for which the result does not depend on every element of the input, which is not the case for the given problem. The algorithm Philipp Maurer has sketched below would also yield an O(n) runtime bound if the maximum range length is 1, i.e. no adjacent numbers in the input are successors.
Something like this should find the maximum length first and not last.
Let max = 0
Let n = array length
While n > 2
Let m = 0
While m <= (array length - n)
Let first = m
Let last = m + n - 1
Let diff = (value of element 'last' in array) - (value of element 'first' in array)
if diff = n - 1 then
max = n
stop
end if
Increase m
end while
Decrease n
end while
Edit (javascript implementation)
var a = [1,2,5,9,10,12,20,21,22,23,24,26,27];
var max = 1;
var n = a.length;
while(n > 2) {
var m = 0;
while(m <= a.length - n)
{
var first = m;
var last = m + n - 1;
var diff = a[last] - a[first];
if (diff == n - 1 && diff > max) {
max = n;
break;
}
m++;
}
n--;
}
console.log(max);
JSFiddle
I think looping and comparing with stored previous maximum length is optimal solution. Maybe like this:
function findLongestRange(input) {
let maxLength = 0
let currentLength = 0
for (let i = 0; i < input.length; i++) {
if (i !== input.length) {
if (input[i] === input[i + 1] - 1) {
currentLength++
} else {
if (maxLength <= currentLength && currentLength !== 0) {
maxLength = currentLength + 1
}
currentLength = 0
}
}
}
return maxLength
}
const data = [1, 2, 5, 9, 10, 12, 20, 21, 22, 23, 24, 26, 27]
console.log(findLongestRange(data))
Here is the version with tests to check how it works with different input.
const data = [1, 2, 5, 9, 10, 12, 20, 21, 22, 23, 24, 26, 27]
function findLongestRange(input) {
let maxLength = 0
let currentLength = 0
for (let i = 0; i < input.length; i++) {
if (i !== input.length) {
if (input[i] === input[i + 1] - 1) {
currentLength++
} else {
if (maxLength <= currentLength && currentLength !== 0) {
maxLength = currentLength + 1
}
currentLength = 0
}
}
}
return maxLength
}
console.clear()
;[
[[1,2,5,6,7,1,2], 3],
[[], 0],
[data, 5],
[[1,2,3], 3],
[[1,3,4,6,8,1], 2],
[[1,3,5], 0],
].forEach((test, index) => {
const result = findLongestRange(test[0])
console.assert(result === test[1], `Fail #${index}: Exp: ${test[1]}, got ${result}`)
})
A Python answer:
l = [1,2,5,9,10,12,20,21,22,23,24,26,27]
current_range = None
current_range_val = 0
max_range = 0
max_range_val = 0
for i, j in zip(l, l[1:]):
if j - i == 1:
current_range_val += 1
if current_range is None:
current_range = (i, j)
current_range = (current_range[0], j)
else:
if current_range_val > max_range_val:
max_range = current_range
max_range_val = current_range_val
current_range_val = 0
current_range = (j, None)
print(max_range)
gives
(20, 24)

How many Odd and Even number are the in a array

I have created an Array with some numbers.
I want to find out how many even, and how many odd numbers it is in
this Array. I have to print it out like this: (this is just an example)
Even number: 6
Odd number: 7
I need to make a loop that count up how many it is off even and odd numbers.
This is what I have so far
<script>
window.onload = run;
var tall = [5,10,15,20,25,30,35,40,45,50];
function run() {
tall = [5,10,15,20,25,30,35,40,45,50];
liste(tall);
}
function liste(arr) {
var sumOdd = 0; // Odd 1, 3, 5 etc..
var sumPar = 0; // Even 2, 4, 6 etc..
for(var i = 0; i < arr.length; i++) {
if(arr[i] % 2 === 0) {
sumPar += arr.length;
}
else {
sumOdd += arr.length;
} // Even numbers // Odd numbers
document.getElementById("print").innerHTML = "Partall: " + sumPar + "<br />" + "Oddetall: " + sumOdd;
}
}
}
</script>
Its something that is wrong here, and I dont know what.
You could iterate with Array#reduce and count only the odds. For the rest just take the difference of the length of the array and the odds.
var tall = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50],
odd = tall.reduce(function (r, a) { return r + a % 2; }, 0),
even = tall.length - odd;
console.log('odd', odd);
console.log('even', even);
You were adding arr.length which is the array length. Instead you should simply increment the number
var tall = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50];
liste(tall);
function liste(arr) {
var sumOdd = 0;
var sumPar = 0;
for (var i = 0; i < arr.length; i++) {
if (arr[i] % 2 === 0) {
sumPar++;
} else {
sumOdd++;
}
}
console.log("Odd : " + sumOdd);
console.log("Par : " + sumPar);
}
You always add the complete Length of the array to your variable
Try this instead of sumPar += arr.length;:
sumPar++;

Finding closest sum of numbers to a given number

Say I have a list [1,2,3,4,5,6,7]
and I would like to find the closest sum of numbers to a given number. Sorry for the crappy explanation but here's an example:
Say I have a list [1,2,3,4,5,6,7] I want to find the closest numbers to, say, 10.
Then the method should return 6 and 4 or 7 and 3 because its the closest he can get to 10. So 5 + 4 would be wrong because thats 9 and he can make a 10.
another example : you want the closest to 14 , so then he should return 7 and 6
If you got any questions plz ask because its difficult to explain what I want :P
Functions for combine, locationOf, are taken from different answers, written by different authors.
printClosest([0.5,2,4] , 5);
printClosest([1, 2, 3, 4, 5, 6, 7], 28);
printClosest([1, 2, 3, 4, 5, 6, 7], 10.9);
printClosest([1, 2, 3, 4, 5, 6, 7], 10, 2);
printClosest([1, 2, 3, 4, 5, 6, 7], 10, 3);
printClosest([1, 2, 3, 4, 5, 6, 7], 14, 2);
function printClosest(array, value, limit) {
var checkLength = function(array) {
return array.length === limit;
};
var combinations = combine(array); //get all combinations
combinations = limit ? combinations.filter(checkLength) : combinations;//limit length if required
var sum = combinations.map(function(c) { //create an array with sum of combinations
return c.reduce(function(p, c) {
return p + c;
}, 0)
});
var sumSorted = sum.slice(0).sort(function(a, b) {//sort sum array
return a - b;
});
index = locationOf(value, sumSorted);//find where the value fits in
//index = (Math.abs(value - sum[index]) <= Math.abs(value - sum[index + 1])) ? index : index + 1;
index = index >= sum.length ? sum.length - 1 : index;
index = sum.indexOf(sumSorted[index]);//get the respective combination
console.log(sum, combinations, index);
document.getElementById("result").innerHTML += "value : " + value + " combi: " + combinations[index].toString() + " (limit : " + (limit || "none") + ")<br>";
}
function combine(a) {
var fn = function(n, src, got, all) {
if (n == 0) {
if (got.length > 0) {
all[all.length] = got;
}
return;
}
for (var j = 0; j < src.length; j++) {
fn(n - 1, src.slice(j + 1), got.concat([src[j]]), all);
}
return;
}
var all = [];
for (var i = 0; i < a.length; i++) {
fn(i, a, [], all);
}
all.push(a);
return all;
}
function locationOf(element, array, start, end) {
start = start || 0;
end = end || array.length;
var pivot = parseInt(start + (end - start) / 2, 10);
if (end - start <= 1 || array[pivot] === element) return pivot;
if (array[pivot] < element) {
return locationOf(element, array, pivot, end);
} else {
return locationOf(element, array, start, pivot);
}
}
<pre id="result"><pre>
var data= [1, 2, 3,4,5,6,7];
var closest = 14;
for (var x = 0; x < data.length; x++) {
for (var y = x+1; y < data.length; y++) {
if(data[x] + data[y] == closet){
alert(data[x].toString() + " " + data[y].toString());
}
}
}
From what I understood from your question, I made this snippet. I assumed you did not wanted to have the same digit twice (e.g 14 => 7 + 7).
It is working with your examples.
var arr = [1, 2, 3, 4, 5, 6, 7];
var a = 0, b = 0;
var nb = 14;
for(var i in arr) {
for(var j in arr) {
if(i != j) {
var tmp = arr[i] + arr[j];
if(tmp <= nb && tmp > a + b) {
a = arr[i];
b = arr[j];
}
}
}
}
document.write("Closest to " + nb + " => " + a + " + " + b);
I have a little bit long winded solution to the problem just so it is easier to see what is done.
The main benefits with solution below:
The second loop will not start from beginning of the array again. What I mean that instead of having loop_boundary for second loop as 0 as you normally would, here it starts from next index. This helps if your numbers array is long. However, if it as short as in example, the impact in performance is minimal. Decreasing first loop's boundary by one will prevent errors from happening.
Works even when the wanted number is 1 or negative numbers.
Fiddle:
JSFiddle
The code:
var numbers = [1,2,3,4,5,6,7];
var wanted_number = 1;
var closest_range, closest1, closest2 = null;
var loop1_boundary = numbers.length-1;
for(var i=0; i<loop1_boundary; i++) {
var start_index = i+1;
var loop2_boundary = numbers.length;
for(var k=start_index; k<loop2_boundary; k++) {
var number1 = parseInt(numbers[i]);
var number2 = parseInt(numbers[k]);
var sum = number1 + number2;
var range = wanted_number - sum;
document.write( number1+' + '+number2 +' < '+closest_range+'<br/>' );
if(Math.abs(range) < Math.abs(closest_range) || closest_range == null ) {
closest_range = range;
closest1 = number1;
closest2 = number2;
}
}
if(range==0){
break;
}
}
document.write( 'closest to given number was '+closest1+' and '+closest2+'. The range from wanted number is '+closest_range );
This proposal generates all possible combinations, collects them in an object which takes the sum as key and filters then the closest sum to the given value.
function getSum(array, sum) {
function add(a, b) { return a + b; }
function c(left, right) {
var s = right.reduce(add, 0);
if (s > sum) {
return;
}
if (!result.length || s === result[0].reduce(add, 0)) {
result.push(right);
} else if (s > result[0].reduce(add, 0)) {
result = [right];
}
left.forEach(function (a, i) {
var x = left.slice();
x.splice(i);
c(left.slice(0, i), [a].concat(right));
});
}
var result = [];
c(array, [], 0);
return result;
}
function print(o) {
document.write('<pre>' + JSON.stringify(o, 0, 4) + '</pre>');
}
print(getSum([1, 2, 3, 4, 5, 6, 7], 10));
print(getSum([1, 2, 3, 4, 5, 6, 7], 14));
print(getSum([1, 2, 3, 4, 5, 6, 7], 19));

JS: get 5 items each time from whole array and get average

I have an array e.g.
var arr = [2,7,3,8,9,4,9,2,8,7,9,7,3,2,4,5,7,8,2,7,6,1,8];
I want that (I think for-loop is best for this to loop over this) a for-loop loops over the whole array and gets 5 items near eachother in the array and runs a function with those 5 items to calculate an average of them. This has of course to repeat till there are no parts of 5 available. The array above has 23 values. So when I should run a code on it, it can loop 4 times on it, cos one more time can't cos it has 3/5 values.
I thought about doing:
for (var i = 0; i < arr.length; i++) {
doThisFunction(i, i+1, i+2, i+3, i+4 );
}
but that shouldn't be efficient I believe... any help?
You're on to something, the easy way to do it is
var arr = [2,7,3,8,9,4,9,2,8,7,9,7,3,2,4,5,7,8,2,7,6,1,8];
var result = [];
for (var i=0; (i+5)<arr.length; i=i+5) {
var average = (arr[i] + arr[i+1] + arr[i+2] + arr[i+3] + arr[i+4]) / 5;
result.push(average);
}
document.body.innerHTML = '<pre>' + JSON.stringify(result, null, 4) + '</pre>';
The somewhat fancier way to do the same thing
var result = arr.map(function(x,i) {
return i%5===0 ? arr.slice(i, i+5).reduce(function(a,b) {return a+b}) / 5 : NaN;
}).filter(isFinite);
Use array.slice:
for (var i = 0; i < Math.floor(arr.length/5); i++) {
f(arr.slice(i*5, i*5+5))
}
The following uses reduce and a slice to sum up a range of values from the array.
function averageRange(arr, start, end) {
return (function(range) {
return range.reduce(
function(total, val) {
return total + val;
}, 0) / range.length;
}([].slice.apply(arr, [].slice.call(arguments, 1))))
}
function averageEveryN(arr, n) {
return arr.map(function(_, index, arr) {
return index % n === 0 ? averageRange(arr, index, index + count) : NaN;
}).filter(isFinite).slice(0, Math.floor(arr.length / n));
}
function println(text) {
document.getElementsByTagName('body')[0].innerHTML += text + '<br />';
}
var arr = [2, 7, 3, 8, 9, 4, 9, 2, 8, 7, 9, 7, 3, 2, 4, 5, 7, 8, 2, 7, 6, 1, 8];
var count = 5;
averageEveryN(arr, count).map(function(value, index) {
println((index + 1) + '.) ' + value.toFixed(4));
});
Output
1.) 5.8000
2.) 6.0000
3.) 5.0000
4.) 5.8000

How do I get the first two closest numbers to a target from an array using lodash?

I'm new to lodash and just getting the feel for functional programming with javascript. I'm using lodash 3.0.0-pre.
I have an array of numbers that are in order and a target number.
I need an array with the first and second closest numbers unless it was the last number then I just need it. How do I get that using lodash?
I found:
function getClosest(array, target) {
var tuples = _.map(array, function(val) {
return [val, Math.abs(val - target)];
});
return _.reduce(tuples, function(memo, val) {
return (memo[1] < val[1]) ? memo : val;
}, [-1, 999])[0];
}
I could change it to give me the closest two instead of one but I believe it will sequence through the entire array instead of just stopping once it has the two numbers it needs since it can stop when the difference in numbers starts to increase.
I would recommend not to use lodash looping functions here if you care about performance.
As soon as you array is ordered - it's good to use a modified version of Binary search to find index of the closest value:
function closestIndex(arr, target) {
var i = 0, j = arr.length - 1, k;
while (i <= j) {
k = Math.floor((i+j) / 2);
if (target === arr[k] || Math.abs(i - j) <= 1 ) {
return k;
} else if (target < arr[k]) {
j = k-1;
} else {
i = k+1;
}
}
return -1;
}
and then simply compare adjacent elements in the array:
if (_.isNumber(arr[closestIndex - 1]) && _.isNumber(arr[closestIndex + 1])) {
if (Math.abs(target - arr[closestIndex - 1]) < Math.abs(target - arr[closestIndex + 1])) {
result.push(arr[closestIndex - 1]);
} else {
result.push(arr[closestIndex + 1]);
}
}
See full example here.
Not really a lodash task because it's not an easy n -> n or n -> 1 transformation. Additionally lodash doesn't allow you to cancel a statement early.
Anyway here's a solution:
var array= [2, 3, 5, 25, 135, 250, 300];
function getClosest(array, target) {
var tuples = _.map(array, function(val) {
return [val, Math.abs(val - target)];
});
var prev= [0, Number.MAX_VALUE];
for(var i=0; i<tuples.length; i++){
if(tuples[i][1] < prev[1]){
prev= tuples[i];
}else{
if(i<2 || tuples[i][1] < tuples[i-2][1]){
return [prev[0], tuples[i][0]];
}else{
return [prev[0], tuples[i-2][0]];
}
}
}
return [prev[0]];
}
console.log(getClosest(array, 3));
Could be optimized by finding the nearest element with Newton's method and then looking at elements before and after that. (If you have like 50000 numbers in your array ^^)
Assuming that your array is sorted and arr[0] ≤ target:
var getClosests = function (arr, target) {
var last = _.last(arr)
if (target > last) {
return [last];
}
var index = _.findIndex(arr, function(a) { return target - a <= 0 });
neighbours = [arr[index-1], arr[index], arr[index+1]]
return _.chain(neighbours).sortBy(function(a) {
return Math.abs( a - target) })
.take(2).value()
}
getClosests([2, 3, 5, 25, 135, 250, 300], 100);
getClosests([2, 3, 5, 25, 135, 250, 300], 25);
getClosests([2, 3, 5, 25, 135, 250, 300], 400);
Find two Closet to given Goal in Array: (without Lodash)
function findCloset(givenList, goal) {
var first;
var second;
var finalCollection = [givenList[0], givenList[1]];
givenList.forEach((item, firtIndex) => {
first = item;
for (let i = firtIndex + 1; i < givenList.length; i++) {
second = givenList[i];
if (first + second < goal) {
if (first + second > finalCollection[0] + finalCollection[1]) {
finalCollection = [first, second];
}
}
}
});
return finalCollection;
}
var counts = [2, 42, 82, 329, 122, 40, 162, 202, 3, 5, 242, 282, 322, 35, 362];
var goal = 80;
console.log(findCloset(counts, goal));

Categories