Comma quibbling

From Rosetta Code
Task
Comma quibbling
You are encouraged to solve this task according to the task description, using any language you may know.

Comma quibbling is a task originally set by Eric Lippert in his blog.


Task

Write a function to generate a string output which is the concatenation of input words from a list/sequence where:

  1. An input of no words produces the output string of just the two brace characters "{}".
  2. An input of just one word, e.g. ["ABC"], produces the output string of the word inside the two braces, e.g. "{ABC}".
  3. An input of two words, e.g. ["ABC", "DEF"], produces the output string of the two words inside the two braces with the words separated by the string " and ", e.g. "{ABC and DEF}".
  4. An input of three or more words, e.g. ["ABC", "DEF", "G", "H"], produces the output string of all but the last word separated by ", " with the last word separated by " and " and all within braces; e.g. "{ABC, DEF, G and H}".


Test your function with the following series of inputs showing your output here on this page:

  • [] # (No input words).
  • ["ABC"]
  • ["ABC", "DEF"]
  • ["ABC", "DEF", "G", "H"]


Note: Assume words are non-empty strings of uppercase characters for this task.

Ada[edit]

with Ada.Text_IO, Ada.Command_Line; use Ada.Command_Line;
 
procedure Comma_Quibble is
 
begin
case Argument_Count is
when 0 => Ada.Text_IO.Put_Line("{}");
when 1 => Ada.Text_IO.Put_Line("{" & Argument(1) & "}");
when others =>
Ada.Text_IO.Put("{");
for I in 1 .. Argument_Count-2 loop
Ada.Text_IO.Put(Argument(I) & ", ");
end loop;
Ada.Text_IO.Put(Argument(Argument_Count-1) & " and " &
Argument(Argument_Count) & "}");
end case;
end Comma_Quibble;
Output:
./comma_quibble
{}
./comma_quibble abc
{abc}
./comma_quibble abc def
{abc and def}
./comma_quibble abc def g h
{abc, def, g and h}

ALGOL 68[edit]

Works with: ALGOL 68G version Any - tested with release 2.8.win32
# returns a string ( assumed to be of space-separated words ) with the words  #
# separated by ", ", except for the last which is separated from the rest by #
# " and ". The list is enclosed by braces #
PROC to list = ( STRING words ) STRING:
BEGIN
# count the number of words #
INT word count := 0;
BOOL in word := FALSE;
FOR char pos FROM LWB words TO UPB words
DO
IF NOT is upper( words[ char pos ] )
THEN
# not an upper-case letter, possibly a word has been ended #
in word := FALSE
ELSE
# not a delimitor, possibly the start of a word #
IF NOT in word
THEN
# we are starting a new word #
word count +:= 1;
in word := TRUE
FI
FI
OD;
 
# format the result #
STRING result := "{";
in word := FALSE;
INT word number := 0;
FOR char pos FROM LWB words TO UPB words
DO
IF NOT is upper( words[ char pos ] )
THEN
# not an upper-case letter, possibly a word has been ended #
in word := FALSE
ELSE
# not a delimitor, possibly the start of a word #
IF NOT in word
THEN
# we are starting a new word #
word number +:= 1;
in word := TRUE;
IF word number > 1
THEN
# second or subsequent word - need a separator #
result +:= IF word number = word count
THEN # final word #
" and "
ELSE # non-final word #
", "
FI
FI
FI;
# add the character to the result #
result +:= words[ char pos ]
FI
OD;
 
result + "}"
END # to list # ;
 
 
# procedure to test the to list PROC #
PROC test to list = ( STRING words ) VOID:
print( ( ( words
+ ": "
+ to list( words )
)
, newline
)
);
 
# test the to list PROC #
test to list( "" );
test to list( "ABC" );
test to list( "ABC DEF" );
test to list( "ABC DEF G H" )
Output:
: {}
ABC: {ABC}
ABC DEF: {ABC and DEF}
ABC DEF G H: {ABC, DEF, G and H}

ALGOL W[edit]

begin
 
 % returns a list of the words contained in wordString, separated by ", ", %
 % except for the last which is separated from the rest by " and ".  %
 % The words are enclosed by braces  %
string(256) procedure toList ( string(256) value words ) ;
begin
string(256) list;
integer wordCount, wordNumber, listPos;
logical inWord;
 
 % returns true if ch is an upper-case letter, false otherwise  %
 % assumes the letters are consecutive in the character set  %
 % (as in ascii) would not be correct if the character set was %
 % ebcdic (as in the original implementations of Algol W)  %
logical procedure isUpper ( string(1) value ch ) ; ch >= "A" and ch <= "Z" ;
 
 % adds a character to the result  %
procedure addChar( string(1) value ch ) ;
begin
list( listPos // 1 ) := ch;
listPos := listPos + 1;
end addChar ;
 
 % adds a string to the result  %
procedure addString( string(256) value str
 ; integer value len
) ;
for strPos := 0 until len - 1 do addChar( str( strPos // 1 ) );
 
 % count the number of words  %
 
wordCount := 0;
inWord  := false;
for charPos := 0 until 255
do begin
if isUpper( words( charPos // 1 ) ) then begin
 % not an upper-case letter, possibly a word has been ended  %
inWord := false
end
else begin
 % not a delimitor, possibly the start of a word  %
if not inWord then begin
 % we are starting a new word  %
wordCount := wordCount + 1;
inWord  := true
end if_not_inWord
end
end for_charPos;
 
 % format the result  %
 
list  := "";
listPos  := 0;
inWord  := false;
wordNumber := 0;
 
addChar( "{" );
 
for charPos := 0 until 255
do begin
if not isUpper( words( charPos // 1 ) ) then begin
 % not an upper-case letter, possibly a word has been ended  %
inWord := false
end
else begin
 % not a delimitor, possibly the start of a word  %
if not inWord then begin
 % we are starting a new word  %
wordNumber := wordNumber + 1;
inWord  := true;
if wordNumber > 1 then begin
 % second or subsequent word - need a separator  %
if wordNumber = wordCount then addString( " and ", 5 ) % final word %
else addString( ", ", 2 ) % non-final word %
end
end;
 % add the character to the result  %
addChar( words( charPos // 1 ) )
end
end for_charPos ;
 
addChar( "}" );
 
list
end toList ;
 
 
 % procedure to test the toList procedure  %
procedure testToList ( string(256) value words ) ;
begin
string(256) list;
list := toList( words );
write( s_w := 0
, words( 0 // 32 )
, ": "
, list( 0 // 32 )
)
end testToList ;
 
 % test the toList procedure  %
testToList( "" );
testToList( "ABC" );
testToList( "ABC DEF" );
testToList( "ABC DEF G H" );
 
end.
Output:
                                : {}
ABC                             : {ABC}
ABC DEF                         : {ABC and DEF}
ABC DEF G H                     : {ABC, DEF, G and H}

AutoHotkey[edit]

MsgBox % quibble([])
MsgBox % quibble(["ABC"])
MsgBox % quibble(["ABC", "DEF"])
MsgBox % quibble(["ABC", "DEF", "G", "H"])
 
quibble(d) {
s:=""
for i, e in d
{
if (i<d.MaxIndex()-1)
s:= s . e . ", "
else if (i=d.MaxIndex()-1)
s:= s . e . " and "
else
s:= s . e
}
return "{" . s . "}"
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

AWK[edit]

function quibble(a, n,    i, s) {
for (i = 1; i < n - 1; i++) s = s a[i] ", "
i = n - 1; if (i > 0) s = s a[i] " and "
if (n > 0) s = s a[n]
return "{" s "}"
}
 
BEGIN {
print quibble(a, 0)
n = split("ABC", b); print quibble(b, n)
n = split("ABC DEF", c); print quibble(c, n)
n = split("ABC DEF G H", d); print quibble(d, n)
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Batch File[edit]

@echo off
setlocal enabledelayedexpansion

::THE MAIN THING...

echo.
set inp=[]
call :quibble
set inp=["ABC"]
call :quibble
set inp=["ABC","DEF"]
call :quibble
set inp=["ABC","DEF","G","H"]
call :quibble
echo.
pause
exit /b
::/THE MAIN THING...

::THE FUNCTION

:quibble
set cont=0
set proc=%inp:[=%
set proc=%proc:]=%
 
for %%x in (%proc%) do (
set /a cont+=1
set x=%%x
set str!cont!=!x:"=!
)
set /a bef=%cont%-1
set output=%str1%
if %cont%==2 (set output=%str1% and %str2%)
if %cont% gtr 2 (
for /l %%y in (2,1,%bef%) do (
set output=!output!^, !str%%y!
)
set output=!output! and !str%cont%!
)
echo {!output!}
goto :EOF
::/THE FUNCTION
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Press any key to continue . . .

Bracmat[edit]

( :?L1
& ABC:?L2
& ABC DEF:?L3
& ABC DEF G H:?L4
& L1 L2 L3 L4:?names
& ( quibble
= w
.  !arg:%?w (% %:?arg)
& !w ", " quibble$!arg
| !arg:%?w %?arg&!w " and " quibble$!arg
| !arg
)
& (concat=.str$("{" quibble$!arg "}"))
& whl
' (!names:%?name ?names&out$(!name concat$!!name))
);
Output:
L1 {}
L2 {ABC}
L3 {ABC and DEF}
L4 {ABC, DEF, G and H}

C[edit]

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
char *quib(const char **strs, size_t size)
{
 
size_t len = 3 + ((size > 1) ? (2 * size + 1) : 0);
size_t i;
 
for (i = 0; i < size; i++)
len += strlen(strs[i]);
 
char *s = malloc(len * sizeof(*s));
if (!s)
{
perror("Can't allocate memory!\n");
exit(EXIT_FAILURE);
}
 
strcpy(s, "{");
switch (size) {
case 0: break;
case 1: strcat(s, strs[0]);
break;
default: for (i = 0; i < size - 1; i++)
{
strcat(s, strs[i]);
if (i < size - 2)
strcat(s, ", ");
else
strcat(s, " and ");
}
strcat(s, strs[i]);
break;
}
strcat(s, "}");
return s;
}
 
int main(void)
{
const char *test[] = {"ABC", "DEF", "G", "H"};
char *s;
 
for (size_t i = 0; i < 5; i++)
{
s = quib(test, i);
printf("%s\n", s);
free(s);
}
return EXIT_SUCCESS;
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF and G}
{ABC, DEF, G and H}

C#[edit]

using System;
using System.Linq;
 
namespace CommaQuibbling
{
internal static class Program
{
#region Static Members
private static string Quibble(string[] input)
{
return
String.Format("{{{0}}}",
String.Join("",
input.Reverse().Zip(
new [] { "", " and " }.Concat(Enumerable.Repeat(", ", int.MaxValue)),
(x, y) => x + y).Reverse()));
}
 
 
private static void Main()
{
Console.WriteLine( Quibble( new string[] {} ) );
Console.WriteLine( Quibble( new[] {"ABC"} ) );
Console.WriteLine( Quibble( new[] {"ABC", "DEF"} ) );
Console.WriteLine( Quibble( new[] {"ABC", "DEF", "G", "H"} ) );
 
Console.WriteLine( "< Press Any Key >" );
Console.ReadKey();
}
 
#endregion
}
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}
< Press Any Key >

C++[edit]

#include <iostream>
 
template<class T>
void quibble(std::ostream& o, T i, T e) {
o << "{";
if (e != i) {
T n = i++;
const char* more = "";
while (e != i) {
o << more << *n;
more = ", ";
n = i++;
}
o << (*more?" and ":"") << *n;
}
o << "}";
}
 
int main(int argc, char** argv) {
char const* a[] = {"ABC","DEF","G","H"};
for (int i=0; i<5; i++) {
quibble(std::cout, a, a+i);
std::cout << std::endl;
}
return 0;
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF and G}
{ABC, DEF, G and H}

Clojure[edit]

(defn quibble [sq]
(let [sep (if (pos? (count sq)) " and " "")]
(apply str
(concat "{" (interpose ", " (butlast sq)) [sep (last sq)] "}"))))
 
; Or, using clojure.pprint's cl-format, which implements common lisp's format:
(defn quibble-f [& args]
(clojure.pprint/cl-format nil "{~{~a~#[~; and ~:;, ~]~}}" args))
 
(def test
#(doseq [sq [[]
["ABC"]
["ABC", "DEF"]
["ABC", "DEF", "G", "H"]]]
((comp println %) sq)))
 
(test quibble)
(test quibble-f)
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

COBOL[edit]

Works with: OpenCOBOL version 2.0
       >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. comma-quibbling-test.
 
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
FUNCTION comma-quibbling
.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 strs-area.
03 strs-len PIC 9.
03 strs PIC X(5)
OCCURS 0 TO 9 TIMES
DEPENDING ON strs-len.
 
PROCEDURE DIVISION.
MOVE "ABC" TO strs (1)
MOVE "DEF" TO strs (2)
MOVE "G" TO strs (3)
MOVE "H" TO strs (4)
 
PERFORM VARYING strs-len FROM 0 BY 1 UNTIL strs-len > 4
DISPLAY FUNCTION comma-quibbling(strs-area)
END-PERFORM
.
END PROGRAM comma-quibbling-test.
 
 
IDENTIFICATION DIVISION.
FUNCTION-ID. comma-quibbling.
 
DATA DIVISION.
LOCAL-STORAGE SECTION.
01 i PIC 9.
 
01 num-extra-words PIC 9.
 
LINKAGE SECTION.
01 strs-area.
03 strs-len PIC 9.
03 strs PIC X(5)
OCCURS 0 TO 9 TIMES
DEPENDING ON strs-len.
 
01 str PIC X(50).
 
PROCEDURE DIVISION USING strs-area RETURNING str.
EVALUATE strs-len
WHEN ZERO
MOVE "{}" TO str
GOBACK
 
WHEN 1
MOVE FUNCTION CONCATENATE("{", FUNCTION TRIM(strs (1)), "}")
TO str
GOBACK
END-EVALUATE
 
MOVE FUNCTION CONCATENATE(FUNCTION TRIM(strs (strs-len - 1)),
" and ", FUNCTION TRIM(strs (strs-len)), "}")
TO str
 
IF strs-len > 2
SUBTRACT 2 FROM strs-len GIVING num-extra-words
PERFORM VARYING i FROM num-extra-words BY -1 UNTIL i = 0
MOVE FUNCTION CONCATENATE(FUNCTION TRIM(strs (i)), ", ", str)
TO str
END-PERFORM
END-IF
 
MOVE FUNCTION CONCATENATE("{", str) TO str
.
END FUNCTION comma-quibbling.
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF and G}
{ABC, DEF, G and H}

CoffeeScript[edit]

quibble = ([most..., last]) -> 
'{' +
(most.join ', ') +
(if most.length then ' and ' else '') +
(last or '') +
'}'
 
console.log quibble(s) for s in [ [], ["ABC"], ["ABC", "DEF"],
["ABC", "DEF", "G", "H" ] ]
 
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Common Lisp[edit]

 
(defun quibble (&rest args)
(format t "{~{~a~#[~; and ~:;, ~]~}}" args))
 
(quibble)
(quibble "ABC")
(quibble "ABC" "DEF")
(quibble "ABC" "DEF" "G" "H")
 
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

D[edit]

import std.stdio, std.string;
 
string quibbler(in string[] seq) pure /*nothrow*/ {
if (seq.length <= 1)
return format("{%-(%s, %)}", seq);
else
return format("{%-(%s, %) and %s}", seq[0 .. $-1], seq[$-1]);
}
 
void main() {
//foreach (immutable test; [[],
foreach (const test; [[],
["ABC"],
["ABC", "DEF"],
["ABC", "DEF", "G", "H"]])
test.quibbler.writeln;
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Alternative Version[edit]

import std.stdio, std.string, std.algorithm, std.conv, std.array;
 
enum quibbler = (in string[] a) pure =>
"{%-(%s and %)}".format(a.length < 2 ? a :
[a[0 .. $-1].join(", "), a.back]);
 
void main() {
[[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]]
.map!quibbler.writeln;
}
Output:
["{}", "{ABC}", "{ABC and DEF}", "{ABC, DEF, G and H}"]

DCL[edit]

$ list = "[]"
$ gosub comma_quibbling
$ write sys$output return_string
$
$ list = "[""ABC""]"
$ gosub comma_quibbling
$ write sys$output return_string
$
$ list = "[""ABC"", ""DEF""]"
$ gosub comma_quibbling
$ write sys$output return_string
$
$ list = "[""ABC"", ""DEF"", ""G"", ""H""]"
$ gosub comma_quibbling
$ write sys$output return_string
$
$ exit
$
$ comma_quibbling:
$ list = list - "[" - "]"
$ return_string = "{}"
$ if list .eqs. "" then $ return
$ return_string = "{" + f$element( 0, ",", list ) - """" - """"
$ if f$locate( ",", list ) .eq. f$length( list ) then $ goto done2
$ i = 1
$ loop:
$ word = f$element( i, ",", list ) - """" - """"
$ if word .eqs. "," then $ goto done1
$ return_string = return_string - "^" + "^," + word
$ i = i + 1
$ goto loop
$ done1:
$ return_string = f$element( 0, "^", return_string ) + " and" + ( f$element( 1, "^", return_string ) - "," )
$ done2:
$ return_string = return_string + "}"
$ return

{}out}}

$ @comma_quibbling
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Déjà Vu[edit]

comma-quibble lst:
"}" )
if lst:
pop-from lst
if lst:
" and "
pop-from lst
for item in lst:
item ", "
concat( "{"
 
!. comma-quibble []
!. comma-quibble [ "ABC" ]
!. comma-quibble [ "ABC" "DEF" ]
!. comma-quibble [ "ABC" "DEF" "G" "H" ]
Output:
"{}"
"{ABC}"
"{ABC and DEF}"
"{ABC, DEF, G and H}"

EchoLisp[edit]

 
(lib 'match)
 
(define (quibble words)
(match words
[ null "{}"]
[ (a) (format "{ %a }" a)]
[ (a b) (format "{ %a and %a }" a b)]
[( a ... b c) (format "{ %a %a and %a }" (for/string ([w a]) (string-append w ", ")) b c)]
[else 'bad-input]))
 
 
;; output
 
(for ([t '(() ("ABC") ("ABC" "DEF") ("ABC" "DEF" "G" "H"))])
(writeln t '----> (quibble t)))
 
null ----> "{}"
("ABC") ----> "{ ABC }"
("ABC" "DEF") ----> "{ ABC and DEF }"
("ABC" "DEF" "G" "H") ----> "{ ABC, DEF, G and H }"
 


Eiffel[edit]

 
class
APPLICATION
 
create
make
 
feature
 
make
-- Test of the feature comma_quibbling.
local
l: LINKED_LIST [STRING]
do
create l.make
io.put_string (comma_quibbling (l) + "%N")
l.extend ("ABC")
io.put_string (comma_quibbling (l) + "%N")
l.extend ("DEF")
io.put_string (comma_quibbling (l) + "%N")
l.extend ("G")
l.extend ("H")
io.put_string (comma_quibbling (l) + "%N")
end
 
comma_quibbling (l: LINKED_LIST [STRING]): STRING
-- Elements of 'l' seperated by a comma or an and where appropriate.
require
l_not_void: l /= Void
do
create Result.make_empty
Result.extend ('{')
if l.is_empty then
Result.append ("}")
elseif l.count = 1 then
Result.append (l [1] + "}")
else
Result.append (l [1])
across
2 |..| (l.count - 1) as c
loop
Result.append (", " + l [c.item])
end
Result.append (" and " + l [l.count] + "}")
end
end
 
end
 
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Elixir[edit]

Translation of: Erlang
defmodule RC do
def generate( list ), do: "{#{ generate_content(list) }}"
 
defp generate_content( [] ), do: ""
defp generate_content( [x] ), do: x
defp generate_content( [x1, x2] ), do: "#{x1} and #{x2}"
defp generate_content( xs ) do
[last, second_to_last | t] = Enum.reverse( xs )
with_commas = for x <- t, do: x <> ","
Enum.join(Enum.reverse([last, "and", second_to_last | with_commas]), " ")
end
end
 
Enum.each([[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]], fn list ->
IO.inspect RC.generate(list)
end)
Output:
"{}"
"{ABC}"
"{ABC and DEF}"
"{ABC, DEF, G and H}"

Erlang[edit]

 
-module( comma_quibbling ).
 
-export( [task/0] ).
 
task() -> [generate(X) || X <- [[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]]].
 
 
 
generate( List ) -> "{" ++ generate_content(List) ++ "}".
 
generate_content( [] ) -> "";
generate_content( [X] ) -> X;
generate_content( [X1, X2] ) -> string:join( [X1, "and", X2], " " );
generate_content( Xs ) ->
[Last, Second_to_last | T] = lists:reverse( Xs ),
With_commas = [X ++ "," || X <- T],
string:join(lists:reverse([Last, "and", Second_to_last | With_commas]), " ").
 
Output:
36> comma_quibbling:task().
["{}","{ABC}","{ABC and DEF}","{ABC, DEF, G and H}"]

F#[edit]

let quibble list = 
let rec inner = function
| [] -> ""
| [x] -> x
| [x;y] -> sprintf "%s and %s" x y
| h::t -> sprintf "%s, %s" h (inner t)
sprintf "{%s}" (inner list)
 
// test interactively
quibble []
quibble ["ABC"]
quibble ["ABC"; "DEF"]
quibble ["ABC"; "DEF"; "G"]
quibble ["ABC"; "DEF"; "G"; "H"]

Output from testing (in F# Interactive 3.0, Open Source version):

 
> quibble [];;
val it : string = "{}"
> quibble ["ABC"];;
val it : string = "{ABC}"
> quibble ["ABC"; "DEF"];;
val it : string = "{ABC and DEF}"
> quibble ["ABC"; "DEF"; "G"];;
val it : string = "{ABC, DEF and G}"
> quibble ["ABC"; "DEF"; "G"; "H"];;
val it : string = "{ABC, DEF, G and H}"

Forth[edit]

Forth is a set of very low level routines (WORDs) that are concatenated to make higher level WORDs. Programming Forth is like making a custom language for the problem. Arguments are passed explicitly on the hardware stack. As the program is written the language level goes higher. This demonstration uses the Forth parser to break the input stream into separate strings and a string stack to collect the input strings. The string stack can also be read as an indexed array.

Stack comments show in/out arguments after a word executes. Example: ( input -- output)

\ string primitives operate on addresses passed on the stack
: C+! ( n addr -- ) dup >R C@ + R> C! ; \ increment a byte at addr by n
: APPEND ( addr1 n addr2 -- ) 2DUP 2>R COUNT + SWAP MOVE 2R> C+! ; \ append u bytes at addr1 to addr2
: PLACE ( addr1 n addr2 -- ) 2DUP 2>R 1+ SWAP MOVE 2R> C! ; \ copy n bytes at addr to addr2
: ,' ( -- ) [CHAR] ' WORD c@ 1+ ALLOT ALIGN ; \ Parse input stream until ' and write into next
\ available memory
 
\ use ,' to create some counted string literals with mnemonic names
create '"{}"' ( -- addr) ,' "{}"' \ counted strings return the address of the 1st byte
create '"{' ( -- addr) ,' "{'
create '}"' ( -- addr) ,' }"'
create ',' ( -- addr) ,' , '
create 'and' ( -- addr) ,' and '
create "] ( -- addr) ,' "]'
 
create null$ ( -- addr) 0 ,
 
HEX
\ build a string stack/array to hold input strings
100 constant ss-width \ string stack width
variable $DEPTH \ the string stack pointer
 
create $stack ( -- addr) 20 ss-width * allot
 
DECIMAL
: new: ( -- ) 1 $DEPTH +! ; \ incr. string stack pointer
: ]stk$ ( ndx -- addr) ss-width * $stack + ; \ calc string stack element address from ndx
: TOP$ ( -- addr) $DEPTH @ ]stk$ ; \ returns address of the top string on string stack
: collapse ( -- ) $DEPTH off ; \ reset string stack pointer
 
\ used primitives to build counted string functions
: move$ ( $1 $2 -- ) >r COUNT R> PLACE ; \ copy $1 to $2
: push$ ( $ -- ) new: top$ move$ ; \ push $ onto string stack
: +$ ( $1 $2 -- top$ ) swap push$ count TOP$ APPEND top$ ; \ concatentate $2 to $1, Return result in TOP$
: LEN ( $1 -- length) c@ ; \ char fetch the first byte returns the string length
: compare$ ( $1 $2 -- -n:0:n ) count rot count compare ; \ compare is an ANS Forth word. returns 0 if $1=$2
: =$ ( $1 $2 -- flag ) compare$ 0= ;
: [""] ( -- ) null$ push$ ; \ put a null string on the string stack
 
: [" \ collects input strings onto string stack
COLLAPSE
begin
bl word dup "] =$ not \ parse input stream and terminate at "]
while
push$
repeat
drop
$DEPTH @ 0= if [""] then ; \ minimally leave a null string on the string stack
 
 
: ]stk$+ ( dest$ n -- top$) ]stk$ +$  ; \ concatenate n ]stk$ to DEST$
 
: writeln ( $ -- ) cr count type collapse ; \ print string on new line and collapse string stack
 
\ write the solution with the new words
: 1-input ( -- )
1 ]stk$ LEN 0= \ check for empty string length
if
'"{}"' writeln \ return the null string output
else
'"{' push$ \ create a new string beginning with '{'
TOP$ 1 ]stk$+ '}"' +$ writeln \ concatenate the pieces for 1 input
 
then  ;
 
: 2-inputs ( -- )
'"{' push$
TOP$ 1 ]stk$+ 'and' +$ 2 ]stk$+ '}"' +$ writeln ;
 
: 3+inputs ( -- )
$DEPTH @ dup >R \ save copy of the number of inputs on the return stack
'"{' push$
( n) 1- 1 \ loop indices for 1 to 2nd last string
DO TOP$ I ]stk$+ ',' +$ LOOP \ create all but the last 2 strings in a loop with comma
( -- top$) R@ 1- ]stk$+ 'and' +$ \ concatenate the 2nd last string to Top$ + 'and'
R> ]stk$+ '}"' +$ writeln \ use the copy of $DEPTH to get the final string index
2drop ; \ clean the parameter stack
 
: quibble ( -- )
$DEPTH @
case
1 of 1-input endof
2 of 2-inputs endof
3+inputs \ default case
endcase ;
 
 
\ interpret this test code after including the above code
[""] QUIBBLE
[" "] QUIBBLE
[" ABC "] QUIBBLE
[" ABC DEF "] QUIBBLE
[" ABC DEF GHI BROWN FOX "] QUIBBLE
 
Output:
"{}"
"{}"
"{ABC}"
"{ABC and DEF}"
"{ABC, DEF, GHI, BROWN and FOX}" ok

Works with any ANS Forth

Needs the FMS-SI (single inheritance) library code located here: http://soton.mpeforth.com/flag/fms/index.html

 include FMS-SI.f
include FMS-SILib.f
 
: foo { l | s -- }
cr ." {"
l size: dup 1- to s
0 ?do
i l at: p:
s i - 1 >
if ." , "
else s i <> if ." and " then
then
loop
." }" l <free ;
 
${ } foo
\ {}
${ ABC } foo
\ {ABC}
${ ABC DEF } foo
\ {ABC and DEF}
${ ABC DEF G } foo
\ {ABC, DEF and G}
${ ABC DEF G H } foo
\ {ABC, DEF, G and H}
${ ABC DEF G H I } foo
\ {ABC, DEF, G, H and I}
 

Fortran[edit]

The usual problem of "How long is a piece of string?" is answered in the usual way with a declaration that is "surely long enough", at least for anticipated problems. Thus, variable TEXT is declared as 666 characters long. The input statement reads up to that number of characters, or the length of the record if shorter, and supplies trailing spaces to pad the recipient variable to its full length. There is unfortunately no read feature that will create a recipient storage area that matches the size of the record being read. There is such a facility in pl/i, except that the recipient variable still has a pre-specified upper bound to its size.

Subroutine QUIBBLE doesn't have to worry about this because it works with TEXT as a parameter, whatever its size (various integer limits apply) however, it too has the same problem because it locates the start and end positions of each word, and, how many words are going to be found? So once again, the arrays are made "surely large enough" for the expected class of problem. The first stage is to locate the words (separated by any amount of "white space", which, thanks to the inability to rely on the evaluation of compound boolean expressions (of the form IF (in bounds & Array indexing)) in the "shortcut" manner, employs a battery of IF-statements. Fortran does not offer a data type "list of ..." so there is no prospect of placing the words into such an entity then inserting commas and "and" elements into the list to taste. Instead, the list of words is represented by a sequence of values in ordinary arrays.

The source style is Fortran 77, thus the use of COMMON to pass some I/O unit numbers. The plan initially was to engage in trickery with the variable FORMAT features, of the form <expression>(blah blah) to signify some number of repetitions of (blah blah), which number might be zero, but alas, although <0>X works, it proved not to work for grouped items in place of a format code. So the <..> extension had to be abandoned, and plainer F77 results.
      SUBROUTINE QUIBBLE(TEXT,OXFORDIAN)	!Punctuates a list with command and stuff.
CHARACTER*(*) TEXT !The text, delimited by spaces.
LOGICAL OXFORDIAN !Just so.
INTEGER IST(6),LST(6) !Start and stop positions.
INTEGER N,L,I !Counters.
INTEGER L1,L2 !Fingers for the scan.
INTEGER MSG !Output unit.
COMMON /IODEV/MSG !Share.
Chop the text into words.
N = 0 !No words found.
L = LEN(TEXT) !Multiple trailing spaces - no worries.
L2 = 0 !Syncopation: where the previous chomp ended.
10 L1 = L2 !Thus, where a fresh scan should follow.
11 L1 = L1 + 1 !Advance one.
IF (L1.GT.L) GO TO 20 !Finished yet?
IF (TEXT(L1:L1).LE." ") GO TO 11 !No. Skip leading spaces.
L2 = L1 !Righto, L1 is the first non-blank.
12 L2 = L2 + 1 !Scan through the non-blanks.
IF (L2.GT.L) GO TO 13 !Is it safe to look?
IF (TEXT(L2:L2).GT." ") GO TO 12 !Yes. Speed through non-blanks.
13 N = N + 1 !Righto, a word is found in TEXT(L1:L2 - 1)
IST(N) = L1 !So, recall its first character.
LST(N) = L2 - 1 !And its last.
IF (L2.LT.L) GO TO 10 !Perhaps more text follows.
Comma time...
20 WRITE (MSG,21) "{" !Start the output.
21 FORMAT (A,$) !The $, obviously, specifies that the line is not finished.
DO I = 1,N !Step through the texts, there possibly being none.
IF (I.GT.1) THEN !If there has been a predecessor, supply separators.
IF (I.LT.N) THEN !Up to the last two, it's easy.
WRITE (MSG,21) ", " !Always just a comma.
ELSE IF (OXFORDIAN) THEN !But after the penultimate item, what?
WRITE (MSG,21) ", and " !Supply the comma omitted above: a double-power separator.
ELSE !One fewer comma, with possible ambiguity arising.
WRITE (MSG,21) " and " !A single separator.
END IF !So much for the style.
END IF !Enough with the separation.
WRITE (MSG,21) TEXT(IST(I):LST(I)) !The text at last!
END DO !On to the next text.
WRITE (MSG,"('}')") !End the line, marking the end of the text.
END !That was fun.
 
PROGRAM ENCOMMA !Punctuate a list with commas.
CHARACTER*(666) TEXT !Holds the text. Easily long enough.
INTEGER KBD,MSG,INF !Now for some messing.
COMMON /IODEV/MSG,KBD !Pass the word.
KBD = 5 !Standard input.
MSG = 6 !Standard output.
INF = 10 !Suitable for a disc file.
OPEN (INF,FILE="List.txt",ACTION = "READ") !Attach one.
 
10 WRITE (MSG,11) "To insert commas into lists..." !Announce.
11 FORMAT (A) !Just the text.
12 READ (INF,11,END = 20) TEXT !Grab the text, with trailing spaces to fill out TEXT.
CALL QUIBBLE(TEXT,.FALSE.) !One way to quibble.
GO TO 12 !Try for another.
 
20 REWIND (INF) !Back to the start of the file.
WRITE (MSG,11) !Set off a bit.
WRITE (MSG,11) "Oxford style..." !Announce the proper style.
21 READ (INF,11,END = 30) TEXT !Grab the text.
CALL QUIBBLE(TEXT,.TRUE.) !The other way to quibble.
GO TO 21 !Have another try.
 
Closedown
30 END !All files are closed by exiting.

Output:

To insert commas into lists...
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Oxford style...
{}
{ABC}
{ABC, and DEF}
{ABC, DEF, G, and H}

Go[edit]

The blog mentioned code maintenence. The idea here is to make the code easy for maintainers to understand by making it correspond as directly as possible to the problem description.

package main
 
import (
"fmt"
"strings"
)
 
func q(s []string) string {
switch len(s) {
case 0:
return "{}"
case 1:
return "{" + s[0] + "}"
case 2:
return "{" + s[0] + " and " + s[1] + "}"
default:
return "{" +
strings.Join(s[:len(s)-1], ", ") +
" and " +
s[len(s)-1] +
"}"
}
}
 
func main() {
fmt.Println(q([]string{}))
fmt.Println(q([]string{"ABC"}))
fmt.Println(q([]string{"ABC", "DEF"}))
fmt.Println(q([]string{"ABC", "DEF", "G", "H"}))
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Groovy[edit]

def commaQuibbling = { it.size() < 2 ? "{${it.join(', ')}}" : "{${it[0..-2].join(', ')} and ${it[-1]}}" }

Testing:

['{}': [], '{ABC}': ['ABC'], '{ABC and DEF}': ['ABC', 'DEF'], '{ABC, DEF, G and H}': ['ABC', 'DEF', 'G', 'H']].each { expected, input ->
println "Verifying commaQuibbling($input) == $expected"
assert commaQuibbling(input) == expected
}
Output:
Verifying commaQuibbling([]) == {}
Verifying commaQuibbling([ABC]) == {ABC}
Verifying commaQuibbling([ABC, DEF]) == {ABC and DEF}
Verifying commaQuibbling([ABC, DEF, G, H]) == {ABC, DEF, G and H}

Haskell[edit]

quibble ws = "{" ++ quibbles ws ++ "}"
where quibbles [] = ""
quibbles [a] = a
quibbles [a,b] = a ++ " and " ++ b
quibbles (a:bs) = a ++ ", " ++ quibbles bs
 
main = mapM_ (putStrLn . quibble) $
[[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]] ++
(map words ["One two three four", "Me myself I", "Jack Jill", "Loner" ])
 
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}
{One, two, three and four}
{Me, myself and I}
{Jack and Jill}
{Loner}

Icon and Unicon[edit]

The following works in both languages:

procedure main()
every write(quibble([] | ["ABC"] | ["ABC","DEF"] | ["ABC","DEF","G","H"]))
end
 
procedure quibble(A)
join := s := ""
while s := pull(A)||join||s do join := if *join = 0 then " and " else ", "
return "{"||s||"}"
end

Sample run:

->cq
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}
->

J[edit]

quibLast2=: ' and ' joinstring (2 -@<. #) {. ]
withoutLast2=: ([: # _2&}.) {. ]
quibble=: '{', '}' ,~ ', ' joinstring withoutLast2 , <@quibLast2

Testing:

   Tests=: (<'');(<'ABC');('ABC';'DEF');<('ABC';'DEF';'G';'H')
quibble every Tests
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Alternative implementation:

commaand=: 1 ;@}.&, ] ,.~ 1 |.!.(<' and ') (<', ')"0
quibble=: '{','}',~ commaand

(same results)

Java[edit]

public class Quibbler {
 
public static String quibble(String[] words) {
String qText = "{";
for(int wIndex = 0; wIndex < words.length; wIndex++) {
qText += words[wIndex] + (wIndex == words.length-1 ? "" :
wIndex == words.length-2 ? " and " :
", ";
}
qText += "}";
return qText;
}
 
public static void main(String[] args) {
System.out.println(quibble(new String[]{}));
System.out.println(quibble(new String[]{"ABC"}));
System.out.println(quibble(new String[]{"ABC", "DEF"}));
System.out.println(quibble(new String[]{"ABC", "DEF", "G"}));
System.out.println(quibble(new String[]{"ABC", "DEF", "G", "H"}));
}
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

JavaScript[edit]

function quibble(words) {
return "{" +
words.slice(0, words.length-1).join(",") +
(words.length > 1 ? " and " : "") +
(words[words.length-1] || '') +
"}";
}
 
[[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]].forEach(
function(s) {
console.log(quibble(s));
}
);
Output:
{}
{ABC}
{ABC and DEF}
{ABC,DEF,G and H}

jq[edit]

Works with: jq version 1.4
def quibble:
if length == 0 then ""
elif length == 1 then .[0]
else (.[0:length-1] | join(", ")) + " and " + .[length-1]
end
| "{" + . + "}";

Example:

( [], ["ABC"],  ["ABC", "DEF"],  ["ABC", "DEF", "G", "H"]) | quibble
Output:
jq -n -r -f Comma_quibbling.jq
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}
 

Julia[edit]

quibble(words) = 
"{"* (isempty(words)  ? ""  :
length(words)==1? words[1] :
join(words[1:end-1],", ")*" and "*words[end]) *"}"
Output:
julia> quibble([])
"{}"

julia> quibble(["ABC"])
"{ABC}"

julia> quibble(["ABC","DEF"])
"{ABC and DEF}"

julia> quibble(["ABC","DEF","G","H"])
"{ABC, DEF, G and H}"

Lasso[edit]

#!/usr/bin/lasso9
 
local(collection =
array(
array,
array("ABC"),
array("ABC", "DEF"),
array("ABC", "DEF", "G", "H")
)
)
 
with words in #collection do {
if(#words -> size > 1) => {
local(last = #words -> last)
#words -> removelast
stdoutnl('{' + #words -> join(', ') + ' and ' + #last'}')
else(#words -> size == 1)
stdoutnl('{' + #words -> first + '}')
else
stdoutnl('{}')
}
 
}

Output:

{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Liberty BASIC[edit]

 
do
read in$
if in$ ="END" then wait
w =wordCount( in$)
select case w
case 0
o$ ="{}"
case 1
o$ ="{" +in$ +"}"
case 2
o$ ="{" +word$( in$, 1) +" and " +word$( in$, 2) +"}"
case else
o$ ="{"
o$ =o$ +word$( in$, 1)
for k =2 to w -1
o$ =o$ +", " +word$( in$, k)
next k
o$ =o$ +" and " +word$( in$, w) +"}"
end select
if w =1 then
print "'"; in$; "'"; " held "; w; " word. "; tab( 30); o$
else
print "'"; in$; "'"; " held "; w; " words. "; tab( 30); o$
end if
loop until 0
 
wait
 
function wordCount( IN$)
wordCount =1
for i =1 to len( IN$)
if mid$( IN$, i, 1) =" " then wordCount =wordCount +1
next i
end function
 
end
 
data "" 'No input words.
data "ABC" 'One input word.
data "ABC DEF" 'Two words.
data "ABC DEF G" 'Three words.
data "ABC DEF G H" 'Four words.
 
data "END" 'Sentinel for EOD.
 
Output:
'' held 1 word.              {}
'ABC' held 1 word.           {ABC}
'ABC DEF' held 2 words.      {ABC and DEF}
'ABC DEF G' held 3 words.    {ABC, DEF and G}
'ABC DEF G H' held 4 words.  {ABC, DEF, G and H}

[edit]

to join :delimiter :list [:result []]
output cond [
[ [empty? :list]  :result ]
[ [empty? :result] (join :delimiter butfirst :list first :list) ]
[ else (join :delimiter butfirst :list
(word :result :delimiter first :list)) ]
]
end
 
to quibble :list
local "length
make "length count :list
make "text (
ifelse [:length <= 2] [
(join "\ and\  :list)
] [
(join "\ and\ (sentence join ",\ butlast :list last :list))
])
output ifelse [empty? :text] "\{\} [(word "\{ :text "\})]
end
 
foreach [ [] [ABC] [ABC DEF] [ABC DEF G H] ] [
print quibble ?
]
 
bye
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Lua[edit]

function quibble (strTab)
local outString, join = "{"
for strNum = 1, #strTab do
if strNum == #strTab then
join = ""
elseif strNum == #strTab - 1 then
join = " and "
else
join = ", "
end
outString = outString .. strTab[strNum] .. join
end
return outString .. '}'
end
 
local testCases = {
{},
{"ABC"},
{"ABC", "DEF"},
{"ABC", "DEF", "G", "H"}
}
for _, input in pairs(testCases) do print(quibble(input)) end
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Maple[edit]

Quibble := proc( los )
uses StringTools;
Fence( proc()
if los = [] then
""
elif numelems( los ) = 1 then
los[ 1 ]
else
cat( Join( los[ 1 .. -2 ], ", " ), " and ", los[ -1 ] )
end if
end(), "{", "}" )
end proc:

Check it on the required inputs:

> Quibble([]);
"{}"
 
> Quibble( [ "ABC" ] );
"{ABC}"
 
> Quibble( [ "ABC", "DEF" ] );
"{ABC and DEF}"
 
> Quibble( ["ABC", "DEF", "G", "H"] );
"{ABC, DEF, G and H}"
 

Mathematica / Wolfram Language[edit]

quibble[words___] :=
ToString@{StringJoin@@
Replace[Riffle[{words}, ", "],
{most__, ", ", last_} -> {most, " and ", last}]}
Output:
In[2]:= quibble[]
Out[2]= {}

In[3]:= quibble["ABC"]
Out[3]= {ABC}

In[4]:= quibble["ABC","DEF"]
Out[4]= {ABC and DEF}

In[5]:= quibble["ABC","DEF","G","H"]
Out[5]= {ABC, DEF, G and H}

MAXScript[edit]

 
fn separate words: =
(
if words == unsupplied or words == undefined or classof words != array then return "{}"
else
(
local toReturn = "{"
local pos = 1
while pos <= words.count do
(
if pos == 1 then (append toReturn words[pos]; pos+=1)
else
(
if pos <= words.count-1 then (append toReturn (", "+words[pos]); pos+=1)
else
(
append toReturn (" and " + words[pos])
pos +=1
)
)
)
return (toReturn+"}")
)
)
 

Output:

 
separate words:#()
"{}"
separate words:#("ABC")
"{ABC}"
separate words:#("ABC","DEF")
"{ABC and DEF}"
separate words:#("ABC","DEF","G","H")
"{ABC, DEF, G and H}"
 

NetRexx[edit]

/* NetRexx */
options replace format comments java crossref symbols nobinary
 
runSample(arg)
return
 
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
method quibble(arg) public static
parse arg '[' lst ']'
lst = lst.changestr('"', '').space(1)
lc = lst.lastpos(',')
if lc > 0 then
lst = lst.insert('and', lc).overlay(' ', lc)
return '{'lst'}'
 
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
method runSample(arg) private static
lists = ['[]', - -- {}
'["ABC"]', - -- {ABC}
'["ABC", "DEF"]', - -- {ABC and DEF}
'["ABC", "DEF", "G", "H"]'] -- {ABC, DEF, G and H}
loop lst over lists
say lst.right(30) ':' quibble(lst)
end lst
return
 
Output:
                            [] : {}
                       ["ABC"] : {ABC}
                ["ABC", "DEF"] : {ABC and DEF}
      ["ABC", "DEF", "G", "H"] : {ABC, DEF, G and H}

Nim[edit]

proc commaQuibble(s): string =
result = ""
for i, c in s:
if i > 0: result.add (if i < s.high: ", " else: " and ")
result.add c
result = "{" & result & "}"
 
var s = @[@[], @["ABC"], @["ABC", "DEF"], @["ABC", "DEF", "G", "H"]]
for i in s:
echo commaQuibble(i)

Oberon-2[edit]

Works with: oo2c
 
MODULE CommaQuibbling;
IMPORT
NPCT:Args,
Strings,
Out;
 
VAR
str: ARRAY 256 OF CHAR;
 
PROCEDURE Do(VAR s: ARRAY OF CHAR);
VAR
aux: ARRAY 128 OF CHAR;
i,params: LONGINT;
BEGIN
params := Args.Number() - 1;
CASE params OF
0:
COPY("{}",s)
|1:
Args.At(1,aux);
Strings.Append("{",s);
Strings.Append(aux,s);
Strings.Append("}",s);
ELSE
Strings.Append("{",s);
FOR i := 1 TO params - 1 DO
Args.At(i,aux);
Strings.Append(aux,s);
IF i # params - 1 THEN
Strings.Append(", ",s)
ELSE
Strings.Append(" and ", s)
END
END;
Args.At(params,aux);
Strings.Append(aux,s);
Strings.Append("}",s)
END;
 
END Do;
 
BEGIN
Do(str);
Out.String(":> ");Out.String(str);Out.Ln
END CommaQuibbling.
 
Output:
$ bin/CommaQuibbling 
:> {}
$ bin/CommaQuibbling ABC
:> {ABC}
$ bin/CommaQuibbling ABC DEF
:> {ABC and DEF}
$ bin/CommaQuibbling ABC DEF G
:> {ABC, DEF and G}
$ bin/CommaQuibbling ABC DEF G H
:> {ABC, DEF, G and H}

Oforth[edit]

: quibbing(l) -- string
| i s |
StringBuffer new "{" <<
l size dup 1- ->s loop: i [
l at(i) <<
i s < ifTrue: [ ", " << continue ]
i s == ifTrue: [ " and " << ]
]
"}" << dup freeze ;
Output:
[ [], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"] ] map(#quibbing) .
[{}, {ABC}, {ABC and DEF}, {ABC, DEF, G and H}]

PARI/GP[edit]

comma(v)={
if(#v==0, return("{}"));
if(#v==1, return(Str("{"v[1]"}")));
my(s=Str("{",v[1]));
for(i=2,#v-1,s=Str(s,", ",v[i]));
Str(s," and ",v[#v],"}")
};
comma([])
comma(["ABC"])
comma(["ABC", "DEF"])
comma(["ABC", "DEF", "G", "H"])

Output:

%1 = "{}"
%2 = "{ABC}"
%3 = "{ABC and DEF}"
%4 = "{ABC, DEF, G and H}"

Perl[edit]

Translation of: Perl 6
sub comma_quibbling(@) {
return "{$_}" for
@_ < 2 ? "@_" :
join(', ', @_[0..@_-2]) . ' and ' . $_[-1];
}
 
print comma_quibbling(@$_), "\n" for
[], [qw(ABC)], [qw(ABC DEF)], [qw(ABC DEF G H)];
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Perl 5.01 version and other approach:

use 5.01;
sub comma_quibbling{
my $last = pop // '';
return '{'. (@_ ? (join ', ', @_).' and '.$last : $last).'}';
}
 
say for map {comma_quibbling(@$_)}
[], [qw(ABC)], [qw(ABC DEF)], [qw(ABC DEF G H)];
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Perl 6[edit]

sub comma-quibbling(@A) {
<{ }>.join: @A < 2 ?? @A !! "@A[0..*-2].join(', ') and @A[*-1]";
}
 
say comma-quibbling($_) for
[], [<ABC>], [<ABC DEF>], [<ABC DEF G H>];
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

PHP[edit]

<?php
 
function quibble($arr){
 
$words = count($arr);
 
if($words == 0){
return '{}';
}elseif($words == 1){
return '{'.$arr[0].'}';
}elseif($words == 2){
return '{'.$arr[0].' and '.$arr[1].'}';
}else{
return '{'.implode(', ', array_splice($arr, 0, -1) ). ' and '.$arr[0].'}';
}
 
}
 
 
$tests = [
[],
["ABC"],
["ABC", "DEF"],
["ABC", "DEF", "G", "H"]
];
 
foreach ($tests as $test) {
echo quibble($test) . PHP_EOL;
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

PicoLisp[edit]

(for L '([] ["ABC"] ["ABC", "DEF"] ["ABC", "DEF", "G", "H"])
(let H (head -1 L)
(prinl
"{"
(glue ", " H)
(and H " and ")
(last L)
"}" ) ) )

Output:

{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}


PL/I[edit]

*process or(!);
quib: Proc Options(main);
/*********************************************************************
* 06.10.2013 Walter Pachl
*********************************************************************/

put Edit*process or(!);
quib: Proc Options(main);
/*********************************************************************
* 06.10.2013 Walter Pachl
* 07.10.2013 -"- change "Oxford comma" to and
*********************************************************************/

put Edit(quibbling(''))(Skip,a);
put Edit(quibbling('ABC'))(Skip,a);
put Edit(quibbling('ABC DEF'))(Skip,a);
put Edit(quibbling('ABC DEF G H'))(Skip,a);
return;
 
quibbling: proc(s) Returns(Char(100) Var);
Dcl s Char(*);
Dcl result Char(100) Var Init('');
Dcl word(10) Char(100) Var;
Dcl (wi,p) Bin Fixed(31);
If s='' Then result='';
Else Do;
Do wi=1 By 1 While(s^='');
p=index(s,' ');
if p=0 Then Do;
word(wi)=s;
s='';
End;
Else Do;
word(wi)=left(s,p-1);
s=substr(s,p+1);
End;
end;
wn=wi-1;
result=word(1);
Do i=2 To wn-1;
result=result!!', '!!word(i);
End;
If wn>1 Then
result=result!!' and '!!word(wn);
End;
Return('{'!!result!!'}');
End;
End;
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}      

PowerShell[edit]

 
function Out-Quibble
{
[OutputType([string])]
Param
(
# Zero or more strings.
[Parameter(Mandatory=$false, Position=0)]
[AllowEmptyString()]
[string[]]
$Text = ""
)
 
# If not null or empty...
if ($Text)
{
# Remove empty strings from the array.
$text = "$Text".Split(" ", [StringSplitOptions]::RemoveEmptyEntries)
}
else
{
return "{}"
}
 
# Build a format string.
$outStr = ""
for ($i = 0; $i -lt $text.Count; $i++)
{
$outStr += "{$i}, "
}
$outStr = $outStr.TrimEnd(", ")
 
# If more than one word, insert " and" at last comma position.
if ($text.Count -gt 1)
{
$cIndex = $outStr.LastIndexOf(",")
$outStr = $outStr.Remove($cIndex,1).Insert($cIndex," and")
}
 
# Output the formatted string.
"{" + $outStr -f $text + "}"
}
 
 
Out-Quibble
Out-Quibble "ABC"
Out-Quibble "ABC", "DEF"
Out-Quibble "ABC", "DEF", "G", "H"
 
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

What it might look like when working with a file:

 
$file = @'
 
ABC
ABC, DEF
ABC, DEF, G, H
'
@ -split [Environment]::NewLine
 
foreach ($line in $file)
{
Out-Quibble -Text ($line -split ", ")
}
 
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Prolog[edit]

Works with: SWI-Prolog version 7.1
words_series(Words, Bracketed) :-
words_serialized(Words, Serialized),
atomics_to_string(["{",Serialized,"}"], Bracketed).
 
words_serialized([], "").
words_serialized([Word], Word) :- !.
words_serialized(Words, Serialized) :-
append(Rest, [Last], Words), %% Splits the list of *Words* into the *Last* word and the *Rest*
atomics_to_string(Rest, ", ", WithCommas),
atomics_to_string([WithCommas, " and ", Last], Serialized).
 
 
 
test :-
forall( member(Words, [[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]]),
( words_series(Words, Series),
format('~w ~15|=> ~w~n', [Words, Series]))
).
Output:
?- test.
[] => {}
[ABC] => {ABC}
[ABC,DEF] => {ABC and DEF}
[ABC,DEF,G,H] => {ABC, DEF, G and H}
true.

Python[edit]

Python: Replace() whilst reversed[edit]

replace(..) can only replace the first X occurrences not the last hence the replace is done on the reverse of the intermediate string then reversed back.

>>> def strcat(sequence):
return '{%s}' % ', '.join(sequence)[::-1].replace(',', 'dna ', 1)[::-1]
 
>>> for seq in ([], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]):
print('Input: %-24r -> Output: %r' % (seq, strcat(seq)))
 
 
Input: [] -> Output: '{}'
Input: ['ABC'] -> Output: '{ABC}'
Input: ['ABC', 'DEF'] -> Output: '{ABC and DEF}'
Input: ['ABC', 'DEF', 'G', 'H'] -> Output: '{ABC, DEF, G and H}'
>>>

Python: Counted replacement[edit]

(Possible)
Translation of: Tcl

replace() will replace nothing if the count of items to replace is zero, (and negative integer counts act to replace all occurrences). This combines with the length of the input sequence to allow this to work:

def commaQuibble(s):
return '{%s}' % ' and '.join(s).replace(' and ', ', ', len(s) - 2)
 
for seq in ([], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]):
print('Input: %-24r -> Output: %r' % (seq, commaQuibble(seq)))
Output:
Input: []                       -> Output: '{}'
Input: ['ABC']                  -> Output: '{ABC}'
Input: ['ABC', 'DEF']           -> Output: '{ABC and DEF}'
Input: ['ABC', 'DEF', 'G', 'H'] -> Output: '{ABC, DEF, G and H}'

Python: Functional[edit]

>>> def quibble(s):
return ('{' +
(', '.join(s[:-1]) + ' and ' if len(s) > 1 else '') +
(s[-1] if s else '') +
'}')
 
>>> for seq in ([], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]):
print('Input: %-24r -> Output: %r' % (seq, quibble(seq)))
 
 
Input: [] -> Output: '{}'
Input: ['ABC'] -> Output: '{ABC}'
Input: ['ABC', 'DEF'] -> Output: '{ABC and DEF}'
Input: ['ABC', 'DEF', 'G', 'H'] -> Output: '{ABC, DEF, G and H}'
>>>

Racket[edit]

(define (quibbling words)
(define (sub-quibbling words)
(match words
['() ""]
[(list a) a]
[(list a b) (format "~a and ~a" a b)]
[(list a b ___) (format "~a, ~a" a (sub-quibbling b))]))
(format "{~a}" (sub-quibbling words)))
 
(for ((input '([] ["ABC"] ["ABC" "DEF"] ["ABC" "DEF" "G" "H"])))
(printf "~s\t->\t~a~%" input (quibbling input)))
Output:
()	->	{}
("ABC")	->	{ABC}
("ABC" "DEF")	->	{ABC and DEF}
("ABC" "DEF" "G" "H")	->	{ABC, DEF, G and H}

REBOL[edit]

Straightforward implementation[edit]

rebol []
 
comma-quibbling: func [block] [
rejoin [
"^{"
 
to-string use [s] [
s: copy block
s: next s
forskip s 2 [insert s either tail? next s [" and "] [", "]]
s: head s
]
 
"^}"
]
]
 
foreach t [[] [ABC] [ABC DEF] [ABC DEF G H]] [print comma-quibbling t]
 
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Alternative (more efficient) version with oxford comma switch[edit]

rebol []
 
; builds string instead of using an intermediate block
 
comma-quibbling: func [block /oxford /local s length] [
length: length? block
rejoin [
"^{"
 
either length < 2 [to-string block] [
s: to-string block/1
for n 2 (length - 1) 1 [repend s [", " pick block n]]
if all [oxford (length > 2)] [append s ","]
repend s [" and " last block]
]
 
"^}"
]
]
 
test: [[] [ABC] [ABC DEF] [ABC DEF G H]]
foreach t test [print comma-quibbling t]
print "Now with Oxford comma"
foreach t test [print comma-quibbling/oxford t]
 
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}
Now with Oxford comma
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G, and H}

REXX[edit]

version 1:[edit]

say quibbling('')
say quibbling('ABC')
say quibbling('ABC DEF')
say quibbling('ABC DEF G H')
exit
 
quibbling: procedure
parse arg list
Select
When list='' Then result=''
When words(list)=1 then result=word(list,1)
Otherwise result=translate(strip(subword(list,1,words(list)-1)),',',' '),
'and' word(list,words(list))
End
Return '{'result'}'
Output:
{}
{ABC}
{ABC and DEF}
{ABC,DEF,G and H}

version 2:[edit]

say quibbling('')
say quibbling('ABC')
say quibbling('ABC DEF')
say quibbling('ABC DEF G H')
exit
quibbling:
parse arg list
If list='' Then result=''
Else Do
Do wi=1 By 1 while list<>''
Parse Var list word.wi ' ' list
End
wn=wi-1
result=word.1
Do wi=2 To wn-1
result=result', 'word.wi
End
If wn>1 Then
result=result 'and' word.wn
End
Return '{'result'}'
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}    

version 3:[edit]

Translation of: NetRexx
/* Rexx */
 
i_ = 0
i_ = i_ + 1; lists.0 = i_; lists.i_ = '[]'
i_ = i_ + 1; lists.0 = i_; lists.i_ = '["ABC"]'
i_ = i_ + 1; lists.0 = i_; lists.i_ = '["ABC", ''DEF'']'
i_ = i_ + 1; lists.0 = i_; lists.i_ = '[ABC, DEF, G, H]'
 
say
do i_ = 1 to lists.0
list = lists.i_
say right(list, 30) ':' quibbling03(list)
end i_
exit
 
quibbling03:
procedure
parse arg '[' lst ']'
lst = changestr('"', changestr("'", lst, ''), '') /* remove double & single quotes */
lc = lastpos(',', lst)
if lc > 0 then
lst = overlay(' ', insert('and', lst, lc), lc)
lst = space(lst, 1) -- remove extra spaces
return '{'lst'}'
Output:
                            [] : {}
                       ["ABC"] : {ABC}
                ["ABC", 'DEF'] : {ABC and DEF}
              [ABC, DEF, G, H] : {ABC, DEF, G and H}

Ruby[edit]

Translation of: Perl 6
def comma_quibbling(a)
%w<{ }>.join(a.length < 2 ? a.first :
"#{a[0..-2].join(', ')} and #{a[-1]}")
end
 
[[], %w<ABC>, %w<ABC DEF>, %w<ABC DEF G H>].each do |a|
puts comma_quibbling(a)
end
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}


Run BASIC[edit]

wrds$ = "[]
[""ABC""]
[""ABC"", ""DEF""]
[""ABC"", ""DEF"", ""G"", ""H""]
"
while word$(wrds$,j+1,chr$(13)) <> ""
a$ = word$(wrds$,j+1,chr$(13))
print a$;" ==> ";
a$ = "{"+mid$(a$,2,len(a$)-2)+"}"
j = j + 1
for i = len(a$) to 1 step -1
if mid$(a$,i,1) = "," then
a$ = left$(a$,i-1) + " and " + mid$(a$,i+2)
exit for
end if
next i
print a$
WEND
Output:
[] ==> {}
["ABC"] ==> {"ABC"}
["ABC", "DEF"] ==> {"ABC" and  "DEF"}
["ABC", "DEF", "G", "H"] ==> {"ABC", "DEF", "G" and  "H"}

Rust[edit]

 
fn quibble(seq: &[&str]) -> String {
match seq.len() {
0 => "{}".to_string(),
1 => format!("{{{}}}", seq[0]),
_ => {
format!("{{{} and {}}}",
seq[..seq.len() - 1].join(", "),
seq.last().unwrap())
}
}
}
 
fn main() {
println!("{}", quibble(&[]));
println!("{}", quibble(&["ABC"]));
println!("{}", quibble(&["ABC", "DEF"]));
println!("{}", quibble(&["ABC", "DEF", "G", "H"]));
}
 
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Scala[edit]

def quibble( s:List[String] ) = s match {
case m if m.isEmpty => "{}"
case m if m.length < 3 => m.mkString("{", " and ", "}")
case m => "{" + m.init.mkString(", ") + " and " + m.last + "}"
}
 
// A little test...
{
println( quibble( List() ) )
println( quibble( List("ABC") ) )
println( quibble( List("ABC","DEF") ) )
println( quibble( List("ABC","DEF","G","H") ) )
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Seed7[edit]

$ include "seed7_05.s7i";
 
const func string: quibble (in array string: input) is func
result
var string: quibble is "{";
begin
case length(input) of
when {0}: quibble &:= "}";
when {1}: quibble &:= input[1] & "}";
otherwise: quibble &:= join(input[.. pred(length(input))], ", ") &
" and " & input[length(input)] & "}";
end case;
end func;
 
const proc: main is func
begin
writeln(quibble(0 times ""));
writeln(quibble([] ("ABC")));
writeln(quibble([] ("ABC", "DEF")));
writeln(quibble([] ("ABC", "DEF", "G", "H")));
end func;
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Sidef[edit]

func comma_quibbling(words) {
'{' + ([words.ft(0, -2).join(', ')]-[''] + [words.last] -> join(' and ')) + '}';
}
 
[<>, <ABC>, <ABC DEF>, <ABC DEF G H>].each { |w|
say comma_quibbling(w);
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Standard ML[edit]

local
fun quib [] = ""
| quib [x] = x
| quib [x0,x1] = x0 ^ " and " ^ x1
| quib (x::xs) = x ^ ", " ^ quib xs
in
fun quibble xs = "{" ^ quib xs ^ "}"
end
 
(* Tests: *)
val t_quibble_0 = quibble [] = "{}"
val t_quibble_1 = quibble ["ABC"] = "{ABC}"
val t_quibble_2 = quibble ["ABC", "DEF"] = "{ABC and DEF}"
val t_quibble_3 = quibble ["ABC", "DEF", "G", "H"] = "{ABC, DEF, G and H}"
 

Swift[edit]

let inputs = [[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]]
 
func quibbling(var words:[String]) {
if words.count == 0 {
println("{}")
} else if words.count == 1 {
println("{\(words[0])}")
} else if words.count == 2 {
println("{\(words[0]) and \(words[1])}")
} else {
var output = "{"
while words.count != 2 {
output += words.removeAtIndex(0) + ", "
}
output += "\(words.removeAtIndex(0)) and \(words.removeAtIndex(0))}"
 
println(output)
}
}
 
for word in inputs {
quibbling(word)
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Tcl[edit]

proc commaQuibble {lst} {
return \{[join [lreplace $lst end-1 end [join [lrange $lst end-1 end] " and "]] ", "]\}
}
 
foreach input { {} {"ABC"} {"ABC" "DEF"} {"ABC" "DEF" "G" "H"} } {
puts [commaQuibble $input]
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

UNIX Shell[edit]

Translation of: AWK
quibble() {
# Here awk(1) is easier than sed(1).
awk 'BEGIN {
for (i = 1; i < ARGC - 2; i++) s = s ARGV[i] ", "
i = ARGC - 2; if (i > 0) s = s ARGV[i] " and "
i = ARGC - 1; if (i > 0) s = s ARGV[i]
printf "{%s}\n", s
exit 0
}'
"$@"
}
 
quibble
quibble ABC
quibble ABC DEF
quibble ABC DEF G H
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

VBScript[edit]

Function Quibble(s)
arr = Split(s,",")
If s = "" Then
Quibble = "{}"
ElseIf UBound(arr) = 0 Then
Quibble = "{" & arr(0) & "}"
Else
Quibble = "{"
For i = 0 To UBound(arr)
If i = UBound(arr) - 1 Then
Quibble = Quibble & arr(i) & " and " & arr(i + 1) & "}"
Exit For
Else
Quibble = Quibble & arr(i) & ", "
End If
Next
End If
End Function
 
WScript.StdOut.Write Quibble("")
WScript.StdOut.WriteLine
WScript.StdOut.Write Quibble("ABC")
WScript.StdOut.WriteLine
WScript.StdOut.Write Quibble("ABC,DEF")
WScript.StdOut.WriteLine
WScript.StdOut.Write Quibble("ABC,DEF,G,H")
WScript.StdOut.WriteLine
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

XPL0[edit]

include c:\cxpl\codes;
 
proc Quibble(N, S);
int N, S;
int I;
[ChOut(0, ^{);
for I:= 0 to N-1 do
[Text(0, S(I));
if I<N-2 then Text(0, ", ");
if I=N-2 then Text(0, " and ");
];
ChOut(0, ^});
];
 
int I;
for I:= 0 to 4 do
if I#3 then [Quibble(I, ["ABC", "DEF", "G", "H"]); CrLf(0)]
 
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

zkl[edit]

This is a cheese ball solution that replies on no commas in the inputs

fcn quib(list){ text:=("{"+list.toString(*)[2,-1]+"}").replace("\"","");
if(list.len()<2) text;
else{
z:=(text=text.replace(",",", ")).rfind(",");
String(text[0,z]," and ",text[z+2,*])
}
}

List.toString("*") converts List(1,2,3) to "L(1,2,3)" with all elements; without the *, long lists are shortened to L(1,2,3,...)

Output:
quib(List)     //-->"{}"
quib(L("ABC")) //-->"{ABC}"
quib(L("ABC", "DEF")) //-->"{ABC and DEF}"
quib(L("ABC", "DEF", "G", "H")) //-->"{ABC, DEF, G and H}"