Conversation

@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?

1
0
1

@postmodern

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.

0
0
0

@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あ'
```

0
0
0

@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

0
0
0

@alpha @postmodern Lol, I just submitted exactly this before reading your answer, based on some old code I wrote for random subarrays:

https://github.com/citizen428/shenanigans/blob/main/lib/shenanigans/array/random_subarray.rb

1
0
1

@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.

1
0
0

@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.

0
0
0
my solution
Show content

@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

0
0
0

@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

0
0
0

@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.

0
0
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.

1
0
0

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.

1
0
0

@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

1
0
0

@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.

0
0
0

@postmodern string.scan(/\p{L}/) { candidates << Regexp.last_match.offset(0).first }

0
0
0

@postmodern This is what I thought to do:

0
0
0