The recursive version of LCS code looks something like this(m, n are lengths of the strings X and Y respectively)
int lcs( char[] X, char[] Y, int m, int n ) {
if (m == 0 || n == 0)
return 0;
if (X[m-1] == Y[n-1])
return 1 + lcs(X, Y, m-1, n-1);
else
return max(lcs(X, Y, m, n-1), lcs(X, Y, m-1, n));
}
I notice that this algo deletes characters of the strings from the end and creates various substrings of the original two strings and then tries to look for a match.
What I don't understand is, since it considers only certain substrings and not all possible subsequences, how is this algorithm guaranteed to provide the correct result? Is there a logical/mathematical/intuitive proof behind this?
one of the step in Dynamic programming is guessing (check out MIT lecture on YouTube)
You have two pointers to each character in both the strings respectively.
The guess here is to either include the character or not.
if both the character are same then you include the character and move the pointer ahead.
chose a character either from String X or Y. hence the max(lcs(X, Y, m, n-1), lcs(X, Y, m-1, n))
it's recurrence is given as:
Since you're guessing all the possible outcomes the above recurrence gives correct answer.
for more reference: https://courses.csail.mit.edu/6.006/fall10/handouts/recitation11-19.pdf
It calculates either lcs( X, Y, m-1, n-1) when the last chars are the same.
OR
It calculates both lcs( X, Y, m-1,n ) AND lcs( X, Y, m, n-1) when the characters are different.
So it covers all possible solutions.
If the lengths are 0. Then lcs is 0.
If the last (or first) items are the same, then the lcs is 1 + lcs(X,Y,m-1,n-1)
This clause reduces the problem by adding the common element to the lcs, and finding the smaller lcs of the 2 shorter strings.
Final the longest subsequence is either
lcs(X, Y, m-1, n)
or
lcs(X, Y, m, n-1)
So when it is asked to calculate lcs( "abce", "bghci", 4, 5);
if( m(4) == 0 || n(5) == 0 ) // evaluates to false
if( X[3]('e') == Y[4] ) // evaluates to false
return max( lcs( "abc", "bghci", 3, 5 ), // These two recursive calls
lcs( "abce", "bghc", 4, 4 ) ); // cover the whole problem space
e.g.
(A) lcs( "abce", "bghci" ) => (B) lcs( "abc", "bghci" )
(C) lcs( "abce", "bghc" )
(B) lcs( "abc", "bghci" ) => (D) lcs( "ab", "bghci" )
(E) lcs( "abc", "bghc" )
(C) lcs( "abce", "bghc" ) => (F) lcs( "abc", "bghc" )
(G) lcs( "abce", "bgh" )
(D) lcs( "ab" , "bghci") => (H) lcs( "a" , "bghci")
(I) lcs( "ab" , "bghc" )
(E) lcs( "abc", "bghc" ) => 1 + (J) lcs( "ab" , "bgh")
(F) lcs( "abc", "bghc" ) => 1 + (K) lcs( "ab" , "bgh")
(G) lcs( "abce", "bgh" ) => (L) lcs( "abc", "bgh" )
(M) lcs( "abce", "bg" )
(H) lcs( "a", "bghci") => lcs( "", "bghci" ) (0)
(N) lcs( "a", "bghc" )
(I) lcs( "ab", "bghc") => (O) lcs( "a", "bghc" )
(P) lcs( "ab", "bgh" )
(J) lcs( "ab", "bgh" ) => (Q) lcs( "a", "bgh" )
(R) lcs( "ab", "bg" )
(K) lcs( "ab", "bgh" ) => (S) lcs( "a", "bgh" )
(T) lcs( "ab", "bg" )
(L) lcs( "abc", "bgh" ) => (U) lcs( "ab", "bg" )
(V) lcs( "abc", "bg" )
(M) lcs( "abce", "bg" ) => (W) lcs( "abc", "bg" )
(X) lcs( "abce","b" )
(N) lcs( "a", "bghc") => lcs( "", "bghc" ) (0)
(Y) lcs( "a", "bgh" )
(O) lcs( "a", "bghc" ) => lcs( "", "bghc" ) (0)
lcs( "a" "bgh" )
(P) lcs( "ab", "bgh" ) => (Z) lcs( "a", "bgh" )
(AA) lcs( "ab", "bg" )
(Q) lcs( "a", "bgh" ) => lcs( "", "bgh") (0)
(AB) lcs( "a", "bg")
(R) lcs( "ab", "bg" ) => (AC) lcs( "a", "bg")
(AD) lcs( "ab", "b" )
(S) lcs( "a", "bgh" ) => lcs( "", "bgh") (0)
(AE) lcs( "a", "bg" )
(T) lcs( "ab", "bg" ) => (AF) lcs( "a", "bg" )
(AG) lcs( "ab", "b" )
(U) lcs( "ab", "bg" ) => (AH) lcs( "a", "bg" )
(AI) lcs( "ab", "b" )
(V) lcs( "abc","bg" ) => (AJ) lcs( "ab", "bg" )
=> (AK) lcs( "abc", "b" )
(W) lcs( "abc","bg" ) => (AL) lcs( "ab", "bg" )
(AM) lcs( "abc", "b" )
(X) lcs( "abce", "b") => (AN) lcs( "abc", "b" )
lcs( "abce", "" ) (0)
(Y) lcs( "abce", "b") => (AO) lcs( "abc", "b" )
lcs( "abce", "" ) (0)
(Z) lcs( "a", "bgh") => lcs( "", "bgh" ) (0)
(AP) lcs( "a", "bg" )
(AA)lcs( "ab", "bg") => (AQ) lcs( "a", "bg" )
(AR) lcs( "ab", "b" )
(AB)lcs( "a", "bg") => lcs( "", "bg" ) (0)
(AS) lcs( "a", "b" )
(AC)lcs( "a", "bg") => lcs( "", "bg") (0)
(AT) lcs( "a", "b")
(AD)lcs( "ab", "b") => 1 + lcs( "a", "" ) (1)
(AE)lcs( "a", "bg") => lcs( "", "bg")
(AU) lcs( "a", "b" )
(AF)lcs( "a", "bg") => lcs( "", "bg") (0)
(AV) lcs( "a", "b")
(AG)lcs( "ab", "b" ) => 1 + lcs( "a", "" ) (1)
(AH)lcs( "a", "bg") => lcs( "", "bg") (0)
(AW) lcs( "a", "b" )
(AI)lcs( "ab", "b") => 1 + lcs( "a", "" ) (1)
(AJ)lcs( "ab", "bg") => (AX) lcs( "a", "bg")
(AK)lcs( "abc", "b") => (AY) lcs( "ab", "b")
lcs( "abc", "b") (0)
(AL)lcs( "ab", "bg") => (AZ) lcs( "a", "bg")
(BA) lcs( "ab", "b")
(AM)lcs( "abc", "b") => (BB) lcs( "ab", "b")
lcs( "abc", "" ) (0)
(AN)lcs( "abc", "b") => (BC) lcs( "ab", "b")
lcs( "abc", "" ) (0)
(AO)lcs( "abc", "b") => (BD) lcs( "ab", "b")
lcs( "abc", "" ) (0)
(AP)lcs( "a", "bg") => lcs( "", "bg") (0)
(BE) lcs( "a", "b")
(AQ)lcs( "a", "bg") => lcs( "", "bg") (0)
(BF) lcs( "a", "b")
(AR)lcs( "ab", "b") => 1 + lcs( "a", "" ) (1)
(AS)lcs( "a", "b") => lcs( "", "b") (0)
lcs( "a", "" ) (0)
(AT)lcs( "a", "b" ) as (AS)
(AU)lcs( "a", "b" ) as (AS)
(AV)lcs( "a", "b") as (AS)
(AW)lcs( "a", "b") as (AS)
(AX)lcs( "a", "bg") => lcs( "", "bg") (0)
(BG) lcs( "a", "b")
(AY)lcs( "ab", "b") => 1 + lcs( "a", "" ) (1)
(AZ)lcs( "a", "bg") => lcs( "", "bg") (0)
(BH) lcs( "a", "b")
(BA)lcs( "ab", "b") => 1 + lcs( "a", "" ) (1)
(BB)lcs( "ab", "b") => 1 + lcs( "a", "" ) (1)
(BC)lcs( "ab", "b") => 1 + lcs( "a", "" ) (1)
(BD)lcs( "ab", "b") => 1 + lcs( "a", "" ) (1)
(BE)lcs( "a", "b") as (AS)
(BF)lcs( "a", "b") as (AS)
(BG)lcs( "a", "b") as (AS)
So the lcs of 2 comes from the following call stack....
lcs( "abcde", "bghci") // (A)
lcs( "abcd", "bghci" ) // (B)
lcs( "abc", "bghc" ) // (E)
1 + lcs( "ab", "bgh" ) // (J)
lcs( "ab", "bg" ) // (R)
lcs( "ab", "b" ) // (AD)
1 + lcs( "a", "" )
Which gives an answer of 2. As you can see, it tests alot more combinations.
What I don't understand is, since it considers only certain substrings
and not all possible subsequences,
The trick in the algorithm is to notice that if the first (or last character is the same), then the longest common subsequence is 1 + the lcs of the 2 shorter strings. Something like proof by induction, or proof by contradiction can help you prove that the division of work is necessary and sufficient to cover all the possible alternatives.
As can be seen in my build out, it considers many alternatives multiple times in calculating the lcs, so it is not a very efficient algorithm.
Related
lion1 =['L',1,[(0,0),(1,0)]]
lion2 =['L',2,[(0,0),(0,1),(1,1)]]
###########
girafe1 =['G',1,[(0,0),(1,0),(1,1)]]
girafe2 =['G',2,[(0,0),(1,0)]]
zebre1 =['Z',1,[(0,0),(1,0)]]
zebre2 =['Z',2,[(0,0),(0,1)]]
elephant1 =['E',1,[(0,0),(0,1)]]
elephant2 =['E',2,[(0,0),(1,0),(0,1)]]
Hippopotam1 =['H',1,[(0,0),(1,0)]]
Hippopotam2 =['H',2,[(0,0),(1,0),(2,0)]]
grille1=[["o","o","o","o","o","o","o","o","o"],["o","o",".",".",".",".",".","o","o"],["o",".",".",".",".",".",".",".","o"],["o",".",".",".",".",".",".",".","o"],["o","o",".",".",".",".",".","o","o"],["o","o","o","o","o","o","o","o","o"]]
Listes_de_tous_animaux=[lion1,lion2,girafe1,girafe2,zebre1,zebre2,elephant1,elephant2,Hippopotam1,Hippopotam2]
def place_libre(x,y,grille,animal):
if grille[x][y]==".":#libre
i=0
temp=[(0,0),(0,0),(0,0)]
for n in range(len(animal[2])):
temp[i]=(x,y)+animal[2][n]
i+=1
if grille[temp[i][0][temp[i][1]]!=".": #depuis position voir si la forme de l'animal correspond a des places vides
return False
print(temp)
return True
place_libre(3,2,grille1,zebre1)
The error is:SyntaxError: invalid syntax
i've tried to remove the return but without success/i'm trying to make a function that check if the position is free (represented by"."in the list)and then check from the position if the animal form doesn't get in a full place then return true if it's free.
you were not indexing grille correctly.
Also, you can use formatter like Black to make your code more readable.
lion1 = ["L", 1, [(0, 0), (1, 0)]]
lion2 = ["L", 2, [(0, 0), (0, 1), (1, 1)]]
###########
girafe1 = ["G", 1, [(0, 0), (1, 0), (1, 1)]]
girafe2 = ["G", 2, [(0, 0), (1, 0)]]
zebre1 = ["Z", 1, [(0, 0), (1, 0)]]
zebre2 = ["Z", 2, [(0, 0), (0, 1)]]
elephant1 = ["E", 1, [(0, 0), (0, 1)]]
elephant2 = ["E", 2, [(0, 0), (1, 0), (0, 1)]]
Hippopotam1 = ["H", 1, [(0, 0), (1, 0)]]
Hippopotam2 = ["H", 2, [(0, 0), (1, 0), (2, 0)]]
grille1 = [
["o", "o", "o", "o", "o", "o", "o", "o", "o"],
["o", "o", ".", ".", ".", ".", ".", "o", "o"],
["o", ".", ".", ".", ".", ".", ".", ".", "o"],
["o", ".", ".", ".", ".", ".", ".", ".", "o"],
["o", "o", ".", ".", ".", ".", ".", "o", "o"],
["o", "o", "o", "o", "o", "o", "o", "o", "o"],
]
Listes_de_tous_animaux = [
lion1,
lion2,
girafe1,
girafe2,
zebre1,
zebre2,
elephant1,
elephant2,
Hippopotam1,
Hippopotam2,
]
def place_libre(x, y, grille, animal):
if grille[x][y] == ".": # libre
i = 0
temp = [(0, 0), (0, 0), (0, 0)]
for n in range(len(animal[2])):
temp[i] = (x, y) + animal[2][n]
i += 1
if grille[temp[i][0]][temp[i][1]] != ".":
return False # depuis position voir si la forme de l'animal correspond a des #places vides
print(temp)
return True
print(place_libre(3, 2, grille1, zebre1))
Having a list of maps as this
def listOfMaps =
[
["car": "A", "color": "A", "motor": "A", "anything": "meh"],
["car": "A", "color": "A", "motor": "A", "anything": "doesn't matter"],
["car": "A", "color": "A", "motor": "B", "anything": "Anything"],
["car": "A", "color": "B", "motor": "A", "anything": "Anything"]
]
How am I supposed to find duplicates by car, color and motor? If there are more than 1 map with the same car, color and motor value it should return true. In this case it should return true since first and second map have the same car, color and motor value, value could be anything as long as they are the same.
Groovy has a handy Collection.unique(boolean,closure) method that allows you to create a new list by removing the duplicates from an input list based on the comparator defined in a closure. In your case, you could define a closure that firstly compares car field, then color, and lastly - motor. Any element that duplicates values for all these fields will be filtered out.
Consider the following example:
def listOfMaps = [
["car": "A", "color": "A", "motor": "A", "anything": "meh"],
["car": "A", "color": "A", "motor": "A", "anything": "doesn't matter"],
["car": "A", "color": "A", "motor": "B", "anything": "Anything"],
["car": "A", "color": "B", "motor": "A", "anything": "Anything"]
]
// false parameter below means that the input list is not modified
def filtered = listOfMaps.unique(false) { a, b ->
a.car <=> b.car ?:
a.color <=> b.color ?:
a.motor <=> b.motor
}
println filtered
boolean hasDuplicates = listOfMaps.size() > filtered.size()
assert hasDuplicates
Output:
[[car:A, color:A, motor:A, anything:meh], [car:A, color:A, motor:B, anything:Anything], [car:A, color:B, motor:A, anything:Anything]]
Not sure I have understood question correctly, but I have come up with the next code snippet:
def listOfMaps = [
["car": "A", "color": "A", "motor": "A", "anything": "meh"],
["car": "A", "color": "A", "motor": "A", "anything": "doesn't matter"],
["car": "A", "color": "A", "motor": "B", "anything": "Anything"],
["car": "A", "color": "B", "motor": "A", "anything": "Anything"]
]
static def findDuplicatesByKeys(List<Map<String, String>> maps, List<String> keys) {
Map<String, List<Map<String, String>>> aggregationKeyToMaps = [:].withDefault { key -> []}
maps.each { singleMap ->
def aggregationKey = keys.collect { key -> singleMap[key] }.join('-')
aggregationKeyToMaps.get(aggregationKey).add(singleMap)
}
aggregationKeyToMaps
}
findDuplicatesByKeys(listOfMaps, ['car', 'color', 'motor'])
Basically it iterates over list of maps and groups them by values of the provided keys. The result will be a map of list of maps. Something similar to:
def aggregatedMaps = [
"A-A-A": [
["car": "A", "color": "A", "motor": "A", "anything": "meh"],
["car": "A", "color": "A", "motor": "A", "anything": "doesn't matter"]
],
"A-A-B": [
["car": "A", "color": "A", "motor": "B", "anything": "Anything"]
],
"A-B-A": [
["car": "A", "color": "B", "motor": "A", "anything": "Anything"]
]
]
You can grab .values() for example and apply needed removals (you haven't specified which duplicate should be removed) and finally flatten the list. Hope that's helpful.
You can group the maps by the appropriate fields and then check if there exists at least one group with more then one element:
boolean result = listOfMaps
.groupBy { [car: it.car, color: it.color, motor: it.motor] }
.any { it.value.size() > 1 }
Let's say I have the following structure:
Map<String,Map<String,Integer>> nestedMap = [
"x": [
"a": 2,
"b": 3,
"c": 4
],
"y": [
"a": 20,
"b": 30,
"c": 40
],
"z": [
"a": 200,
"b": 300,
"c": 400
]
]
I want to flatten this structure and get:
[
"x-a" : 2,
"x-b" : 3,
"x-c" : 4,
"y-a" : 20,
"y-b" : 30,
"y-c" : 40,
"z-a" : 200,
"z-b" : 300,
"z-c" : 400
]
How can I do this with collect/collectEnteries etc?
Alternatively, as you can see the values are quite similar. so the way I have constructed this nested map structure, was by running a for loop iterating through [x,y,z] and setting the entries of my map to be equal to a function that iterates through [a,b,c] and returns a map. Is there a way of skipping this nested structure and functionally append the elements in a big map?
You can create a recursive function that will loop over the map and flatten it.
Map<String,Map<String,Integer>> nestedMap = [
"x": ["a": 2, "b": 3, "z": 4],
"y": ["a": 20, "b": 30, "c": 40],
"z": ["a": 200, "b": 300, "c": 400]
]
String concatenate(String k, String v) {
"${k}-${v}"
}
def flattenMap(Map map) {
map.collectEntries { k, v ->
v instanceof Map ?
flattenMap(v).collectEntries { k1, v1 ->
key = concatenate(k,k1)
[ (key): v1 ]
} :
[ (k): v ]
}
}
print flattenMap(nestedMap)
Regarding the alternate question (and echoing a comment by #damahapatro), consider the following.
Given two lists:
def list1 = ['x', 'y', 'z']
def list2 = ['a', 'b', 'c']
and a function f that gives the appropriate values, then we can use inject (which will accumulate a map while iterating over the combos (e.g. [x,a]):
def m = [list1, list2].combinations().inject([:]) { map, combo ->
assert combo.size() == 2
def a = combo[0]
def b = combo[1]
map["${a}-${b}" as String] = f(a,b)
map
}
assert m == [
"x-a" : 2, "x-b" : 3, "x-c" : 4,
"y-a" : 20, "y-b" : 30, "y-c" : 40,
"z-a" : 200, "z-b" : 300, "z-c" : 400
]
Another way of building the map from scratch would be:
def result = ['a'..'c', 'x'..'z']
.combinations()
.collect { a, b -> b + '-' + a }
.indexed()
.collectEntries { idx, name ->
[name, ((idx % 3) + 2) * (int)Math.pow(10, idx.intdiv(3))]
}
I modified the define syntax of #Paul Walkington and tested it:
String concatenate(String k, String v) {
"${k}-${v}"
}
def flattenMap(Map map) {
map.collectEntries { k, v ->
v instanceof Map ?
flattenMap(v).collectEntries { k1, v1 ->
def key = concatenate(k,k1)
[ (key): v1 ]
} :
[ (k): v ]
}
}
You can do your task with a single istruction:
Map<String,Map<String,Integer>> nestedMap = [
"x": ["a": 2, "b": 3, "c": 4],
"y": ["a": 20, "b": 30, "c": 40],
"z": ["a": 200, "b": 300, "c": 400] ]
result = nestedMap.inject([:]){ a, k, v ->
a << v.collectEntries{ [k + '-' + it.key, it.value] }; a }
println result
(plus setting the source data, plus print the result).
I'm new to Elixir and trying to get a random letter from a function.
I'm tryng to define a function that return a random letter between a and z.
For for some reason this sometimes returns a blank character.
Why?
defp random_letter do
"abcdefghijklmnopqrstuvwxyz"
|> String.split("")
|> Enum.random
end
def process do
Enum.each(1..12, fn(number) ->
IO.puts random_letter
end)
end
Output:
g
m
s
v
r
o
m
x
e
j
w
String.split("abcdefghijklmnopqrstuvwxyz", "")
returns
["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", ""]
Look at the last element in the list and you get your answer :)
To avoid that, you can use trim option like this:
String.split("abcdefghijklmnopqrstuvwxyz", "", trim: true)
When you want to split the string, here are two alternatives. The second one is used when you have Unicode strings.
iex(1)> String.codepoints("abcdefghijklmnopqrstuvwxyz")
["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"]
iex(2)> String.graphemes("abcdefghijklmnopqrstuvwxyz")
["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"]
Or you can use
iex(1)> <<Enum.random(?a..?z)>>
"m"
When I run this equation in Excel it tells me that there is only 1 argument for the IF statement. I am not sure why it is saying that when I have 3 arguments. Within the OR statement I have 2 different AND statements. It works just fine if I get rid of the second AND statement. Did I mess up a parentheses somewhere? I am not sure what is wrong. Any help would be greatly appreciated. Thanks!
=IF(OR(ARRAYFORMULA(SUM(COUNTIF(B19:O19,{"I","Ip","Ia","It","Ih","A","Aa","Ap","At","Ah","X","R","Rt","Rx","Rp","Rh","K","Kt","E","Et","AL","HL","TV*","FFSL","ADM*"})))=10, AND(ARRAYFORMULA(SUM(COUNTIF(B19:O19,{"R-10","Rx-10*","Rp-10","Rt-10*","Rh-10","I-10","Ia-10","Ip-10","It-10","Ih-10","X-10*","A-10*","At-10"})))=4, ARRAYFORMULA(SUM(COUNTIF(B19:O19,{"I","Ip","Ia","It","Ih","A","Aa","Ap","At","Ah","X","R","Rt","Rx","Rp","Rh","K","Kt","E","Et","AL","HL","TV*","FFSL","ADM*"})))=5),AND(ARRAYFORMULA(SUM(COUNTIF(B19:O19,{"HL-9","X-9","N-9","E-9","J-9","Jh-9","Nh-9","Eh-9"})))=8,ARRAYFORMULA(SUM(COUNTIF(B19:O19,{"I","Ip","Ia","It","Ih","A","Aa","Ap","At","Ah","X","R","Rt","Rx","Rp","Rh","K","Kt","E","Et","AL","HL","TV*","FFSL","ADM*"})))=1) ,"80 Hours","Error"))
This question makes me think "If only there was an online Excel Formula Beautifier".
Oh look, there is.
If you copy-and-paste it into the beautifier you get the code below.
You can now see that your parameters "80 Hours", "Error" are parameters of the first ARRAYFORMULA function, not the IF function.
=IF(
OR(
ARRAYFORMULA(
SUM(
COUNTIF(
B19:O19,
{ "I",
"Ip",
"Ia",
"It",
"Ih",
"A",
"Aa",
"Ap",
"At",
"Ah",
"X",
"R",
"Rt",
"Rx",
"Rp",
"Rh",
"K",
"Kt",
"E",
"Et",
"AL",
"HL",
"TV*",
"FFSL",
"ADM*"
ARRAYROWSTOP)
ARRAYSTOP)
)
)
) = 10,
AND(
ARRAYFORMULA(
SUM(
COUNTIF(
B19:O19,
{ "R-10",
"Rx-10*",
"Rp-10",
"Rt-10*",
"Rh-10",
"I-10",
"Ia-10",
"Ip-10",
"It-10",
"Ih-10",
"X-10*",
"A-10*",
"At-10"
ARRAYROWSTOP)
ARRAYSTOP)
)
)
) = 4,
ARRAYFORMULA(
SUM(
COUNTIF(
B19:O19,
{ "I",
"Ip",
"Ia",
"It",
"Ih",
"A",
"Aa",
"Ap",
"At",
"Ah",
"X",
"R",
"Rt",
"Rx",
"Rp",
"Rh",
"K",
"Kt",
"E",
"Et",
"AL",
"HL",
"TV*",
"FFSL",
"ADM*"
ARRAYROWSTOP)
ARRAYSTOP)
)
)
) = 5
),
AND(
ARRAYFORMULA(
SUM(
COUNTIF(
B19:O19,
{ "HL-9",
"X-9",
"N-9",
"E-9",
"J-9",
"Jh-9",
"Nh-9",
"Eh-9"
ARRAYROWSTOP)
ARRAYSTOP)
)
)
) = 8,
ARRAYFORMULA(
SUM(
COUNTIF(
B19:O19,
{ "I",
"Ip",
"Ia",
"It",
"Ih",
"A",
"Aa",
"Ap",
"At",
"Ah",
"X",
"R",
"Rt",
"Rx",
"Rp",
"Rh",
"K",
"Kt",
"E",
"Et",
"AL",
"HL",
"TV*",
"FFSL",
"ADM*"
ARRAYROWSTOP)
ARRAYSTOP)
)
)
) = 1
),
"80 Hours",
"Error"
)
)