Here's what I'm trying to do. I'm making a game where opponents guess each other's word, and each guess has direct and indirect hits. See asterisks for where I'm having an issue.
var directSum = 0
var indirectSum = 0
var words = ["canon", "cnams"]
var secretWord = Array(words[0].characters)
var guessWord = Array(words[1].characters)
var secretWordInd = [Character]()
var guessWordInd = [Character]()
let dir1 = secretWord[0] == guessWord[0]
let dir2 = secretWord[1] == guessWord[1]
let dir3 = secretWord[2] == guessWord[2]
let dir4 = secretWord[3] == guessWord[3]
let dir5 = secretWord[4] == guessWord[4]
if dir1 && dir2 && dir3 && dir4 && dir5 {
print ("you won!")
}
if dir1 {
directSum += 1
} else {
secretWordInd.append(secretWord[0])
guessWordInd.append(guessWord[0])
}
if dir2 {
directSum += 1
} else {
secretWordInd.append(secretWord[1])
guessWordInd.append(guessWord[1])
}
if dir3 {
directSum += 1
} else {
secretWordInd.append(secretWord[2])
guessWordInd.append(guessWord[2])
}
if dir4 {
directSum += 1
} else {
secretWordInd.append(secretWord[3])
guessWordInd.append(guessWord[3])
}
if dir5 {
directSum += 1
} else {
secretWordInd.append(secretWord[4])
guessWordInd.append(guessWord[4])
**}
for var secretLetter in secretWordInd {
for var guessLetter in guessWordInd{
if secretLetter == guessLetter {
secretWordInd.remove(at:secretWordInd.indexOf(secretLetter))
guessWordInd.remove(at:guessWordInd.indexOf(guessLetter))
indirectSum += 1
}
}
}**
var score = [directSum, indirectSum]
what I need to do is count every time there's a character in Array:SecretWordInd that matches a character in Array:guessWordInd, remove only those two characters(one from each string), and indirectSum += 1 for every time this occurs. If there are 2 "a"s in one and 1 in the other, for instance, the function needs to remove 1 a from each. That means the output of the function for directSum, indirectSum in this case should be [1,2] since there is only one indirect hit from one "n" from the guess word. This is what is making the function complicated for me. The number of values in both arrays will not be constant. I can't figure out how to use a method to do this(.contains is for only strings I think). Your help is greatly appreciated! Thanks!
You should look into Array, Set and String data types in the documentation, they provide lots of functions to perform that kind of calculations and transformations.
For example:
var secretWord = "canon"
var guessWord = "cnams"
// identify shared letters using sets
let sharedLetters = Set(secretWord.characters).intersection(Set(guessWord.characters))
// count number of shared letters present in both words
let sharedCounts = sharedLetters.map{ c in (c, secretWord.characters, guessWord.characters) }
.map{ (c,s,g) in (c, s.filter{$0==c}, g.filter{$0==c}) }
.map{ (c,s,g) in (c, min(s.count,g.count)) }
// count same letters at same position and shared
// (if you're making a mastermind game, you should subtract directCount from indirectCount)
let directCount = zip(secretWord.characters,guessWord.characters).filter{$0==$1}.count
let indirectCount = sharedCounts.map{$0.1}.reduce(0,+)
// remove shared letters (for count) from both strings
sharedCounts.flatMap{ Array(repeating:$0, count:$1) }
.forEach{ secretWord.remove(at:secretWord.characters.index(of:$0)!) }
sharedCounts.flatMap{ Array(repeating:$0, count:$1) }
.forEach{ guessWord.remove(at:guessWord.characters.index(of:$0)!) }
sharedLetters // {"n", "a", "c"}
sharedCounts // ("n", 1), ("a",1), ("c", 1)]
directCount // 1
indirectCount // 3
secretWord // "on"
guessWord // "ms"
As others have said this isn't the place for homework ;)
But here are some pointers as to what might be useful:
indexOf : returns index of element you are looking for, nil if not in set
remove : remove from an array
Related
I have an array. I try to analyze each element (each element is a character) and compare if each element individually is equal to another character, something like this:
(The following code is incorrect, only illustrative)
let array = ["5","a","5","8","l","j"]
var finalString = ""
for i in array {
if array[i] = ["^0-9"] {
//Compares if the element in position i is equal to a number between 0 - 9
finalString + "1 " //or do something else
} else if array[i] = ["^a-z"] {
//Compares if the element in position i is equal to a character between a - z
finalString + "2 " //or do something else
}
}
print(finalString)
//Expected output
// 1 2 1 1 2 2
I hope the explanation of my problem is clear.
I would like to find out more about the possible input values.
let array = ["5","a","5","8","l","j"]
let finalString = array.reduce("") { result, character in
switch character {
case "0"..."9": return result.appending("1 ")
case "a"..."z": return result.appending("2 ")
default: assertionFailure("\(character) is unexpected input"); return result
}
}
print(finalString)
You have several issues. The following code cleans up those issues and makes use of CharacterSet to see if the array elements contains numbers or letters.
let array = ["5","a","5","8","l","j"]
var finalString = ""
for str in array {
if str.rangeOfCharacter(from: .decimalDigits) != nil {
//Compares if the string contains any digits
finalString += "1 " //or do something else
} else if str.rangeOfCharacter(from: .letters) != nil {
//Compares if the string contains any letters
finalString += "2 " //or do something else
} else {
finalString += "0 " //or do something else
}
}
print("Res: \(finalString)")
Output:
Res: 1 2 1 1 2 2
These particular checks are slightly different from the checks you mention in your code but for your example it gives the same result.
If you only want to accept 0-9 or a-z then you would need to update the specific character set being used.
I have 2 multi-dimensional arrays that I'm trying to compare data against. While the length of the nested arrays may vary on a case-by-case basis, for each instance the lengths will be the same and mirrored between both arrays AND the first array[0] of each will contain the exact same info. The only thing that will/may vary is the order the headers may appear in array[0]. It may be
h1, h2, Id
in the first and
Id, h1, h2
in the second array.
Full Example
1st array
[ [ Header1 , Header2 , ID ] , [ dataA , dataB , 000 ] ]
2nd array
[ [ Header1 , Header2 , ID ] , [ dataA , dataB , 111 ] ]
Question
How can I parse, loop, split, whatever (still new to this) so that I can compare each values and put them all into a single array?
Result Wanted
[ [ oldHeader1 , oldHeader2 , oldID , newHeader1 , newHeader2 , newID ] , [ dataA , dataB , 000 , dataA , dataB , 111 ] ]
and yes, dataA and dataB should be the same from both sources. That is what I'm comparing against, the IDs of each are the only things that should be different. If dataA doesn't match but dataB does, I'd like to continue but add a logging. But that's a later step.
I've thought about looping through 1st array and taking each value, then looping through the 2nd value to see if that value exists. That would be all well and good but I can't seem to figure out how to .push that value and keep everything in order. These values are also being pulled from a sheet but I read that it is faster to generate an array and use Google's server to do the work rather than look at each cell, vlookup or find, return a result, ++ and iterate.
Any advice here would be great, thanks!
Here is the code I'm using to generate the 2 arrays I'm going to compare as an FYI. Maybe there's something here that I can modify? I call this function twice, with a file that contains source info and a file that contains destination info.
//This will take a sheet and an array of headers and return the subset in a new array
function grabColumnByHeader(headers,sheet,whichID) {
var initialArray = new Array(); //Array to store sheet data
var headerIndex = new Array(); //Blank array to house header index(es)
var outputArray = []; //Will be returned
var data = sheet.getDataRange(); //all data
var dataNumRows = data.getNumRows(); //number of rows
var dataNumCol = data.getNumColumns(); //number of columns
initialArray = sheet.getRange(1, 1, dataNumRows, dataNumCol).getValues();
//Get the index(es) of header(s). This is assuming that headers are in row 1
for (i = 0;i<1;++i){
for (j = 0;j<dataNumCol;++j){
//loop through headers var for each header
for (k = 0;k<headers.length;++k){
if(initialArray[i][j].toString().toUpperCase() == headers[k][i].toString().toUpperCase()) {
headerIndex.push(j);
}
}
}
}
//Using the array's indexes from headerIndex, loop through and grab values
//If coming from SOURCE file, prepend 'SOURCE_'
for (i = 0;i<dataNumRows;++i) {
outputArray[i] = [];
for (j = 0;j<headerIndex.length;++j) {
if (i == 0 && whichID == 'TRUE') {
outputArray[i][j] = 'SOURCE_' + initialArray[i][headerIndex[j]];
} else {
outputArray[i][j] = initialArray[i][headerIndex[j]];
}
}
}
//Logger.log(outputArray);
return outputArray;
}
Update
Here is the code I've been able to google fu together and use my basic knowledge. I realize that it is performing unnecessary loops and this is still a work in progress:
var tempArray = [];
var idIndex;
//get index of ID so it can be skipped in comparison
for (i=0;i<1;++i) {
for (j=0;j<newData[i].length;++j) {
if (newData[i][j].toString().toUpperCase() == 'ID') {
idIndex = j;
}
}
}
//Logger.log(idIndex);
for (i=0;i<newData.length;++i) {
tempArray[i] = [];
//if on headers, automatically concatenate and add to tempArray
if (i==0) {
tempArray[i] = newData[i].concat(oldData[i]);
}
else {
//Logger.log('newData['+i+']');
for (j=0;j<newData[i].length;++j) {
//for newData[i][j], begin looking in oldData
//if we're not comparing indexes then
if (j != idIndex) {
//Logger.log('newData['+i+']['+j+']');
//begin looping through the oldData arrays
for (k=0;k<oldData.length;++k){
//Logger.log('newData['+i+']['+j+'] == oldData['+k+']['+j+']');
if (newData[i][j] == oldData[k][j]) {
//NEED TO MAKE SURE HERE THAT ++j IS THE SAME TOO
tempArray[i] = newData[i].concat(oldData[k]);//continue on with j
break;
}
}
}
//continue through for(j)
}
}
//continue through for(i)
}
output.getRange(1, 1, tempArray.length, tempArray[0].length).setValues(tempArray);
Found my own answer after enough trial and error. May not be the way a pro would do it, but works for me:
var tempArray = []; //will be used for output
var sameArray = []; //will look like this [sameIndex,sameCount]
var sameIndex = ''; //will get the index number of match to be used when pushing to tempArray
var sameCount = 0; //if count is == my keys-ID then push to tempArray
//concat the headers, as this will be a constant
tempArray[0] = newData[0].concat(oldData[0]);
for (i=1;i<newData.length;++i) {
//reset variables after each [i]
//these are used to verify if my newData array and oldData array are the same
sameArray = [];
sameIndex = '';
sameCount = 0;
for (j=0;j<newData[i].length;++j) {
//for newData[i][j], begin looking in oldData
//we're not comparing our IDs because we know they'll be different
//pulled those out earlier and set to idIndex
if (j != idIndex) {
//begin looping through the oldData arrays
for (k=1;k<oldData.length;++k){
for (l=0;l<oldData[k].length;++l) {
if (l != idIndex) {
if (newData[i][j] == oldData[k][l]){
sameArray[0] = k;
sameArray[1] = ++sameCount;
}
}
}
}
}
//since looped through all of oldData array, check to see
//if the amount of matches in a single row were keyCount-1
//keyCount is my variables and we subtract 1 because not comaring IDs
if (sameArray[1] == keyCount-1) {
tempArray.push(newData[i].concat(oldData[sameArray[0]]));
} else {
//Will be used to catch indexes that didn't match
}
}
}
output.getRange(1, 1, tempArray.length, tempArray[0].length).setValues(tempArray);
I am trying to generate 5 random substrings of six characters each from the alphabet. For example: ABCDEF, RSTUVW, UVWXYZ, etc. These substrings can be duplicates, so generating ABCDEF twice is a not a problem.
When I have these 5 substrings, I want to generate five arrays containing three characters. One of these characters should be the last letter of the substring and the other two letters should be two random unique letters from the entire alphabet.
Example:
Get five random substrings:
[ABCDEF], [RSTUVW], [CDEFGH], [LMNOPQ], [UVWXYZ]
For [ABCDEF] the system could generate [F, H, S] and for [RSTUVW] it could generate [K, Q, W]. As you can see, the three-character arrays always contain the last letter of its substring and two other randomised unique letters.
The above is part of a game for kids to practice the order of the alphabet. In order to generate possible answers I actually need small sets of characters to assign to buttons.
What do you think is the best way to approach this?
Thanks to #vacawama here's a possible solution.
1. Create the alphabet
let alphabet = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZ".characters)
2. Create the 6 "sequences"
let sequences = (0..<5).map { _ -> String in
let startIndex = Int(arc4random_uniform(UInt32(alphabet.count - 5)))
let endIndex = startIndex + 5
return String(alphabet[startIndex...endIndex])
}
["PQRSTU", "DEFGHI", "JKLMNO", "CDEFGH", "KLMNOP"]
3. Get the last char for each sequence
let lastChars = sequences.flatMap { $0.characters.last }
["U", "I", "O", "H", "P"]
4. Build 5 elms
Here's the code snippet
let elms = lastChars.map { char0 -> String in
var tempAlphabet = alphabet
tempAlphabet.removeAtIndex(tempAlphabet.indexOf(char0)!)
let index1 = Int(arc4random_uniform(UInt32(tempAlphabet.count)))
let char1 = tempAlphabet.removeAtIndex(index1)
let index2 = Int(arc4random_uniform(UInt32(tempAlphabet.count)))
let char2 = tempAlphabet[index2]
return String(Array(Set<Character>([char0, char1, char2])))
}
["DUN", "ZIQ", "ROP", "HSW", "PGS"]
Update
Here's another solution to fix the problem hightligted by #dfri in the comments below. The following code snipped could replace the previous bullet 4.
extension Array {
mutating func removeRandom() -> Element {
let index = Int(arc4random_uniform(UInt32(count)))
return removeAtIndex(index)
}
}
var availableChars = Array(Set(alphabet).subtract(lastChars))
let elms = lastChars.map { String([$0, availableChars.removeRandom(), availableChars.removeRandom()]) }
Random substrings:
func randomString(length: Int) -> String {
let charactersString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
let n = UInt32(charactersString.characters.count)
var out = ""
for _ in 0..<length {
let index = charactersString.startIndex.advancedBy(Int(arc4random_uniform(n)))
out.append(charactersString[index])
}
return out
}
Get 5 Strings:
func createSubstrings() -> [String] {
var array = [String]()
for _ in 0...5 {
array.append(self.randomString(6))
}
return array
}
Last array:
func createFinalStrings() -> [String] {
let substrings = createSubstrings()
var finalStrings = [String]()
let lastChars = substrings.flatMap { $0.characters.last }
for _ in 0...5 {
var string = ""
while string.characters.count < 3 {
let index = Int(arc4random_uniform(5))
let lastChar = lastChars[index]
if string.rangeOfString(String(lastChar)) == nil{
string = string + String(lastChar)
}
}
finalStrings.append(string)
}
return finalStrings
}
i have 2 different arrays called: criptedChar and alphabet. I need to check the first character in criptedChar (so "criptedchar[0]) and check a correspondence in alphabet.
For exemple:
criptedChar // ["d","e","c","b"]
alphabet // ["a","b","c" and so on]
I want to take d from criptedChar[0] and check if there's a "d" in all alphabet and then save the position of "d" in the second array.
I also need to increment the number inside the parenthesis of criptedChar. I'll take the number from the user.
Can you please help me? Thank you!
func decript() {
var criptedText = incriptedText.text! //get text from uiTextField
var criptedChar = Array<Character>(criptedText.characters) //from text to char & all in array :D
var alfabeto: Array<Character> = ["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 capacityCriptedCharArray = criptedChar.capacity
for (var i = 0; i < 26; i++) {
if criptedChar[0] == alfabeto[i] {
decriptedText.text = decriptedText.text! + "\(newLettersFromSecondViewController[i])"
}
}
for (var i = 0; i < 26; i++) {
if criptedChar[1] == alfabeto[i] {
decriptedText.text = decriptedText.text! + "\(newLettersFromSecondViewController[i])"
}
}
for (var i = 0; i < 26; i++) {
if criptedChar[2] == alfabeto[i] {
decriptedText.text = decriptedText.text! + "\(newLettersFromSecondViewController[i])"
}
}
}
This code works, but it's dumb and i have no control of the user input
If I understand your question correctly, you are looking for
something like this (explanations inline):
// Start with your crypted text, and an empty string for the result:
let cryptedText = "mifpyx"
var decryptedText = ""
// Two character arrays (of equal length):
let alphabet = Array("abcdefghijklmnopqrstuvwxyz".characters)
let newLetters = Array("ghijklmnopqrstuvwxyzabcdef".characters)
// For each character in the input string:
for c in cryptedText.characters {
// Check if `c` is contained in the `alphabet` array:
if let index = alphabet.indexOf(c) {
// Yes, it is, at position `index`!
// Append corresponding character from second array to result:
decryptedText.append(newLetters[index])
}
}
print(decryptedText) // solved
Alternatively, you can create a lookup-dictionary from the
two arrays:
var mapping = [ Character : Character ]()
zip(alphabet, newLetters).forEach {
mapping[$0] = $1
}
and then map each character from the input through that
dictionary:
let decryptedText = Array(cryptedText.characters
.map { mapping[$0] }
.flatMap { $0 }
)
(Here flatMap is used to filter-out the nils from characters which are not present in the input array.)
Here are examples of simple cypher logic that you can probably adapt to your application:
let readableText = "the quick brown fox jumped over the lazy dog"
// letters: letters in readable text that will be encoded
// cypher : corresponding encoded letters
//
// note: letters and cypher must have the same number of elements
let letters:[Character] = ["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"]
let cypher:[Character] = ["o","p","q","r","a","b","c","d","e","f","g","h","i","u","v","w","x","y","z","j","k","l","m","n","s","t"]
// build a mapping disctionary from readable to encoded
var encode:[Character:Character] = [:]
for (index, letter) in letters.enumerate() { encode[letter] = cypher[index] }
// encrypt the readble text gives: "jda xkeqg pyvmu bvn fkiwar vlay jda hots rvc"
let cryptedText = String(readableText.characters.map({ encode[$0] ?? $0 }))
// build a mapping disctionary from encoded to readable
var decode:[Character:Character] = [:]
for (index, letter) in cypher.enumerate() { decode[letter] = letters[index] }
// decrypted the encrypted text gives: "the quick brown fox jumped over the lazy dog"
let decryptedText = String(cryptedText.characters.map({ decode[$0] ?? $0 }))
this is an array of objects that i want to alphabetize:
var streets:Array = new Array();
streets.push({name:"Édouard-Montpetit"});
streets.push({name:"Alexandre de Sève"});
streets.push({name:"Van Horne"});
streets.push({name:"Atwater"});
now i'll sort my array:
streets.sortOn("name", Array.CASEINSENSITIVE);
//Sorted
Alexandre de Sève
Atwater
Van Horne
Édouard-Montpetit
the accent above the E in Édouard-Montpetit, and any other first letter with a non-english accent is sorted after Z.
any ideas how i can sort this correctly? i do not have access to the named data.
I know this is late, but for anyone going through this answer, you could pass a Collator
object to the Array.sort() method. A simple example from the documentation:
var words:Array = new Array("coté", "côte");
var sorter:Collator = new Collator("fr-FR", CollatorMode.SORTING);
words.sort(sorter.compare);
trace(words);// côte,coté
Hope this helps
I don't think you can do it with sortOn, as there's no way to tell flash to use a particular collaction for sorting text (at least, not that I'm aware of).
However, you could use sort and a custom sort function.
In this sort function, basically you want to strip all accents and do a case insensitive comparation. Replacing diacritics is easy and after that, you can safely use < and > for comparing the strings. A sort function is called by sort with two of the items to be sorted at a time. It should return a negative number if the first passed items sorts first , a possitive number if the second comes first and 0 if they sort equal.
function sortText(obj1:Object,obj2:Object):int {
var a:String = replaceDiacritics(obj1.name);
var b:String = replaceDiacritics(obj2.name);
if(a < b) {
return -1;
} else if(b < a) {
return 1;
} else {
return 0;
}
}
function replaceDiacritics(str:String):String {
str = str.toLowerCase();
str = str.replace(/á/g,"a");
str = str.replace(/é/g,"e");
str = str.replace(/í/g,"i");
str = str.replace(/ó/g,"o");
str = str.replace(/ú/g,"u");
str = str.replace(/à/g,"a");
str = str.replace(/è/g,"e");
str = str.replace(/ì/g,"i");
str = str.replace(/ò/g,"o");
str = str.replace(/ù/g,"u");
return str;
}
streets.sort(sortText);
A couple of notes about this. I know this method won't work for Spanish, as you have ñ, which is considered a letter on its own (not a regular n with a funny mark) and comes after n and before o. So, it's not possible to just replace accents and do a < / > compare. I think this is not a problem in French, but I could be wrong (not sure how Ç / ç is considered for sort purposes, for instance). Also, note that I haven't replaced all possible diacritics, so you'd want to add circumflexes (^) and umlauts (¨) to replaceDiacritics as necessary.
Edit
For a table based approach you could try something like the following. Each letter is assigned a number that reflects the sort order. As long as you can assume that any letter will have an absolute sort order (that is, context won't change how this works, which is not the case on some languages), it should give you good results.
Out of laziness, I built the table with a loop and just did what was neccesary to put "Ñ" between "n" and "o". I'm not considering any diacritics for sorting purposes, so they have the same value than their unaccented counterpart. But you could change this table as neccesary. Also, this table probably should be hardcoded for the required locale, but this code is just to give you an idea of how you could do this, not a full implementation (and it's probably not entirely correct from a purist perspective, but I think it could do the job). Also, in case we find a character that is not mapped, I'm falling back to its code point to determine how it sorts.
var sortTable:Object = buildSortTable();
function buildSortTable():Object {
var sortTable:Object = {};
var char:String;
var offset:int = 0;
for(var i:int = 1; i < 256; i++) {
char = String.fromCharCode(i);
if(char == "Ñ" || char == "ñ") {
offset--;
continue;
}
sortTable[char] = i + offset;
if(char == "N") {
sortTable["Ñ"] = sortTable["N"] + 1;
offset++;
}
if(char == "n") {
sortTable["ñ"] = sortTable["n"] + 1;
offset++;
}
}
sortTable["Á"] = sortTable["À"] = sortTable["Ä"] = sortTable["Â"] = sortTable["A"];
sortTable["É"] = sortTable["È"] = sortTable["Ë"] = sortTable["Ê"] = sortTable["E"];
sortTable["Í"] = sortTable["Ì"] = sortTable["Ï"] = sortTable["Î"] = sortTable["I"];
sortTable["Ó"] = sortTable["Ò"] = sortTable["Ö"] = sortTable["Ô"] = sortTable["O"];
sortTable["Ú"] = sortTable["Ì"] = sortTable["Ü"] = sortTable["Û"] = sortTable["U"];
sortTable["á"] = sortTable["à"] = sortTable["ä"] = sortTable["â"] = sortTable["a"];
sortTable["é"] = sortTable["è"] = sortTable["ë"] = sortTable["ê"] = sortTable["e"];
sortTable["í"] = sortTable["ì"] = sortTable["ï"] = sortTable["î"] = sortTable["i"];
sortTable["ó"] = sortTable["ò"] = sortTable["ö"] = sortTable["ô"] = sortTable["o"];
sortTable["ú"] = sortTable["ù"] = sortTable["ü"] = sortTable["û"] = sortTable["u"];
return sortTable;
}
function sortText(obj1:Object,obj2:Object):int {
var a:String = obj1.name.toLowerCase();
var b:String = obj2.name.toLowerCase();
var len_a:int = a.length;
var len_b:int = b.length;
var char_a:String;
var char_b:String;
var val_a:Number;
var val_b:Number;
for(var i = 0; i < len_a && i < len_b; i++) {
char_a = a.charAt(i);
char_b = b.charAt(i);
val_a = sortTable[char_a];
val_b = sortTable[char_b];
// this is just in case we have a letter that we haven't mapped...
// let's fall back to using its code point
if(isNaN(val_a)) {
val_a = char_a.charCodeAt(0);
}
if(isNaN(val_b)) {
val_b = char_b.charCodeAt(0);
}
if(val_a < val_b) {
return -1;
} else if(val_a > val_b) {
return 1;
}
}
// both strings are equal so far; so the sorter one (if any) must sort first
if(len_a < len_b) {
return -1;
} else if(len_a > len_b) {
return 1;
} else {
return 0;
}
}