I’m attempting it using an associative array. First of all, how many formats of an associative array are there?
I’ve seen:
declare -A letters
letters=( ["b"]="3" ["c"]="3" ["m"]="3" ["p"]="3" )
and also:
declare -A letters
letters["bcmp"]=3
letters["aeioulnrst"]=1
letters["dg"]=2
letters["fhvwy"]=4
letters["k"]=5
letters["jx"]=8
letters["qz"]=10
I’ve tried both, does it matter which?
That last one is what I’m trying now. When I enter a string at the terminal and run the script with debugging turned on and some echo statements, the output just scrolls endlessly.
set -x
i=0
counter="0"
declare -A letters
letters["bcmp"]=3
letters["aeioulnrst"]=1
letters["dg"]=2
letters["fhvwy"]=4
letters["k"]=5
letters["jx"]=8
letters["qz"]=10
while [[ $i < "${#letters[@]}" ]]
do
if [[ $@ == "${!letters[@]}" ]]
then
(( counter+=${letters[@]} ))
echo "counter is: $counter"
((i++))
echo "i is $i"
fi
done
It’s supposed to be checking the input against each element of the array and assigning the value to counter
Absolutely. Try both in a new terminal then do declare -p letters afterwards. That will show what you set. They will look different.
As the output shows, neither side of the check is a single letter so the if never matches and i is never incremented. Since i never changes, the loop never exits.
d and dg have different values. Why would you expect them to match? Though you’re not even comparing those two values! The output shows you’re checking if d is equal to \q\z \d\g \f\h\v\w\y \k \b\c\m\p \j\x \a\e\i\o\u\l\n\r\s\t. d would only match d and nothing longer than one character.
This does not expand to a single array key, it expands to all the keys the array has (a list of strings). There is a syntax to test for the existance of a key in an array and there is a syntax to access the contents of the array by a key. But there is no syntax to partially match an array key and retreive the full key. This is why the difference between letters=( ["b"]="3" ["c"]="3" ["m"]="3" ["p"]="3" ) and letters["bcmp"]=3 matters: the latter has one single 4-letter-key, the first has 4 separate keys.
What you need to do for multi-letter keys is loop over the list of keys, partially string match your letter to each key and then use the full key to access the array content. For single letter keys you only have to read the value from the array by using the letter as the key.
declare -A letters
letters["bcmp"]=3
letters["aeioulnrst"]=1
letters["dg"]=2
letters["fhvwy"]=4
letters["k"]=5
letters["jx"]=8
letters["qz"]=10
counter=0
read -p "Enter a letter: " input
for input in "${!letters[@]}"
do
(( counter+=${letters[@]} ))
echo "counter is: $counter"
done
I get 7 of these errors:
+ for input in "${!letters[@]}"
+ (( counter+=10 2 4 5 3 8 1 ))
./scrabble_test.sh: line 30: ((: counter+=10 2 4 5 3 8 1 : syntax error in expression (error token is "2 4 5 3 8 1 ")
+ echo 'counter is: 10'
counter is: 10
With each iteration incrementing counter by 10, so it goes from 10 to 70.
So it seems to be only assigning the value of ["qz"]=10, instead of looping through each value, checking input against it.
I tried replacing input with $@, but I received an error that said it was invalid.
I also tried using substring expansion, but I couldn’t get it to work. I read in the manual that substring expansion produces undefined results with Associative arrays.
But now, $input is one of the array keys. We no longer know what the user typed in. You want to use a different loop variable. Let’s use a
for a in "${!letters[@]}"
do
...
done
Inside the loop, we need to test if the user input is contained in $a.
We can use this check
if [[ "$a" == *"$input"* ]]
then
...
fi
Inside double brackets, the == operators does glob matching.
If the user input is the letter t, and $a is bcmp then [[ bcmp == *t* ]] is falsy, but when $a is aeioulnrst then [[ aeioulnrst == *t* ]] is truthy.
What do we do when we find a match? We add the score to the counter. But we need to add the score for the current $a, not use all the scores at the same time
(( counter += ${letters[$a]} ))
# ......................^^
# or, bash allows us to drop the "parameter expansion
# syntax" in an arithmetic expression
(( counter += letters[$a] ))
Putting that all together
read -p "Enter a letter: " input
for a in "${!letters[@]}"
do
if [[ "$a" == *"$input"* ]]
then
(( counter += letters[$a] ))
echo "counter is: $counter"
fi
done
That solution works perfectly for individual characters, but not for whole words, which is what the exercise asks for.
I tried to get it to work with whole words:
#!/bin/bash
declare -A letters
letters["bcmp"]=3
letters["aeioulnrst"]=1
letters["dg"]=2
letters["fhvwy"]=4
letters["k"]=5
letters["jx"]=8
letters["qz"]=10
counter=0
set -x
echo "Enter a letter: "
read -a input
for x in "${input[@]}"
do
if [[ "$x" == "${!letters[@]}" ]]
then
(( counter+=${letters[@]} ))
fi
done
echo "counter is: $counter"
Using the word “cabbage” as in the instructions, I get the following output with debugging turned on:
+ echo 'Enter a letter: '
Enter a letter:
+ read -a input
c a b b a g e
+ for x in "${input[@]}"
+ [[ c == *\q\z \d\g \f\h\v\w\y \k \b\c\m\p \j\x \a\e\i\o\u\l\n\r\s\t* ]]
+ for x in "${input[@]}"
+ [[ a == *\q\z \d\g \f\h\v\w\y \k \b\c\m\p \j\x \a\e\i\o\u\l\n\r\s\t* ]]
+ for x in "${input[@]}"
+ [[ b == *\q\z \d\g \f\h\v\w\y \k \b\c\m\p \j\x \a\e\i\o\u\l\n\r\s\t* ]]
+ for x in "${input[@]}"
+ [[ b == *\q\z \d\g \f\h\v\w\y \k \b\c\m\p \j\x \a\e\i\o\u\l\n\r\s\t* ]]
+ for x in "${input[@]}"
+ [[ a == *\q\z \d\g \f\h\v\w\y \k \b\c\m\p \j\x \a\e\i\o\u\l\n\r\s\t* ]]
+ for x in "${input[@]}"
+ [[ g == *\q\z \d\g \f\h\v\w\y \k \b\c\m\p \j\x \a\e\i\o\u\l\n\r\s\t* ]]
+ for x in "${input[@]}"
+ [[ e == *\q\z \d\g \f\h\v\w\y \k \b\c\m\p \j\x \a\e\i\o\u\l\n\r\s\t* ]]
+ echo 'counter is: 0'
counter is: 0
I’m trying to get it to cycle/browse through the array keys, but it’s not working.
And I have to enter the input with spaces between each character: c a b b a g e
I tried substring expansion, but that didn’t work either.
So is it pointless to try substring expansions in Associative arrays?
It seems like a perfect way to cycle through each character in the keys, but the GNU manual says it’s undefined to use it with Associative arrays.
Could you explain your approach/logic in English first? It’s not clear what/how you expect this to work. I’d start with a plan, in English, of what you’re trying to achieve, vs sharing code which definitely doesn’t work and expecting us to figure from non-working code which part isn’t working as intended … and also work out the intent from code that clearly doesn’t follow the intent.
I’m not sure what you don’t understand about my last post. It’s written the same as any other that I’ve posted here, and you’ve responded to. My intent is to solve the Scrabble exercise, as has been stated. As far as posting code that doesn’t work, that’s how it’s done on most of these forums. If my code is bad, it’s because I’m new to this, and so not very good.
Usually walking through an approach, in English, is the first step and best approach to breaking down what steps ought to happen. Once you have the steps (an “algorithm”), it’s a lot easier to see issues in the code, as you can tell which steps do or do not line up with the stated algorithm.
Without an approach/algorithm, it can be difficult to discuss logical bugs in code. So, I would encourage you to start with a set of steps using English and no coding constructs (eg “keys” or “associative arrays”).
Step 4 mentions looping through keys. Keys implies associative array. The only associative array mentioned prior to step 4 would be in step 1. Step 1 is the letter to score napping, not the user input.
To be specific, I want to loop through the individual characters of the keys, not as whole words: b c m p not 'bcmp'. I’m assuming (maybe incorrectly) this would work for both whole words or individual letters entered by the user.
I slightly changed the wording of the algorithm to reflect this.
1.) Create an associative array, whose keys contain the letters of the alphabet, and whose values are the values that each letter is assigned.
letter['bcmp']=3
2.) Accept user input through a Read command, which places the input into an array.
echo "Enter a letter/word: "
read -a input
3.) Loop through the user input (array) one char at a time, with a For loop.
for x in "${input[@]}"
4.) Use a nested For loop, to loop through the keys (alphabet) one char at a time, of the array.
for i in "${!letters[@]}"
5.) If the current letter of the user input is equal to the current letter of the array key.
if [[ "$x" == "$i" ]] …
6.) Increment the Counter with the value of the letter.
(( counter+=${letters[@]} ))
7.) When the loop is finished, display the value of Counter.