20
\$\begingroup\$

Article Spinning with Spintax

Article spinning is a search engine optimization technique by which search engine optimizers post a unique version of relevant content on article directories, web 2.0 sites, or other sources for backlinks.

It is occasionally used by website authors on their own sites but usually avoided because the quality of spun content will be lower than hand written content. Spinning works by rewriting existing articles, or parts of articles, and replacing specific words, phrases, sentences, or even entire paragraphs with any number of alternate versions to provide a slightly different variation with each spin.

This process can be completely automated or rewritten manually. Your job today will be to create a program or function which interprets a string containing Spintax, the syntax by which a program would read the different options available. Below is an example string that uses Spintax:

{Hello|Hi there|Bonjour}, user! {What's up?|How are you?|Feeling alright?|I caught a fish!}

As you can see, sections of the string that are to be 'spun' are enclosed in curly braces, where individual options are split by the | character. Here are some example sentences this could produce.

Hi there, user! What's up?
Hello, user! How are you?
Bonjour, user! Feeling alright?
Hello, user! What's up?
Hi there, user! I caught a fish!

Your Job

Your mission, should you choose to accept it, is to create a program or function which takes an input string that may or may not contain Spintax, then print 5 versions of the string, randomly selecting one of the choices from each block of options. You can make a few assumptions:

  1. You will not need to handle nested Spintax ({This|is {an|one}|example}).
  2. The |,{ and } characters will not occur anywhere else other than within a Spintax block.
  3. You do not need to check if the 5 output sentences are distinct, as long as the code selects options using your language's random number function.
  4. You may assume the input ends with a newline.

Rules

  1. No standard loopholes.

Tests

1. {A|The|One} {quick|magnificent|charming|lucky|fast|super|cool} {gold|tan|yellow|white|brown|silver} {beaver|packrat|cat|lion|tiger|fox|dog|wolf} {consumed|ate|pilfered} my {pastry|strudel|cake}.
-------------------------------------------
One cool brown wolf consumed my pastry.
...(3 more here)...
The charming yellow beaver ate my cake.
-------------------------------------------
2. This sentence is valid, and happens to have no Spintax!
-------------------------------------------
This sentence is valid, and happens to have no Spintax!
...(3 more here)...
This sentence is valid, and happens to have no Spintax!
-------------------------------------------
3. {beg|get|ton|sun|bun|can|top|big}{2|3|4|5}{A|B|C|D|E|F|G}{2|3|4|5|6|7}{at|as|be|ex|hi|}{!|#|%}
-------------------------------------------
top2F2hi%
...(3 more here)...
big3E4ex#

A fourth test can be found here.


This is , so fewest number of bytes wins.

\$\endgroup\$
3
  • 2
    \$\begingroup\$ Now, with a slight change... mad libs! \$\endgroup\$
    – Geobits
    Jun 19, 2015 at 20:11
  • \$\begingroup\$ What types of characters can we expect in the input? \$\endgroup\$
    – Sp3000
    Jun 20, 2015 at 6:10
  • \$\begingroup\$ @Sp3000 Everything from ASCII 32-127. The only place where {,|,} is going to be found in the input string is where there is Spintax. \$\endgroup\$
    – Kade
    Jun 20, 2015 at 16:17

15 Answers 15

8
\$\begingroup\$

Pyth, 18

V5sOMcR\|scR\{cz\}

Try it online here

Explanation:

V5sOMcR\|scR\{cz\}                     : z = input()
V5                                     : do this 5 times
              cz\}                     : split z on the } character
          cR\{                         : split each element of the resulting list on {
         s                             : join the list of lists back into a list of strings
     cR\|                              : split these strings on the | character
   OM                                  : Choose a random element from each inner list
  s                                    : join those choices, and print them
\$\endgroup\$
1
  • 1
    \$\begingroup\$ My god, that was fast! \$\endgroup\$
    – Kade
    Jun 19, 2015 at 20:09
7
\$\begingroup\$

Ruby, 46

With command-line flag -p, run

($_*=5).gsub!(/{(.+?)}/){$1.split(?|).sample}

Read in a string with a trailing newline. Concatenate it to itself 5 times, mutate it in place, and output the result. /{(.+?)}/ is a regular expression with a lazy quantifier: without the ?, it would match the leftmost { to the rightmost } instead of the nearest one. $1 is a magic variable referring to the first capture group in each match, while the ?| literal refers to the | character. sample is an array method returning a random element.

\$\endgroup\$
6
\$\begingroup\$

CJam, 25 22 19 18 bytes

q5*'}/{'{/~'|/mR}/

This code requires the input to contain a trailing newline.

Try it online in the CJam interpreter.

How it works

q     e# Read from STDIN.
5*    e# Repeat the input five times.
'}/   e# Split at right curly brackets.
{     e# For each chunk:
  '{/ e#   Split at left curly brackets.
  ~   e#   Dump both chunks on the stack.
  '|/ e#   Split the right chunk at vertical bars.
  mR  e#   Select a chunk, pseudo-randomly.
}/    e#

In each iteration, two chunks (one constant, one variable) are left on the stack and will be printed when the program exits.

\$\endgroup\$
6
\$\begingroup\$

Python 3, 110 bytes

import re,random
exec('print("".join(random.choice(s.split("|"))for s in re.split("{(.*?)}",%r)));'%input()*5)

Regex splitting, like the others.

Python 3, 121 116 114 bytes

from random import*
exec(("print(%r[:-1]);"%(input()+'"')).translate({123:"'+choice(['",124:"','",125:"'])+'"})*5)

This one performs the appropriate replacements then evaluates it. If it wasn't for escaping backslashes and apostrophes, this would have turned out shorter.

For example, suppose we have the Spintax

I {like|hate} {Python|C}.

The .translate performs a few conversions to give:

print('I '+choice(['like','hate'])+' '+choice(['Python','C'])+'.')
\$\endgroup\$
4
\$\begingroup\$

Perl, 50

$_=$_ x5;s/{(.*?)}/@r=split"\\|",$1;$r[rand@r]/ge

49 characters +1 for -p.

It works by first setting $_ to the 5 sentences. Then it does a global search and replace, where we search for each { | | } group. It splits the group on |, and the picks a random element for the replacement.

Run with:

echo '{A|The|One} {quick|magnificent|charming|lucky|fast|super|cool} {gold|tan|yellow|white|brown|silver} {beaver|packrat|cat|lion|tiger|fox|dog|wolf} {consumed|ate|pilfered} my {pastry|strudel|cake}.' | perl -pe'$_=$_ x5;s/{(.*?)}/@r=split"\\|",$1;$r[rand@r]/ge'
\$\endgroup\$
1
  • \$\begingroup\$ $_ x=5 saves a few bytes. \$\endgroup\$
    – Dennis
    Jun 19, 2015 at 21:08
4
\$\begingroup\$

Ostrich v0.7.0, 27 characters

{`{.*?}`{);(;"|/.,R*F=}X}5*

Explanation:

{...}5*  repeat 5 times
  `{.*?}`  regex to match "spintax" sections
  {...}X   replace with...
    );(;     remove first and last characters (the curly brackets)
    "|/      split on |
    .,R*     multiply length of that array by a random number
    F=       take the floor of that and get the nth element of the array

(note: the 0.7.0 release was made after this question was posted, but the answer is still valid as all the commits that made up that release were all pushed before this questions was posted.)

\$\endgroup\$
4
\$\begingroup\$

Pip, 25 22 20 bytes

Late to the party, but here's mine. Takes input as a command-line argument, which may need to be quoted.

L5P_@RR#_Ma^`}|{`^'|

Explanation:

L5                    Loop 5 times:
          a^`}|{`     Split cmdline arg on curly braces using regex
                 ^'|  Split each item of the result on pipe (if there isn't one,
                        the result is a single-item list)
         M            Map this function to each item a in the result:
   _@RR#_             Calculate a random number between 0 and len(item)-1; use it to
                        index into item
  P                   Print the resulting list, concatenating elements together

17 bytes (but not a valid submission)

L5PRC_Ma^`}|{`^'|

Requires the latest version of Pip, updated after this question was posted. The RC (random choice) operator was planned for some time, but I never got around to implementing it until just now. :^(

Learn more about Pip

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Seriously? You write you own language to win golf contests? \$\endgroup\$
    – Chloe
    Jun 21, 2015 at 4:47
  • 3
    \$\begingroup\$ @Chloe: CJam, GS2, Ostrich, pyg, Pyth, Retina and rs are all golfing languages invented by the users of this site. \$\endgroup\$
    – Dennis
    Jun 21, 2015 at 13:46
3
\$\begingroup\$

JavaScript ES6, 86 84 bytes

f=s=>s.repeat(5).replace(/{(.+?)}/g,(_,e)=>(p=e.split`|`)[Math.random()*p.length|0])

This expects the input to have a trailing newline. It starts by repeating the input 5 times, and then replacing each Spintax string with a random word from it, which is obtained by splitting out the | characters and then choosing a random number between 0 and the length of the resulting array minus 1. In this case, |0 is just a shorter way of doing Math.floor. Thanks to vihan1086 for reminding me about tagged template strings.

The Stack Snippet below contains ungolfed and easily testable code.

f=function(s){
  return s.repeat(5).replace(/{(.+?)}/g,function(_,e){
    return (p=e.split('|'))[Math.random()*p.length|0]
  })
}

run=function(){document.getElementById('output').innerHTML=f(document.getElementById('input').value+'\n')};document.getElementById('run').onclick=run;run()
<input type="text" id="input" value="{A|The|One} {quick|magnificent|charming|lucky|fast|super|cool} {gold|tan|yellow|white|brown|silver} {beaver|packrat|cat|lion|tiger|fox|dog|wolf} {consumed|ate|pilfered} my {pastry|strudel|cake}." style="width: 400px;" /><button id="run">Run</button><br />
<pre id="output"></pre>

\$\endgroup\$
2
  • \$\begingroup\$ |0 is equivalent to Math.floor not Math.round. Although that is what you want as Math.round would provide uneven distribution. \$\endgroup\$ Jun 20, 2015 at 13:27
  • \$\begingroup\$ @GeorgeReith You're right, I meant to put floor but accidentally put round. Thanks \$\endgroup\$ Jun 20, 2015 at 13:32
2
\$\begingroup\$

Perl, 82 bytes

while($o=<>){for(0..4){$_=$o;s/{(.*?)}/@x=split\/\|\/,$1 and$x[rand@x]/ge;print;}}
\$\endgroup\$
2
\$\begingroup\$

Python 2, 139 bytes

Added two bytes for quotes around input string. If these are not needed let me know.

Try it here

import re,random
s=input()
exec"print''.join(t*(t!=''and(t[0]!='{'))or random.choice(t[1:].split('|'))for t in re.split('({.*?)}',s));"*5
\$\endgroup\$
0
1
\$\begingroup\$

Java, 243 215 242 234 bytes

int i,k;void f(String s){String a,b[],c=s;for(;k++<5;System.out.println(c),c=s)while((i=c.indexOf("{"))>=0){a=c.substring(i,c.indexOf("}")+1);b=a.replaceAll("\\{|\\}","").split("\\|");c=c.replace(a,b[(int)(Math.random()*b.length)]);}}

Finds strings within braces {} and replaces them with a random element from the array of strings created by splitting by the pipe character. (I realized a little too late that five sentences had to be printed :P)

\$\endgroup\$
1
\$\begingroup\$

Bash: 144 138 characters

IFS={} read -ap
w()(for o in "${p[@]}";{
[[ $o =~ \| ]]&&{
IFS=\| read -aa<<<"$o"
o=${a[RANDOM%${#a[@]}]}
}
echo -n "$o"
}
echo)
w
w
w
w
w

Sample run:

bash-4.3$ bash spintax.sh <<< "Look {ma'|daddy|mr. president}! No {bin|core|doc|find|mail}utils tools nor {Awk|Sed|jq|XML Starlet}!"
Look ma'! No docutils tools nor Awk!
Look daddy! No binutils tools nor XML Starlet!
Look mr. president! No docutils tools nor XML Starlet!
Look ma'! No docutils tools nor Awk!
Look mr. president! No binutils tools nor Sed!
\$\endgroup\$
2
  • 1
    \$\begingroup\$ You can save a few bytes by using w()(...) instead of w(){...} (allows to eliminate whitespace), printf instead of echo -n and curly brackets instead of do and done. \$\endgroup\$
    – Dennis
    Jun 21, 2015 at 1:16
  • \$\begingroup\$ Thank you, @Dennis. Again. (Sadly is not the first time I forget these tricks.) Regarding printf, that would fail on input string containing “%”, unless I misunderstood something. \$\endgroup\$
    – manatwork
    Jun 21, 2015 at 10:27
1
\$\begingroup\$

Javascript, 143 142 bytes

a=prompt(b=5);for(c=[];b--;c.push(a.replace(/{(.+?)}/g,function(_,j){return (d=j.split("|"))[d.length*Math.random()|0]})));alert(c.join("\n"))
\$\endgroup\$
1
\$\begingroup\$

Python 3, 97 bytes

Using a lambda function in regex substitution. This is somewhat similar to how I did it in my Spintax module https://github.com/AceLewis/spintax however it does not deal with nested Spintax or escaping the characters.

import re,random
print(re.sub("{(.*?)}",lambda x:random.choice(x.group(1).split('|')),input()*5))

If you do not assume the input ends with a newline then it will be 104 bytes.

import re,random
print(re.sub("{(.*?)}",lambda x:random.choice(x.group(1).split('|')),(input()+'\n')*5))
\$\endgroup\$
0
\$\begingroup\$

Japt -R, 18 bytes

5Ær"\{(.+?)}"Ïq| ö

Try it

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.