@postmodern Get a random number in the inclusive range of 1 to 2**(string length), zip the characters in the string with the bits of the random number, and swapcase all the characters where the bit is 1?
def random_case(s)
size = s.size
bit_mask = ('0' * size).tap { |b| b[rand(size + 1)] = '1' }.to_i(2)
string_mask = size.times.map { rand(2).to_s }.join.to_i(2) | bit_mask
is_on = -> i { string_mask & (string_mask << i) != 0 }
s.chars.map.with_index { |c, i| is_on[i] ? c.upcase : c }.join
end
Screwing about with masking and bit twiddling, which I am not very good at.
@postmodern It's fun for me✨ 💎 ✨
```ruby
using Module.new { refine(String) {
def up_or_down_case = public_send([:upcase, :downcase].sample)
def random_case = chars.map(&:up_or_down_case).join }
}
def random_case(str) =
str.swapcase == str ? str : Enumerator.produce { str.random_case }.find { _1 != str }
puts random_case ''
puts random_case 'あ'
puts random_case 'abcdefあ'
```
@postmodern Not very "clever" but I think it works.
def randomise(s)
new_s = s
new_s = s.chars.map { |c|
c.send([:upcase, :downcase].sample)
}.join until new_s != s
new_s
end
@postmodern my attempt, trying to capture the spirit of the intention so it's comprehensible:
def random_case(string)
chars_to_flip = (0...string.length).to_a.shuffle.take(rand(string.length) + 1)
string.chars.map.with_index { |c,i| chars_to_flip.include?(i) ? c.swapcase : c }.join
end
We can guarantee that the string is never the same by ensuring that the set of random character indexes selected for flipping always contains at least one element.
@postmodern I'm a little late to the party but I'm curious how people would suggest testing this (any implementation), without getting into a prove-the-negative situation.
@postmodern def postmodern_case_mem(s); man
datory = rand(s.size); s.size.times { |i| s[i] =
s[i].swapcase if i == mandatory || rand(2) == 1
}; s; end
@postmodern Another implementation, just for fun:
def randomize(string)
string.chars.map {|c| rand(2).zero? && c.swapcase || c }.join.tap do |res|
res[-1] = res[-1].swapcase if res == string
end
end
@james this is also what I was thinking. You would need to select +1 random (but unique) indexes to flip to ensure that at least one index is always flipped. Doing bitflipping with rand() might still result in no indexes being flipped, because there's still a small probability that rand() might return 0.
Here's my solution. It takes into account unicode letters and non-letter characters.
```
def randcase(string)
candidates = []
string.each_char.each_with_index do |char,index|
if char =~ /\p{L}/
candidates << index
end
end
new_string = string.dup
candidates.sample(rand(candidates.length-1)+1).each do |index|
new_string[index] = new_string[index].swapcase
end
return new_string
end
```
This ensures at least 1, but not all letter chars, get swapped.
I suspect there's a way to eliminate the `.each_char.each_with_index` with a single Regexp match and use `MatchData#captures`. Also, I'm not sure whether to return the same String object or a dup, if no swappable candidates can be found in the input String.
@postmodern so kind of like the Spongebob mock meme? Funny enough, I had written this for a bot a while ago
def mock(text)
random = Random.new
chars = text.chars.map do |chr|
if chr.ascii_letter? && random.next_bool
chr.ascii_lowercase? ? chr.upcase : chr.downcase
else
chr
end
end
chars.join
end
In Crystal of course
@watzon that's roughly my original implementation. Unfortunately, it doesn't guarantee at least one character will always be swapcase'd. Random#next_bool can return three or more falses in a row, which means smaller strings can occasionally not be randomized at all, which will break tests.
@postmodern string.scan(/\p{L}/) { candidates << Regexp.last_match.offset(0).first }