Reputation: 9886
Trying to recreate the "Only smart people can read this" meme. Here's a sample:
Hrad to blveiee taht you cluod aulaclty uesdnatnrd waht yor’ue rdanieg. The phaonmneal pweor of the hmuan bairn, aoccdrnig to a rscheearch at Cmabrigde Uinervtisy, sowhs taht it deosn’t mttaer in waht oredr the ltteers in a wrod are, the olny iprmoatnt tihng is taht the frist and lsat ltteer be in the rghit pclae.
The rset can be a taotl mses and you can sitll raed it wouthit a porbelm.
Tihs is bcuseae the huamn biarn deos not raed ervey lteter by istlef, but the wrod as a wlohe ptatren. Amzanig huh? Yaeh, and you awlyas tghuhot slpeling was ipmorantt!
How do I create a Ruby method that jumbles the middle words for every word greater than 3 letters, in a sentence I pass it.
Clarification: I've posted this question and answer both at the same time. There's no code in the question because I posted it in the answer.
Upvotes: 6
Views: 1148
Reputation: 106087
Okay, I'll bite:
def srlabmce(str)
str.gsub(/([\p{L}'])([\p{L}']{2,})([\p{L}'])/) { "#$1#{$2.chars.shuffle.join}#$3" }
end
puts srlabmce("Hard to believe that you could actually understand what you're reading")
# => Hrad to beviele taht you cuold atlculay unantdresd what yoru'e raeindg
See it on repl.it: https://repl.it/@jrunning/TrainedDangerousSlope
I originally used the Regexp /(\S)(\S+)(\S)/
, which counted as a "word" any sequence of three or more non-whitespace characters. This unfortunately counted punctuation as word characters, so e.g. "Hello, world.
" might become "Hlloe, wlodr.
"—the ,
and .
were counted as the last "letters" of the words, and the actual last letters were moved.
I've updated it to use the Regexp /([\p{L}'])([\p{L}']{2,})([\p{L}'])/
. The character class \p{L}
corresponds to the Unicode category "Letters," so it works with basic diacritics, and I threw in '
to match amingilani's implementation.
puts srlabmce("Quem ïd feugiat iaculisé éu mié tùrpus ïn interdùm grâvida, malesuada vivamus nam nullä urna justo conubia torétoré lorem.")
# => Qeum ïd fgieuat iliacusé éu mié tpùurs ïn iedùtnrm girâdva, madueasla vimavus nam nullä unra jutso cnboiua ttoréroé lerom.
If we want to add the requirement that no word's letter order may be the same in the output as the input, we can modify the proc passed to gsub
to call itself again until the order has changed:
def srlabmce(str)
replacer = ->*{
if $2.chars.uniq.size < 2 then $&
else
o = $2.chars.shuffle.join
o == $2 ? replacer[] : "#$1#{o}#$3"
end
}
str.gsub(/([\p{L}'])([\p{L}']{2,})([\p{L}'])/, &replacer)
end
We can still make this a one-liner, but its readability quickly deteriorates:
def srlabmce(str)
str.gsub(/([\p{L}'])([\p{L}']{2,})([\p{L}'])/, &(r = ->*{ $2.chars.uniq.size < 2 ? $& : (o = $2.chars.shuffle.join) == $& ? r[] : "#$1#{o}#$3" }))
end
See it on repl.it: https://repl.it/@jrunning/TrainedDangerousSlope-2
Upvotes: 8
Reputation: 9886
Edit: this code now guarantees words can't accidentally be scrambled to their original text. E.g. read
will now always be scrambled to raed
.
Edit 2: if words can't be scrambled, it'll return the original word, e.g. jumble 'feet' # => 'feet'
Create a method to jumble
individual words, and call it via mess_up
for each word in the sentence
def mess_up(sentence)
sentence = sentence.downcase.split(' ').map { |e| jumble(e) }.join(' ')
sentence[0] = sentence[0].upcase
sentence
end
def jumble(word)
return word if word.size <= 3
str = word.split('')
f = str.shift
l = str.pop
return word if str.uniq.size == 1
str = [f, str.shuffle, l].join('')
return jumble(str) if word == str
str
end
mess_up "Hard to believe that you could actually understand what you're reading"
# => "Hrad to bleevie taht you cuold aactlluy unrdnestad waht y'ruoe rendaig"
Motivation
I did this as a fun experiment when I saw the post. Was going to push it in a Gist, but realized someone may search for this at some point and SO is the best place for it.
Upvotes: 6
Reputation: 110745
def scramble(sentence)
sentence.split.map do |word|
word.size <= 3 || word[1..-2].squeeze.size == 1 ? word : word[0] +
word[1..-2].chars.shuffle.join('') + word[-1]
end.join(' ')
end
scramble "Little Miss Muffat sat on a tuffet"
#=> "Llitte Miss Mfauft sat on a tffuet"
If a shuffled word (of length 4 or more) must differ from the original word, one could write the following. Note one cannot shuffle a word of length 4 or greater to be different than the original word if all characters other than the first and last are the same.
def scramble(sentence)
sentence.split.map do |word|
word.size <= 3 || word[1..-2].squeeze(word[0]).size == 1 ? word :
word[0] + my_shuffle(word[1..-2]) + word[-1]
end.join(' ')
end
def my_shuffle(str)
a = arr = str.chars
loop do
a = a.shuffle
break a unless a == arr
end.join('')
end
Upvotes: 4