Recursively Concatenate Array with JS/Coffeescript - javascript

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.

Related

sort function unexpected behaviour

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)

Why can I not push a number into my array of strings even though I have converted the number back to a string?

Hello there I am trying to do a code wars kata: https://www.codewars.com/kata/54a91a4883a7de5d7800009c/train/javascript
I know what I have done is very long-winded but I wanted to do it step by step before I refactored it.
I have split the string input from the user
Filtered through the array just to get the number and converted it to a NUMBER
Incremented that number by 1
Changed number back to string
Now I want to add my string number to the original array but it doesn't work and I don't understand why :S
I know this is not the final answer to the kata but I am just trying things and wondered why this did not work...
function isNumeric(num){
return !isNaN(num)
}
function incrementString (string) {
const splitString = string.split("");
let numbers = Number(splitString.filter(el => isNumeric(el)).join("")); //[
'f', 'o', 'o', 'b',
'a', 'r', '0', '0',
'4', '2'
]
let incrementNumber = numbers +=1; // 43
let revertNumberToString = incrementNumber.toString(); // "43"
let test = splitString.push(revertNumberToString); // why can I not push the number 43 onto my original array?
console.log(test); // 11? why?
}
incrementString("foobar0042")
It does seem to be working correctly. If you check splitString again after you push to it then it will have all 11 items. That is where the number 11 is coming from. When you save a push to a variable it doesn't make a new array but rather it saves the length of the new array.
console.log(splitString)
// ["f", "o", "o", "b", "a", "r", "0", "0", "4", "2"]
let test = splitString.push(revertNumberToString);
console.log(splitString)
// ["f", "o", "o", "b", "a", "r", "0", "0", "4", "2", 43]
console.log(test); // 11? why?
Javascript push method adds the element to the array and returns the length, that's why you get 11 instead of the array itself. Reference
You could take a different approach by splitting the value into string and number part and take the length of the number part for later padding the value with leading zeroes.
function incrementString(value) {
const
string = (value.match(/\D+/) || [''])[0],
number = (value.match(/\d+/) || ['0'])[0];
return string + (+number + 1).toString().padStart(number.length, 0);
}
function assertEquals(a, b) {
console.log(a === b, a, b);
}
assertEquals(incrementString("foobar000"), "foobar001");
assertEquals(incrementString("foo"), "foo1");
assertEquals(incrementString("foobar001"), "foobar002");
assertEquals(incrementString("foobar99"), "foobar100");
assertEquals(incrementString("foobar099"), "foobar100");
assertEquals(incrementString(""), "1");

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

Join Array from startIndex to endIndex

I wanted to ask if there is some kind of utility function which offers array joining while providing an index. Maybe Prototype of jQuery provides this, if not, I will write it on my own :)
What I expect is something like
var array= ["a", "b", "c", "d"];
function Array.prototype.join(seperator [, startIndex, endIndex]){
// code
}
so that array.join("-", 1, 2) would return "b-c"
Is there this kind of utility function in an pretty common Javascript Library?
Regards
globalworming
It works native
["a", "b", "c", "d"].slice(1,3).join("-") //b-c
If you want it to behave like your definition you could use it that way:
Array.prototype.myJoin = function(seperator,start,end){
if(!start) start = 0;
if(!end) end = this.length - 1;
end++;
return this.slice(start,end).join(seperator);
};
var arr = ["a", "b", "c", "d"];
arr.myJoin("-",2,3) //c-d
arr.myJoin("-") //a-b-c-d
arr.myJoin("-",1) //b-c-d
Just slice the array you want out, then join it manually.
var array= ["a", "b", "c", "d"];
var joinedArray = array.slice(1, 3).join("-");
Note: slice() doesn't include the last index specified, so (1, 3) is equivalent to (1, 2).

Categories

Resources