Как-то сидя на работе, и занимаясь написанием кода на Go, я вспомнил про старую задачку, суть которой заключалась в том, что нам необходимо реализовать деление любого числа на заранее заданное в условии задачи. Казалось бы просто, но было два ограничения:
1. Нельзя использовать числа в явном виде, кроме 0.
2. Количество разрешённых математических операций тоже было ограничено.
Повторять её на Go я не стал, но решил применить один из вариантов решения, для отображения строки. Идея эта не нова, и на полноправное авторство не претендую, просто решил поделиться.
Суть в том, что строка разбивается на байты, а каждый байт преобразуется в последовательность побитовых сдвигов, и операций XOR и OR с единицей.
Выглядит это примерно так:
EAX = uint8(unsafe.Sizeof(true))
(((EAX<<EAX^EAX)<<EAX|EAX)<<EAX<<EAX<<EAX^EAX)<<EAX)
И так начнём. Модуль unsafe тут нужен лишь для функции sizeof. Если кто знает как в Go получить размер структуры другими способами, буду рад если поделитесь.
import (
"fmt"
"math/rand"
"time"
"unsafe"
)
const (
EAX = uint8(unsafe.Sizeof(true))
ONE = "EAX"
)
Далее нужна функция, которая будет для заданного байта, находить и генерировать такую комбинацию. Наиболее простым и очевидным вариантом было просчитать сначала шаблон, вида [0,1,1,0,1,1,...]. Где 0 — означает что число четное, 1 — число нечётное. Уменьшая каждый раз число, выполняя побитовый сдвиг вправо на 1.
func getNumber(n byte) (buf string) {
var arr []byte
for n > EAX {
if n%2 == EAX {
arr = append(arr, EAX)
} else {
arr = append(arr, 0)
}
n = n >> EAX
}
}
Таким образом длина среза arr у нас будет равна количеству шагов, которые необходимо выполнить, для получения исходного числа.
buf = ONE
rand.Seed(time.Now().Unix())
for i := len(arr) - 1; i >= 0; i-- {
buf = fmt.Sprintf("%s<<%s", buf, ONE)
if arr[i] == EAX {
if rand.Intn(2) == 0 {
buf = fmt.Sprintf("(%s^%s)", buf, ONE)
} else {
buf = fmt.Sprintf("(%s|%s)", buf, ONE)
}
}
}
Пробегая по срезу arr мы генерируем строку «сдвигов», и если встречаем нечётное число, то рандомно выполняем над этим числом операцию OR или XOR с 1.
Осталось написать функцию, которая собственно и будет заданную строку превращать в последовательность таких сдвигов. В качестве примера, я решил выводить код, исполняя который можно получить заданную строку.
func TextToCode(txt string) string {
b := []byte(txt)
tmp := "var str []byten"
for _, item := range b {
tmp = fmt.Sprintf("%snstr = append(str, %s)", tmp, getNumber(item))
}
tmp += "nfmt.Println(string(str))"
return tmp
}
Вот собственно и всё. После запуска, для строки: «Author: GH0st3rs» получаем следующий результат:
var str []byte
str = append(str, (EAX<<EAX<<EAX<<EAX<<EAX<<EAX<<EAX^EAX))
str = append(str, ((((EAX<<EAX^EAX)<<EAX|EAX)<<EAX<<EAX^EAX)<<EAX<<EAX^EAX))
str = append(str, (((EAX<<EAX^EAX)<<EAX|EAX)<<EAX<<EAX^EAX)<<EAX<<EAX)
str = append(str, ((EAX<<EAX^EAX)<<EAX<<EAX|EAX)<<EAX<<EAX<<EAX)
str = append(str, (((((EAX<<EAX^EAX)<<EAX<<EAX|EAX)<<EAX^EAX)<<EAX^EAX)<<EAX|EAX))
str = append(str, (((EAX<<EAX^EAX)<<EAX|EAX)<<EAX<<EAX<<EAX^EAX)<<EAX)
str = append(str, (((EAX<<EAX^EAX)<<EAX|EAX)<<EAX<<EAX^EAX)<<EAX)
str = append(str, EAX<<EAX<<EAX<<EAX<<EAX<<EAX)
str = append(str, EAX<<EAX<<EAX<<EAX<<EAX<<EAX<<EAX)
str = append(str, (((EAX<<EAX<<EAX<<EAX<<EAX^EAX)<<EAX|EAX)<<EAX^EAX))
str = append(str, (EAX<<EAX<<EAX<<EAX^EAX)<<EAX<<EAX<<EAX)
str = append(str, (EAX<<EAX^EAX)<<EAX<<EAX<<EAX<<EAX)
str = append(str, ((((EAX<<EAX^EAX)<<EAX|EAX)<<EAX<<EAX<<EAX^EAX)<<EAX^EAX))
str = append(str, (((EAX<<EAX^EAX)<<EAX|EAX)<<EAX<<EAX^EAX)<<EAX<<EAX)
str = append(str, (((EAX<<EAX^EAX)<<EAX<<EAX<<EAX|EAX)<<EAX^EAX))
str = append(str, (((EAX<<EAX^EAX)<<EAX|EAX)<<EAX<<EAX<<EAX^EAX)<<EAX)
str = append(str, ((((EAX<<EAX^EAX)<<EAX|EAX)<<EAX<<EAX<<EAX^EAX)<<EAX^EAX))
fmt.Println(string(str))
» Исходный код проекта доступен на GitHub
Автор: GH0st3rs