Bring out the inner llama of a sentence
Perl, 52 bytes
The solution is provided as function that takes the string as argument and returns a list of positions.
One-based positions, case-sensitive search, without newlines: 52 bytes
sub l{pop=~/(l).*?(l).*?(a).*?(m).*?(a)/;@+[1..$#+]}
The case-sensitive search returns an empty array in the example of the question, because after matching the first three letters the lowercase letter
m
is missing in the input text.Support of newlines: + 1 byte = 53 bytes
sub l{pop=~/(l).*?(l).*?(a).*?(m).*?(a)/s;@+[1..$#+]}
The text can now span several lines.
Case-insensitive search: + 1 byte = 54 bytes
sub l{pop=~/(l).*?(l).*?(a).*?(m).*?(a)/si;@+[1..$#+]}
Now the example in the question reports a list of index positions, they are one-based numbers:
[45 68 77 106 115]
Zero-based positions: + 9 bytes = 63 bytes
sub l{pop=~/(l).*?(l).*?(a).*?(m).*?(a)/si;map{$_-1}@+[1..$#+]}
Result for the example in the question:
[44 67 76 105 114]
Ungolfed:
The latter variant includes more or less the other variants.
sub l {
# pop() gets the last argument
pop() =~ /(l).*?(l).*?(a).*?(m).*?(a)/si;
# the letters inbetween are matched against ".*?",
# the non-greedy variant of ".*". Thus ".*?"
# matches only as few as possible characters.
# The modifier /i controls the case-sensitivity
# and means ignore case. Without the case matters.
# Modifier /s treats the string as single line,
# even if it contains newlines.
map { $_-1 } # subtract 1 for zero-based positions
@+[1..$#+]
# Array @+ contains the end-positions of the last
# submatches, and of the whole match in the first position.
# Therefore the first value is sliced away.
# @+ is available since Perl 5.6.
}
# test
my @result = l(<<"END_STR");
Pie is good. I just ate a bunch of pies early this morning. Actually, it was closer to the afternoon. Mornings are good.
END_STR
print "[@result]\n";
sed, 299+1
Yes, sed can find a llama. No, sed can't do math. This is the longest answer so far, at 299+1 characters, because I had to teach sed to count.
This answer requires a sed with extended regular expressions (sed -E
or sed -r
). I used OpenBSD sed(1). Input is one string per line. (Therefore, the string may not contain a newline.) Output is a line of numbers, or nothing.
Usage (+1 character for -r
):
$ echo 'All arms on all shoulders may ache.' | sed -rf llama.sed
1 2 12 26 30
Source code (299 characters):
s/%/z/g
s/(.*)[Aa]/\1%/
s/(.*)[Mm](.*%)/\1%\2/
s/(.*)[Aa]((.*%){2})/\1%\2/
s/(.*)[Ll]((.*%){3})/\1%\2/
s/(.*)[Ll]((.*%){4})/\1%\2/
/(.*%){5}/!d
s/[^%]/z/g
:w
s/(z*)%/\10 z\1/
s/z*$//
s/z0/1/
s/z1/2/
s/z2/3/
s/z3/4/
s/z4/5/
s/z5/6/
s/z6/7/
s/z7/8/
s/z8/9/
s/([0-9]z*)z9/z\10/g
s/(z*)z9/1\10/
/[%z]/bw
The program first replaces the llama with five %
. (All %
in this program are literal.) The first command s/%/z/g
changes any %
to z
in the input line. The next five commands find the llama, so All arms on all shoulders may ache. becomes A%% arms on %ll shoulders %ay %che. Because each .*
is greedy, I always finds the llama on the right: llama llama would become llama %%%%%. If I can't get five %
, then /(.*%){5}/!d
deletes the input line and skips the next commands.
s/[^%]/z/g
changes every character but %
to z
. Then I enter a loop. s/(z*)%/\10 z\1/
changes the first %
to 0
, copies zero or more z
from left to right, and adds one more z
to right. This is so the number of z
will equal the index. For example, zz%zzz%...
becomes zz0 zzzzzzzz%...
because the first %
was at index 2, and the next %
is at index 8. s/z*$//
removes extra z
from the end of the string.
The next eleven commands count z
by removing each z
and counting up from 0
. It counts like zzz0
, zz1
, z2
, 3
. Also, 1zzzz9
becomes z1zzz0
(later 23
), or zzzz9
becomes 1zzz0
(later 13
). This loop continues until there are no more %
or z
.
CJam - 33
lel"llama"{1$#)_T+:T\@>}/;]___|=*
It gets the 1-based indexes (2 more bytes for 0-based)
Explanation:
l
reads a line from the input (replace with q
for whole input)
el
converts to lowercase
"llama"{...}/
executes the block for each "llama" letter
1$
copies the current string
#
finds the index of the letter
)_
increments and duplicates
T+:T
adds T (initially 0), updates T and leaves it on the stack
\@
swaps items around, now we have current-T, index, string
>
slices the string starting at the index
;
pops the remaining string
]
gathers the indexes in an array
At this point we have all the 1-based indexes; iff any letter was not found, the array will have duplicates.
___
makes 3 more copies of the array
|
(with 2 array copies) removes duplicates
=
compares, resulting in 0 if there were duplicates or 1 if not
*
multiplies the array 0 or 1 times accordingly