sort function unexpected behaviour - javascript

i am trying to understand array sorting method , the problem i am currently facing is when i am declaring some variables inside compare function its not sorting the same as it is doing without those variables although those variables are not used anywhere
can anyone explain what is actually happening here
also i find out that sort functions behave different in firefox and chrome
page_link i am testing this in firefox dev edition
let list1 = ["a","b","c","d","e","f","g","h","i"]
list1.sort((a,b)=>{
let pat = ["d","a"]
return b - a
})
console.log(list1) // Array(9) [ "a", "b", "c", "d", "e", "f", "g", "h", "i" ]
let list2 = ["a","b","c","d","e","f","g","h","i"]
list2.sort((a,b)=>{
// let pat = ["d","a"]
return b - a
})
console.log(list2) // Array(9) [ "i", "h", "g", "f", "e", "d", "c", "b", "a" ]

If you do "a" - "b" it evaluates to NaN which is incorrect as that's not what you intended and also inconsistent (varies browser to browser).
Either don't pass a callback to sort in which case it does the following:
The sort() method sorts the elements of an array in place and returns the sorted array. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.
Or you can use String.prototype.localeCompare
let list = ["a", "b", "c", "d", "e", "f", "g", "h", "i"];
console.log([...list].sort());
console.log([...list].sort((a, b) => a.localeCompare(b)));
If you want to prepend some values to the sorted array you'll have to do it separately you can't do it using sort.
let list = ["a", "b", "c", "d", "e", "f", "g", "h", "i"];
let pat = ["x", "y"];
let sortedList = [...list].sort();
let patAppendedSortedList = [...pat, ...sortedList];
console.log(patAppendedSortedList);
And if you want to sort both the list and pat but you want to keep all pat elements before all list elements then consider the snippet below.
let list = ["a", "b", "c", "d", "e", "f", "g", "h", "i"];
let pat = ["z", "x", "y"];
let patSet = new Set(pat);
let sortedList = [...pat, ...list].sort((a, b) => {
// If a has higher priority put it before b
if (patSet.has(a) && !patSet.has(b)) {
return -1;
}
// If b has higher priority put it before a
if (patSet.has(b) && !patSet.has(a)) {
return 1;
}
// Otherwise both a and b have same priority
// Sort them according to their value
return a.localeCompare(b);
});
console.log(sortedList);

If you subtract a string from another string, it will attempt to convert the strings to numbers.
All of your strings will convert to NaN.
NaN-NaN is also NaN.
So it doesn't matter which two values from your array you are comparing, your comparison function will always return NaN.
(Note that a comparison function is supposed to return a number that is 0, greater than 0 or less than 0 and be consistent for any given pair of values. The one you are using is just broken for the data you are using).
The order the letters get sorted into therefore depends on the order in which they are compared to each other (because your comparison function is broken).
That order is determined by the sort algorithm that the JS engine uses. This is an implementation detail that the specification doesn't mandate.
One browser might use a quick sort while another might use a bubble sort. Hence you get different results.
Write a comparison function which isn't nonsense to get consistent results.
The default comparison function is find for lexical sorting under most circumstances.

Now i understood your goal. But you cant archive this with your way. You cant make some prepends of values in the JS sort function. You have to make it in two steps. 1.) sort 2.) prepend your pat values to the sorted array.
const list = ["a","b","c","d","e","f","g","h","i"];
const pat = ["d","a"];
const sorted = [...pat, ...list.sort()];
console.log(sorted)

Related

Javascript unusual behavior regarding Array containing 1...N using spread operator

In Javascript, I make this simple ascending array by spread operator.
[...Array(5).keys()]
// printed list
[0, 1, 2, 3, 4]
But I simply added 1 and this unexpected array generated.
(I just expected it would be an error)
[...Array(5).keys() + 1]
// printed list
["[", "o", "b", "j", "e", "c", "t", " ", "A", "r", "r", "a", "y", " ", "I", "t", "e", "r", "a", "t", "o", "r", "]", "1"]
I would like to know what makes this behavior.
First, the expression to the right of the ... is evaluated. That is, Array(5).keys() + 1 gets evaluated, and the result of that evaluation is then spread. Grouping the expression may make things a little clearer:
[...(Array(5).keys() + 1)]
So, above, we start with calculating the result of Array(5).keys() + 1. The + operator works between primitive operands. When you do Array(5).keys() you get an iterator back which is not classified as a primitive. So, JS will try and convert it to a primitive. When JS tries to convert the iterator into a primitive in this context it tries to:
Invoke the iterator's Symbol.toPrimitive method, since this doesn't exist, it moves onto trying the next step
Invoke the .valueOf() method of the iterator. This succeeds, but it returns the iterator back, which is not a primitive, so we go to the next step
Invoke the .toString() method. This results in a non-object type (ie: a string), and so this is successful.
When the iterator's .toString() method is called it gets evaluated to "[object Array Iterator]". As this then gets added with the number 1, we concatenate the string with the number (rather than add), giving:
[..."[object Array Iterator]1"]
Since strings are iterable, the spread syntax will use the string's iterator to loop through the code points in the string, resulting in every character of the string becoming its own element:
["[", "o", "b", "j", "e", "c", "t", " ", "A", "r", "r", "a", "y", " ", "I", "t", "e", "r", "a", "t", "o", "r", "]", "1"]
In order to add one to each item in your iterator, you can use Array.from() which can take an iterator, and applies a mapping function to each element:
const res = Array.from(Array(5).keys(), i => i+1);
console.log(res);
Since the argument to Array.from() doesn't have to be an iterator, you could also just use Array(5) instead and use the index as the value to add to:
const res = Array.from(Array(5), (_,i) => i+1);
console.log(res);
When you add + 1 it will handle the return from ...Array(5).keys() as a string.
Look at these additional examples:
console.log(Array(5).keys())
outputs:
[object Array Iterator]
console.log(Array(5).keys() + 1)
outputs:
[object Array Iterator]1
console.log(...Array(5).keys() + 1)
outputs:
[ o b j e c t A r r a y I t e r a t o r ] 1
Read more about the types conversion in JavaScript when using different operators
to simplify the answer, if you try to do Object() + 1, you'll get "[object Object]1", it can't be converted to a number, JS converts the Object() to a string, you can get the same by doing String(Object()),
in your example, Array(5).keys() is an object that inherits from Array Iterator, so when converted to a string you get "[object Array Iterator]" and of course by adding 1 you get "[object Array Iterator]1", and it's now a string, if you use spread operator for it it will be splitted to characters, which is an expected result for your example
instead, to add 1, you can do: [...Array(5).keys()].map(i=> i+1)
it is as you typed [...(Array(5).keys() + 1)] which mean the + has precedence over ...

How does one merge and compute array items within a nested array structure via a fixed condition/rule

I have an JavaScript heterogenous array of arrays which looks like :
let bigArray = [["A", "B", 221.67],["C", "B", 221.65],["B", "D", 183.33],["B", "A", 4900],["E", "B", 150],["A", "B", 150]]
Now i want to add the 3rd element(number) if the first and second elements matches with the first and second element of next array(in multidimensional array), then add 3rd element(number) of both array & also perform minus operation if reverse of those elements match is found.
Output will be:
let ans = [["B", "A", 4528.33],["C", "B", 221.65],["B", "D", 183.33],["E", "B", 150]]
sub array ["B","A", 4528.33] is formed by performing minus operation, i,e 4900-221.67-150
In bigArray there are array with repeated pair of elements like "A" & "B"(in single sub array). So for all matching subarrays perform sum operation & if reverse of that matching sub array is found then perform minus operation. i,e=> 4900-221.67-150
I tried many methods but cant able to achieve the desired output for all the cases. Any help will be appreciated, Thanks
You could group with sorted keys to maintain a standard key and check if the part keys have the same order, then add the value or subtract.
If a value is negative change the keys and take a positive value.
let data = [["A", "B", 221.67], ["C", "B", 221.65], ["B", "D", 183.33], ["B", "A", 4900], ["E", "B", 150], ["A", "B", 150]],
result = Object.values(data.reduce((r, [a, b, v]) => {
const key = [a, b].sort().join('|');
if (r[key]) {
r[key][2] += a === r[key][0] ? v : -v;
if (r[key][2] < 0) r[key] = [r[key][1], r[key][0], -r[key][2]];
} else {
r[key] = [a, b, v];
}
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

guaranteed indexed object to array in order

extension of this question
So I was playing around with a project and I found myself needing to create a string where I didn't know the exact order of the lettering right away, but I did know their position(and by extension, the length of this string).
Part of the reason for doing it this way is that I don't want to skip array elements at any point during the process(NO ["a", , "t"] during the process). Meaning that the order of insertion matters.
let string_length = 10;
let o = {
0: "a",
4: "q",
7: "t",
3: "p",
6: "h",
1: "z",
2: "t",
5: "a",
9: "b",
8: "z"
};
function obj_to_arr(o) {
// this returns ["a","z","t","p","q","a","h","t","z","b"]
}
All the answers I've found until now don't necessarily guarantee that given an index object, it will give the corresponding ordered object, and this is mainly because of the nature of object, being unordered.
Is there a way to implement this?
You could fix this in two possible ways (and probably more).
Array
Either you add objects containing both the key and the value to an array.
array.push({c: "a"}) or array.push({ key: c, value: "a"}), then add the next and so on.
Push puts an element at the end of an array.
Sorted object
Or you insert into the object using alphabetic keys, in which case you can use the example below.
You could sort the keys (properties) of the object, and use them to access each value and put them into another array. Here's a verbose example of how you could do that.
const someObject = {
c: "a",
4: "q",
7: "t",
b: "p",
6: "h",
1: "z",
2: "t",
5: "a",
a: "b",
8: "z"
};
function obj_to_arr(obj) {
let result = []
// Get keys from object as an array
let keys = Object.keys(obj)
console.log('keys: ', keys)
// Sort the array in alphabetical order
let sorted = keys.sort()
console.log('sorted: ', sorted)
// Add each value to the resulting array
sorted.forEach(function(key) {
console.log('adding ' + obj[key] + ' to array')
result.push(obj[key])
})
console.log('result: ', result)
return result
}
// Call the function
obj_to_arr(someObject)
Using Object.keys(), you can retrieve an array of keys, sort them numerically, then map that sorted array to one with the values.
The result will be an array of values in an order corresponding to the numeric values of the keys, even if they are not sequential.
For example
let o = {"0":"a","1":"z","2":"t","3":"p","4":"q","5":"a","6":"h","7":"t","8":"z","10":"b"}
let sortedValues = Object.keys(o)
.sort((a, b) => a - b) // numeric comparison
.map(k => o[k]) // map to values
console.info(sortedValues)
The short answer is "you can't" because of the Javascript spec.
Objects in Javascript are not guaranteed to have their keys ordered in any way due it being a primitive type that engines are allowed to optimize the hell out of, including reordering keys to speed up property access.
If ordering really matters, then you shouldn't be using an object primitive. Either an array of tuples (which has guaranteed ordering):
let fixedList = [
{ key: 0, value: 'a' },
{ key: 4, vlaue: 'q' },
...
];
or you can use a Map, which exists specifically as a way to have "an object, with the same ordering of key/value pairs as you declared".
Of the two, for data transport you probably want the former, and for running code you typically want the latter.

How can I add another element to an array?

I am trying to code a bot that can play hangman. I have used an array to store all the previous letter guesses. How can I add to this array, so that all previous guesses are shown in the console?
I have already tried using the push() function in JavaScript, and this seems to work fine for the first guess. But a second guess returns another array, which contains only that letter, instead of an array containing all guesses.
This also happens when I try to write the array using correctGuessed[correctGuessed.length] = guessVal; and I am not sure where I am going wrong.
var word = "ALPHABET";
var wordLength = word.length;
var choices = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K",
"L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z"];
var correctGuessed = correctGuessed || [];
var guess = Math.floor(Math.random() * choices.length);
var guessVal = choices[guess].valueOf();
//Start the game with a word length and a letter guess
message.reply("I am thinking of a " + wordLength + " letter word.");
message.channel.send("My guess is " + guessVal);
//If the guess was correct
if(word.includes(guessVal))
{
//Send a congratulatory message and push the letter onto
//the end of the array so it cannot be guessed again.
message.channel.send("Your guess was correct! Guess again!");
//newCorrect = correctGuessed.push(guessVal);
correctGuessed[correctGuessed.length] = guessVal;
console.log(correctGuessed);
}
The expected outcome of this code is that if I guess the letter 'A', the console will display the result ['A']. Then, after guessing, say, the letter 'B', the console will display the result ['A', 'B'].
The actual output of this code is ['A'] after guessing 'A', and after guessing 'B', the output is ['B'] rather than ['A', 'B']. I am getting each guess in its own array.
The problem is not that I don't understand how push() works; I understand that
var array = [1,2,3];
array.push(4);
will produce [1,2,3,4], but I am trying to achieve this, and it is not working.
The push() function of arrays appends an item at the end of an array. E.g.:
var array = [1,2,3];
array.push(4);
console.log(array)
// [1,2,3,4]
It is possible that you are declaring the array multiple times. If the code you are showing is inside a function, every time you call the function a completely new array will be created, when you do
var correctGuessed = correctGuessed || [];
To solve this, you could declare correctGuessed as a global variable, by declaring it outside of any function

Recursively Concatenate Array with JS/Coffeescript

I'm trying to generate something similar to the following:
Given HINTS = ["a", "s", "d", "f", "g", "h", "j", "k", "l", ";"] then allHints(21) should give an array similar to:
["a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "aa", "as", "ad", "af", "ag", "ah", "aj", "ak", "af"..."sa", "ss", "sd"...]
That is, all elements joined together one-by-one to make individual combinations. I'd like to write this using some sort of recursion, if possible, since it seems like a problem inclined to be solved that way.
My solution was to nest two for loops iterating through each combination by 9's (given that's how many hints there are) but it seems it gets stuck somewhere around the second time. I'm not too familiar with Coffeescripts for syntax but I've tried something similar to:
allHints = ->
for i in [0...anchors]
do (i) ->
if i > 9
(for y in [0...i % 9]
do (y) ->
#changing this to HINTS[Math.round(i / 10)] as it should be produces weird results
HINTS[y] + HINTS[i % 9 - 1])[0]
else
HINTS[i]
console.log allHints 19
But unfortunately, that provides an undefined for the last element. Can anyone help me figure out how to the correct for loops to generate an array? Here's a gist for reference.
A somewhat idiomatic CoffeeScript solution:
HINTS = ["a", "s", "d", "f", "g", "h", "j", "k", "l", ";"]
allHints = (n) ->
HINTS.concat(((b + a for a in HINTS) for b in HINTS)...)[0...n]
Then allHints(12) returns ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', 'aa', 'as'].
The first splat (...) converts the 2D array created by the nested comprehensions into a list of 1D array arguments for concat().
This solution is obviously not very efficient since it generates all permutations only to throw away any it doesn't need.
It sounds like you basically want a doubly nested loop where you iterate over each character each time you iterate over each character, in which case you might as well use the length of the HINTS array and not bother with counting stuff out yourself.
How's this?
var HINTS = ["a", "s", "d", "f", "g", "h", "j", "k", "l", ";"]
function allhints() {
var all = Array(); // Create a new array
all = all.concat(HINTS); // Add the individual hints
for (var i=0; i<HINTS.length; i++) // for each character in HINTS
for (var j=0; j<HINTS.length; j++) // with each character in HINTS
all.push(HINTS[i] + HINTS[j]); // Append the two combined hint characters
return all;
}
It's not recursive, but recursion doesn't really help here anyway. It also gives the desired outcome you described, but without any arbitrary limits to the length. If you do want to limit the result, you can define allhints like so:
function allhints(n) {
var all = Array(); // Create a new array
all = all.concat(HINTS); // Add the individual hints
for (var i=0; i<HINTS.length; i++) // for each character in HINTS
for (var j=0; j<HINTS.length && all.length < n; j++) // with each character in HINTS
all.push(HINTS[i] + HINTS[j]); // Append the two combined hint characters
return all.slice(0,n);
}
So then allhints(4) just returns ["a", "s", "d", "f"].
The problem that you really seem to want solved is "how can I generate all subsets of a set", in particular, "how do I generate all subsets given the array HINTS".
The easiest way to do this is to consider that each element in your set can be mapped to a binary string. Your array, HINTS, has 10 elements, so to find all subsets, we just need to count from 0000000000 to 1111111111 in binary, then select whichever elements as we go (for example, 0000000101 would be "k;").
The below code is almost complete; I've taken a look at it and there seems to be a bug (I'll see if I can debug it later).
HINTS = ["a", "s", "d", "f", "g", "h", "j", "k", "l", ";"]
var results = [];
var binCounter;
var hint;
for ( var i = 0; i < Math.pow(2, HINTS.length); i++) {
binCounter = i.toString(2); //convert the number to binary
hint = "";
//generate the hint
for ( var j = 0; j < binCounter.length; j++) {
//if the boolean digit was true, pick the corresponding element from our set
if (binCounter[j] == 1) {
hint += HINTS[j];
}
}
results.push(hint);
}
console.log(results);
The problem seems indeed to be generating the set of possible subsets of a given set. which sometimes is referred to as the powerset of a set.
Basically there are 3 possible solutions:
1) Binomial coefficients. See http://en.wikipedia.org/wiki/Binomial_coefficient
An implementation can be found in Pyton's itertools. Binomial coefficients give you subsets of a certain length. If you combine the subsets from length 0 upto the length of your original set you are done.
2) A recursive algorithm that "grows" subsets in generations. See kyoto's answer. See my more verbose version below.the wikipedia article mentions Pascal's triangle which is a hint for such an algorithm
3) An element is in a subset or not. This means there are 2^(length of the set) subsets.
Each subset can be in encoded as a binary number with length of the subsets digits.
this is done in NT3RP's answer. You could also use an array of booleans for this instead of a string. I post my C# version below.
My recursive version of Powerset in coffeescript based on an implementation in Miranda.
(I was wondering whether I could code it in Coffeescript as compact as in Miranda and then I found this question)
powerset in Miranda
powerset [] = [[]]
powerset (x:xs) = [[x] ++ y | y <- ys] ++ ys
where ys = powerset xs
powerset in coffeescript:
powerset = (zs) ->
if zs.length is 0
[[]]
else
[x,xs...]=zs
ys=powerset xs
result=([x].concat y for y in ys).concat ys
# number of elements in powerset is 2^length of the powerset
res=powerset [1,2,3,4,5,6,7,8,9,10]
console.log res
console.log "length:" +res.length
My interests in all this:
I wrote a C# implementation of the binary number approach for generating
subsets a while ago. For fun I also wanted to write a version that "grows" the subsets.
I knew Miranda a very succinct functional programming language. I wondered whether coffeescript allowed the same level as succinctnes.I was not able to achieve this in Scala, F# or Clojure. I was not able to do it in coffeescript but "kyoto" showed how it is done.
Below the C# version as IEnumerable. It generates tuples of elements that are in the subset and all other elements.
...
//imports and house keeping removed
private static IEnumerable<Tuple<IList<T>,IList<T>>> SubsetImpl<T>(this IList<T> argList){
int elementCount=argList.Count;
int bits=(1<<elementCount);//2 to the power of elementCount
List<Tuple<IList<T>,IList<T>>> subsets=new List<Tuple<IList<T>, IList<T>>>(bits);
for(int i=0;i<bits;i++){
int[] mask={i};
BitArray flags=new BitArray(mask);
IList<T> incomb=new List<T>();
IList<T> outcomb=new List<T>();
for(int j=0;j<argList.Count;j++){
if( flags[j]){
incomb.Add(argList[j]);
}else{
outcomb.Add(argList[j]);
}
}
Tuple<IList<T>,IList<T>> t=new Tuple<IList<T>,IList<T>>(incomb,outcomb);
subsets.Add(t);
}
return subsets;
}
...
A simple solution:
allHints = (n) ->
ln = hints.length
# return sliced array if n is less than it's length
return hints.slice(0, n) if n <= ln
# clone the array, find number of possible combinations
clone = hints.slice()
limit = Math.min(n, ln*ln)
# add the combinations up to the requested length or limit
for i in [ln...limit]
clone[i] = hints[Math.floor(i / ln) - 1] + hints[i % ln]
return clone
This could be optimized to not lookup the base character everytime with a nested loop, but is left as is for clarity.

Categories

Resources