# Bubbler and 3 others are typing․․․

Implement a kind of "some users are typing" system, which can be found in Discord and some other chat clients.

• If 1 user (say, Bubbler) is typing, output Bubbler is typing...
• If 2 users (say, Bubbler and user) are typing, output Bubbler and user are typing...
• If 3 or more users are typing (say, Bubbler, randomuser1, randomuser2, randomuser3), output Bubbler and 3 others are typing...

The input is a list of usernames (list of strings); single string with delimiters (symbols or newlines) is also allowed. The output is a single string. Always respect the order of the input usernames. A username can contain upper/lowercase letters, digits, and spaces. Both usernames and the entire input list are never empty.

Standard rules apply. The shortest code in bytes wins.

## Test cases

["Bubbler"] -> "Bubbler is typing..."
["no one"] -> "no one is typing..."
["HyperNeutrino", "user"] -> "HyperNeutrino and user are typing..."
["emanresu A", "lyxal", "Jo King"] -> "emanresu A and 2 others are typing..."
["pxeger", "null", "null", "null", "null", "null", "null", "null", "null", "null", "null"]
-> "pxeger and 10 others are typing..."
["10", "null", "null", "null", "null", "null", "null", "null", "null", "null", "null"]
-> "10 and 10 others are typing..."
["and", "are"] -> "and and are are typing..."
["is"] -> "is is typing..."
["Bubbler is typing", "is", "are", "and"]
-> "Bubbler is typing and 3 others are typing..."

• May username contain keywords such as "is", "are", "typing", "and"? Some regex based solutions may be confused in such cases. And I would suggest add some testcases for these behaviors.
– tsh
Nov 25 '21 at 1:34
• @tsh Yes. I'll add some more testcases. Nov 25 '21 at 1:36

# Whython, 84 77 bytes

f=lambda a,*b:a+f" {'and %s are'%b?'is'%b} typing..."?f(a,f"{len(b)} others")

Attempt This Online!

This is my first answer in Whython, a horrible modification of Python I made. Its only new feature so far is ?, which is a short-circuiting exception [mis-]handling operator.

Here I use it to handle the case where there are more than 2 people typing: I recurse on the function f with a newly formatted second argument.

This answer also makes use of the (not Whython-specific) fact that you can %-format a string with a tuple of arguments, as long as it's the right number of arguments - and that number can also be 0 or 1 - so both " is"%() and " and %s are"%("second argument",) work fine.

-7 bytes thanks to @ovs: by trying to format and %s are (which requires b to be of length 1), then trying to format is (which requires b to be empty), we can save some bytes by avoiding len().

• 77 with one more error
– ovs
Nov 25 '21 at 13:45

# Python 3, 89 bytes

lambda a,*b:a+f" {b and'and %s are'%[b[0],'%d others'%len(b)][len(b)>1]or'is'} typing..."


Try it online!

-2 bytes thanks to dingledooper

• 89 bytes Nov 25 '21 at 2:04
• Also 89 bytes Nov 25 '21 at 5:10
• b[0] can just be b, because you can format a string with a tuple Nov 25 '21 at 9:51

g(a:x)=a++f x++" typing..."
f[]=" is"
f[x]=" and "++x++" are"
f l=f[show(length l)++" others"]


Try it online!

# Vyxalṡ, 32 bytes

ḣ:[‛λ¬$ḣ[_?L‹‛÷„]‛λ½|_‛is]Ȯ»...  Try it Online! ḣ # Push the first item (first user) and the rest to the stack # The first item will remain on the bottom of the stack :[ ] # If the rest exists (More than one user) ‛λ¬$                          # Push 'and' under the rest
ḣ                         # Push the first item (second user) and rest to the stack
[       ]                # If the rest exists (≥3 users)
_                       # Pop the second user
?L‹                    # Push the input's length, -1
‛÷„                 # Push 'others'
‛λ½             # Push 'are'
|            # Otherwise (one user)
_‛is        # Pop the resulting empty list and push 'is'
Ȯ»... # Push 'typing'
# (ṡ flag) join stack by spaces)


36 bytes flagless.

# Pip, 52 bytes

[POgg?["and"#c?[#g"others"]b"are"]"is""typing..."]Js


(Or 50 bytes in Pip -s, which would use a flag to accomplish the same thing as the Js at the end.)

Takes the names as separate command-line arguments. Try it online!

### Explanation

One big list, with the exact contents depending on the number of inputs, joined on spaces:

[                   ; List containing:
PO g              ;  Pop first element of g (the command-line args)
g ?               ;  Is g still nonempty?
[               ;   If so (multiple names case), list containing:
"and"         ;    This string
# c ?         ;    Is the third cmdline arg nonempty?
[           ;     If so (more than 2 names case), list containing:
# g       ;      Number of elements remaining in g
"others"  ;      and this string
]
b           ;     Otherwise (2 names case), second cmdline arg
"are"         ;    and this string
]
"is"            ;   Otherwise (single name case), this string
"typing..."       ;  and this string
]
J s                 ; Join that list (including all sublists) on space


# R, 113 102 bytes

Or R>=4.1, 95 bytes by replacing the word function with \.

function(s,l=length(s)-1,?=paste)s[1]?"if"(l,"and"?"if"(l-1,l?"others",s[2])?"are","is")?"typing..."


Try it online!

It appears that paste chains nicely, so we can rename that to ? and use like string addition (with convenient spaces).

# JavaScript, 77 bytes

a=>a[0]+ ${(l=a.length-1)?and${l>1?l+' others':a[1]} are:'is'} typing...


Try it online!

# Jelly, 47 bytes

ḢɓL,“¥ṃkŒ»ƊḷḊ?“ and ”;;“@k»ʋ;“ is”$ḷ?ṭ⁹“Æʋ@1'ɼ»  Try it online! Full program, as it uses Jelly's smash-printing and nilad dumping. ## How it works ḢɓL,“¥ṃkŒ»ƊḷḊ?“ and ”;;“@k»ʋ;“ is”$ḷ?ṭ⁹“Æʋ@1'ɼ» - Main link. Takes a list of usernames U on the left
Ḣ                                               - Chop off the first username, B, and set U' to be U without its head
?            - If:
ḷ             -   U' is non-empty
ʋ                    - Then:
?                                  -   If:
Ḋ                                   -     U' has more than one element
Ɗ                                     -   Then:
L                                             -     Take the length of U'
“¥ṃkŒ»                                      -     Compressed string: " others"
,                                            -     Pair; [len(U'), " others"]
ḷ                                     -   Else: Yield B
“ and ”;                          -   Prepend " and "
;“@k»                     -   Append " are"
$- Else: ;“ is” - Append " is" ṭ⁹ - Tack all of this to the end of B “Æʋ@1'ɼ» - Unparseable nilad. Smash print the previous stuff, then print " typing..."  # Vim, 92 89 bytes :3,$s/.*/\=line('.')-1.' others'
:1s/\v(\n.+)+/\1
:2s/^/and
ois typing...<esc>:3s/is/are
V{J


Try it online!

:3,$s/.*/\=line('.')-1.' others'<cr>  On every line from line 3 to the end of the buffer (if they exist), replace the contents of the line with the line number minus 1 followed by the string  others. :1s/\v(\n.+)+/\1<cr>  Beginning at (the end of) line 1, find 1 or more matches of a newline followed by some characters. Replace with the last (newline + characters) match. :2s/^/and <cr>  Add and  at the beginning of line 2 (if it exists). ois typing...<esc>  Open a new line after the cursor (which is always on the last line at the point) and insert is typing.... :3s/is/are  If that new line was line 3, change is to are (if it was line 2, leave unchanged). V{J  Enter visual line mode, select to the beginning of the buffer, and join lines (space-separated). # C (clang) with -m32, 151148 144 bytes • -3 thanks to ceilingcat • -4 by changing the base index from 0 to -1 To try a different approach from the other C submission, only the list of strings is needed as input for this answer. To get the length of the list, the function is called recursively and output is printed as the function processes the list. De-golfed:  g(*s,i) { // type-punning 'char **' as 'int *' printf( ~i? // is this the first element? *s? // (first element) no: end of list? i? // (end of list) no: is this the second element? "": // (second element) no, don't print if there is only 1 element s[1]? // (second element) yes, are there only 2 elements? "": // no, don't print second element if there are more than 2 " and %s": // yes, print second element i<2? // (end of list) yes: are there more than 2 elements? "": // no " and %d others": // yes, print summary *s, // (first element) yes: print first element *s? : // use string if not at end i); // otherwise use the list count *s? g(s+1,i+1): // continue processing list printf(" %s typing...", i<2? "is": "are"); } f(s) { g(s,-1); } // initialize list count  g(*s,i){printf(~i?*s?i?"":s[1]?"":" and %s":i<2?"":" and %d others":*s,*s?:i);*s?g(s+1,i+1):printf(" %s typing...",i?"are":"is");}f(s){g(s,-1);}  Try it online! • 148 bytes Nov 25 '21 at 10:56 # Vyxalṡ, 42 41 bytes ḢḢ[₌hL‹ ÷„+"] λ¬ jis λ½⌈?L‹ḃiȮ»...  Try it Online! Lovely dictionary compression. 44 bytes flagless ## Explained ḢḢ[₌hL‹ ÷„+"] λ¬ jis λ½⌈?L‹ḃiȮ»... ḢḢ # If the length of the input is > 2: [₌hL‹ # Push the head of the input and the length of the input - 1  ÷„+ # and append the string " others" to that " # and wrap both in a list ] # endif  λ¬ j # Join the top of the stack on " and " is λ½⌈ # Push the list ["is", "are"] ?L‹ # And push the lenght of the input - 1 ḃi # boolify it to make it 0 or 1 (this is basically just checking if the list length is 1) and index into the list Ȯ»... # Push "typing..." to the stack # The -ṡ flag joins the stack on spaces. Without it, you'd need WṄ for +3 bytes.  ## Excel, 100 bytes =A1&IF(COUNTA(A:A)=1," is"," and "&(IF(COUNTA(A:A)=2,A2,COUNTA(A:A)-1&" others"))&" are")&" typing…"  # PHP, 94 91 bytes fn($a)=>"$a[0] ".($a[1]?'and '.($a[2]?count($a)-1 .' others':$a[1]).' are':is).' typing...'  Try it online! Raw first try at it, probably still golfable. It's a pity PHP cannot handle concatenation or nested ternary conditions without brackets.. EDIT: 3 bytes saved with thanks to Dom Hastings and 640KB's suggestions! • Nice, I tried to tinker with rest args but ended up longer! You can save 1 byte moving the space to after $l[0] and using a bare word for is too: Try it online! Nov 25 '21 at 18:37
• Here's a 92 byte version. Maybe there's some ideas to combine? (I wrote this on TIO on my phone so I've hit my limit for now!) Nov 26 '21 at 2:32
• @DomHastings I tried to move the space too but I dindn't think to pair it with an assumed constant, nice! Nov 26 '21 at 8:31
• @640KB Oh, adding a space there can save a couple of brackets! I have to remember this trick, PHP parser can be weird sometimes. I actually combined the 2 suggestions Nov 26 '21 at 8:36

# Ruby, 79 ... 73 bytes

->a,*s{[a,s[0]?[:and,s[1]?[s.size,:others]:s,:are]:"is","typing..."]*" "}


Try it online!

Thanks ovs for -3 bytes

• You can replace a few of those strings with symbols to save a byte each: TIO
– ovs
Nov 26 '21 at 8:42

# C (clang)-m32, 109 107 bytes

-2 thanks to ErikF!

t(*n,l){printf(--l?l-1?"%s and %d others are%s":"%s and %s are%s":"%s is%3$s",*n,l-1?l:n[1]," typing...");}  Try it online! Takes input as an array of char* and its length. # C (clang), 115 108 bytes -7 thanks to ErikF! t(**n,l){printf(--l?l-1?"%s and %d others are%s":"%s and %s are%s":"%s is%3$s",*n,l-1?l:n[1]," typing...");}


Try it online! Takes input as an array of char* and its length.

# Zsh, 67 bytes

x=(is "and $2 are") echo$1 ${x[#]-and$[~-#] others are} typing...

Attempt This Online!

Looks up the correct format string into the array $x (one of is, and$2 are, or an unset variable), defaulting to and $[~-#] others are (where $[~-#] is number of arguments - 1), and prints that with the first input $1 and the string typing.... ## Zsh, 79 bytes T=\ typing... 1()<<<$1\ is$T 2()<<<"$1 and $2 are$T"
$#$@||2 $1$[~-#]\ others

Attempt This Online!

• Defines two functions called 1 and 2 which print formatted messages for singular and plural, respectively
• Then it calls the function with the name $# (the number of arguments), so that if 1 or 2 people are typing, the correct message is printed • If there are more than 2 arguments, the function call will fail, triggering the || branch • This calls 2 with the first argument and the string $[~-#] others
• (It has to be ~-#, because #-1 doesn't work, although I don't really understand why)

## Batch, 170 bytes

@if "%2"=="" echo %~1 is typing...&exit/b
@set s=%~1
@set t=%~2
@set n=2
:l
@if not "%3"=="" set t=%n% others&set/an+=1&shift&goto l
@echo %s% and %t% are typing...


Takes input as command-line parameters. Explanation:

@if "%2"=="" echo %~1 is typing...&exit/b


Handle the case of there only being one user.

@set s=%~1
@set t=%~2
@set n=2


Assume there are two users.

:l
@if not "%3"=="" set t=%n% others&set/an+=1&shift&goto l


While more users are found, update the number of others and increment the total number of users.

@echo %s% and %t% are typing...


Output the users or count as appropriate.

## Prolog, 108 135 bytes (of which 27 are spent on producing a string)

This was fun, although Prolog does not have very much scope for golfery. My first, unsubmitted, attempt tried to use Prolog's Directed Clause Grammar syntax, but I couldn't get that to be shorter.

IIRC the rules here on CoGoSO correctly, it is permitted to return a string, and not required to print it; so I'm returning in a variable (O), as is traditional in Prolog.

f([X|R],O):-g(R,P),flatten([X,P,typing],Q),atomics_to_string(Q,' ',O).
g([],is).
g(L,[and,C,are]):-L=[Y],C=Y;C=[N,others],length(L,N).


Legend to the one-letter variable names:

% X: first name
% R: Rest of the names
% L: Rest of the names, seen from g/2's perspective
% O: Output
% C: Company, i.e. a word list like '[and, Y]'.
% P: intermediate output: nested list of atoms (representing inner words)
% Q: intermediate result: flat list of atoms


Usage examples:

?- f([jan,piet,klaas,tjoris,korneel], O).
O = "jan and 4 others are typing"

?- f([jan,piet,klaas], O).
O = "jan and 2 others are typing"

?- f([jan,piet], O).
O = "jan and piet are typing"

?- f([jan], O).
O = "jan is typing"


Ungolfed:

f([X|MoreNames], Out) :-
% InnerWords like 'is' or '[and, Y, are]'
innerwords(MoreNames, InnerWords),
% Flatten [X, [and, [N others], are], typing]
% to [X, and, N, others, are, typing]
flatten([X, InnerWords, typing], AllWords),
% Turn it into a string. This is not a very Prolog thing to
% do, but the challeng requests it, so here goes.
atomics_to_string(AllWords, ' ', Out).

% No more others: produce 'is' for 'X is typing'.
innerwords([], is).

% One or more others
innerwords(MoreNames, [and, Company, are]) :-
% MoreNames is a list of 1, s
Company=Y, MoreNames=[Y] ;
% If we reach here, there are 2+ others.
%  and getting wrong answers as a reward.)
% [and, [N, others], are]
Company=[N, others], length(MoreNames, N).

• Welcome to Code Golf, and nice first answer! Make sure to check out our tips for golfing in Prolog to see if there are any ways you can shorten this. Nov 29 '21 at 13:55

# Python3, 95 bytes:

lambda d:f'{d[0]} {"and "+[d[1],f"{l-1} others"][l>2]+" are"if(l:=len(d))>1else"is"} typing...'


This solution requires no pre-formatting of the input list (i.e no unpacking).

• Welcome to Code Golf, and nice first answer! Be sure to check out our Tips for golfing in Python page for ways you can golf your program. This is currently invalid, as it's a snippet that assumes the input is in d. However, you can make this valid by turning it into a function by prepending lambda d: for +9 bytes Nov 26 '21 at 22:00

a=>(b=a.length,a[0]+(b<2?" is": and ${b<3?a[1]:b-1+" others"} are)+" typing...")  Try it online! • 80 bytes. removed the parentheses declared b inside. Nov 25 '21 at 14:07 # 05AB1E, 42 bytes ćs©gĀi'€ƒ®ćsgĀi\Ig<'ˆ†]…is€™#Ig≠è“ÜÔ...“ðý  Explanation: ć # Extract head of the (implicit) input-list; pop and push # remainder-list and first item separately s # Swap so the remainder-list is at the top © # Store it in variable ® (without popping) gĀi # Pop and if there are any strings left (2+ inputs): '€ƒ '# Push dictionary string "and" ® # Push list ® ćs # Extract head and swap again gĀi # Pop and if there are any strings left (3+ inputs): \ # Discard the head we've pushed Ig< # Push the input-length - 1 'ˆ† '# Push dictionary string "others" ] # Close both if-statements …is€™ # Push dictionary string "is are" # # Split it on spaces: ["is","are"] Ig # Push the input-length ≠ # Check that it's NOT 1 (0 if 1; 1 otherwise) è # Use that to index into the pair “ÜÔ...“ # Push dictionary string "typing..." ðý # Join the entire stack with space delimiter # (after which the result is output implicitly)  See this 05AB1E tip of mine (section How to use the dictionary?) to understand why '€ƒ is "and"; 'ˆ† is "others"; …is€™ is "is are"; and “ÜÔ...“ is "typing...". # Retina 0.8.2, 63 bytes ^.*$
$& is (¶.*){2,} ¶$#1 others
¶(.*)
and $1 are$
typing...


Try it online! Takes newline-delimited input but test suite splits on commas for convenience. Explanation:

^.*& is


Just one person is typing.

(¶.*){2,}
¶$#1 others  If there are at least two others then replace the list with a single element giving their count. ¶(.*) and$1 are


More than one person is typing.

x>y...=x>"$(length(y)) others"  Try it online! the names are expected as arguments of the function >, for example >("Bubbler"), >("a","b","c") # Julia 0.7, 79 bytes !t=t[1]*"$((L=length(t)-1)<1?:is:"and $(L<2?t[2]:"$L others") are") typing..."


Try it online!

# Perl 5 + -pl, 64 bytes

$_.=" @{[(@_=<>)?('and',$#_?@_.' others':@_,are):is]} typing..."


Try it online!

## Explanation

The first name is implicitly stored in $_ (via -p) so it's easy to just add to it a string consisting of a leading space and an in-line list @{[...]}, finishing with  typing... as that's always needed no matter what. The in-line list will be implicitly joined by $" (space), which allows use of some barewords, saving bytes for the quotes. The rest of the input (<>) is slurped into @_ which, if it's non-empty, results in a list consisting of 'and', and if $#_ is truthy ($#_ is the last array index of @_, which will be 0 if there is only one entry), we use @_ in a scalar context (which is the number of elements i n@_) concatenated with ' others', but if @_ only contains one entry, add it as is. If @_ is empty, we just add the bareword is to the in-line list.

# SimpleTemplate 0.84, 126 bytes

This full program expects a variable number of arguments passed to the render() method. Argument unpacking can be used for convenience. Each argument is 1 user.

{@echoargv.0}{@ifargc is1} is{@else} and {@ifargc is2}{@echoargv.1}{@else}{@set-T argc,1}{@echoT} others{@/} are{@/} typing...


This will output the text into the standard output, by default.

## Ungolfed:

To make it easier to understand, here's an ungolfed version of the code above:

{@echo argv.0}

{@if argc is 1}
{@echo " is"}
{@else}
{@echo " and "}

{@if argc is 2}
{@echo argv.1}
{@else}
{@set- count argc, 1}
{@echo count, " others"}
{@/}

{@echo " are"}
{@/}

{@echo " typing..."}


There are a few important things to keep in mind when reading the code.
This section is important, but code is importanter.

• All text that isn't part of an instruction, is just displayed as-is.

Newlines and all, except when what is to be displayed is just whitespace, which will be deleted by default.

• The variables argv and argc almost the same as the $argv and $argc variables in PHP, or argv and argc in C.

However, the first argument in argv is the first argument passed to the render() method of the compiler, instead of the name of the file.

• Whitespace handling is super loose, so, {@echo 123} and {@echo123} work the same. This helps saving a lot of space.

• The line {@echo argv.0} shows the syntax to access an array element.
This line is accessing the 1st 0-indexed element in the array argv.

• The line {@set- count argc, 1} looks weird.
All it does is to set the the variable count to the value of argc - 1.

The set- (set minus) instruction does both operations in one.
This is the same as {@set count argc}{@inc by -1 count}, or $argc =$count; $count += -1;, but all in 1. • The lines {@if argc is <val>} are verifying is argc is <value> (1 or 2, in this case). Internally, this will verify if argc and <value> are identical, with the === operator. ## Running the code: You can run the code on: http://sandbox.onlinephpfunctions.com/code/7233b0a3b8b2fd96924e054944e4ddc850c8f381 Please pick a version between 5.6 and 7.4.13. Can't fix the 8.0.0 compatibility issue without invalidating this answer. # Rust, 185183162 136 bytes |v:&[&str]|[v[0],&match v.len(){1=>"is".into(),2=>format!("and {} are",v[1]),l=>format!("and {} others are",l-1)},"typing..."].join(" ")  Try it online! Ungolfed: |v: &[&str]| { [ v[0], &match v.len() { 1 => "is".into(), 2 => format!("and {} are", v[1]), l => format!("and {} others are", l - 1), }, "typing...", ] .join(" ") }  Log: • -2 for excluding the closure name • -21 for v:&[&str], implicit return type, wildcard with binding, s removal, brackets. Thanks @AnttiP! • -26 for removing redundant "typing..." occurrences and one format! use (by @Ezhik) • -21 bytes with input type v:&[&str], removing the String type annotation (which can be inferred), replacing the wildcard with a binding, removing the assignment to s, and the now redundant brackets: |v:&[&str]|match v.len(){1=>format!("{} is typing...",v[0]),2=>format!("{} and {} are typing...",v[0],v[1]),l=>format!("{} and {} others are typing...",v[0],l-1)} Nov 25 '21 at 13:07 • @bigyihsuan and Ezhik, we have a consensus that it is not OK to golf an answer by editing it directly. Instead, please leave golfing suggestions in comments. Reference: When is it OK or not to edit someone else's post? Dec 1 '21 at 4:16 # Python 2, 102 bytes i=input() l=len(i) print(i[0]+" and "+[l-1+" others",i[-1]][l<3]+" are",i[0]+" is")[l<2],"typing..."  Try it online! Simple nested list slices but takes advantage of no brackets required for printing in Python 2 and also the use of back ticks for the string conversion. # jq, 99 99+1 (-r flag penalty) = 100 bytes (length-1)as$l|.[0]as$n|["is","and \(.[1]) are"]|"\($n) \(.[$l]//"and \($l) others are") typing..."


Try it online!

I'm pretty sure there's room to golf this more, but not tonight. :)

1. Stash the length of the input list in $l. stash the first element in $n

(length-1)as$l|.[0]as$n

2. Generate a new list consisting of two elements, is and is 2nd-input-list-entry are

|["is","and \(.[1]) are"]

3. Print the first input list name, then index the list from step 2 by the number of entries in the input minus 1. The // code is called if the index doesn't exist. In that case the alternate text and length-of-input-minus-1 are is used. The typing... string is appended regardless of what other choices were made.

|"\($n) \(.[$l]//"and \(\$l) others are") typing..."

# Java, 93 bytes

a->a[0]+(a.length>1?" and "+(a.length>2?a.length-1+" others":a[1])+" are":" is")+" typing..."
`

Try it online!