某サイトのアルゴリズムを作ってみる

某サイトとは....

[診断メーカー(おいしいパスタ作ったお前version)](https://shindanmaker.com/189987)です

みなさんが一度は使用したことがあるのではと思います（偏見w
名前を入れると、勝手に診断結果をくれる便利(?)なものです

今回使ってみた診断は、文字からランダムに文章(?)を作ってくれます

```
診断したい名前：hmhm
診断結果：マジギレ一目惚れ女がお前家庭的な大貧民負けて大貧民負けて俺大貧民負けて作った
```

# なんとなくの推測アルゴリズム

```
おいしいパスタ
作った
お前
家庭的な
女が
タイプの
俺
一目惚れ
大貧民負けて
マジギレ
```
の10個の文字列をランダムに出しているだけじゃないかと....

# Coding

## 必要な変数定義

```swift
let array = ["おいしいパスタ", "作った", "お前", "家庭的な", "女が", "タイプの", "俺", "一目惚れ", "大貧民負けて", "マジギレ"]
var result: [String] = []
let output = 10
```

- 使う文字を入れた配列 `array` を用意
- 結果を入れる配列 `result` を用意
- 本家ではおそらく10個の単語を出力してそうだったので、出力する数`output`を10として用意

## 乱数で文字列を選択

```swift
for _ in 0..<output {
	let random = Int(arc4random_uniform(UInt32(array.count)))
	result.append(array[random])
}
print(result.reduce("", +))
```

- ループを出力する数だけ実行
- swiftで乱数を生成するとなると`arc4random_uniform()`が良さげなので使用
- 乱数の範囲は、0から使う文字を入れた配列 `array`の個数までに設定
- 生成された乱数の場所の文字列を`result`に格納
- `reduce`で配列内の文字を合成

## 実行結果
```swift
func sindanmaker() {
	let array = ["おいしいパスタ", "作った", "お前", "家庭的な", "女が", "タイプの", "俺", "一目惚れ", "大貧民負けて", "マジギレ"]
	var result: [String] = []
	let output = 10
	for _ in 0..<output {
		let random = Int(arc4random_uniform(UInt32(array.count)))
		result.append(array[random])
	}
	print(result.reduce("", +))
}

sindanmaker()
// result print
お前マジギレマジギレマジギレ一目惚れお前お前マジギレ作ったタイプの
```
ふむふむ
とりあえず成功！
成功したので、これで終わりま....せん！w

## 問題点

`arc4random()`が毎回違う乱数を生成します
そのため実行するたびに結果が変わります

```swift
sindanmaker()
sindanmaker()

// result print
マジギレおいしいパスタ俺家庭的なおいしいパスタお前家庭的なおいしいパスタ一目惚れ大貧民負けて

おいしいパスタ作った家庭的な大貧民負けておいしいパスタ家庭的なおいしいパスタタイプのタイプの一目惚れ
```
普通はいいことなのですが、本家サイトでは、同じ名前を入れると同じ結果が返って来ます
本家に合わせるためにそこまでやってみます

# Fixed coding

## 入力された値からhash値をとる
いろいろ考えた末、入力された文字列からhash値を得て、それを元に診断結果を作ろうかなと

```swift
let str: String // 入力値
var hash = str.hashValue
```
swiftに元からあるメソッドを使っていきます
これでString型をInt型にすることができました

```example.swift
let str: String = "hmhm"
var hash = str.hashValue // 4799450094189775535
```
このように入力された文字によって固有の値が出て来ます

## hash値の変換
```swift
let num = Int(log10(Double(hash)) + 1)
if num < 10 {
	return
}
```
hash値の桁数を取得します

- `log10()`メソッドで10の何乗かを取得
- 桁数はそれに+1した数になります
- それをIntで整数化します（少数第一位のくり上がりを気にする場合は、ここで`floor`などでで切り捨てすべきでしたね...）

```swift
let round = Int(powf(10, Float(num - 10)))
let prefix = hash / round
let suffix = hash - prefix * round
var value = prefix + suffix
```
ここが一番悩みました...
本家サイトがどうやっているのかわからないのですが、自分流でw

- hash値の桁数から出力分の10桁を引いた数分、10をべき乗`powf`していきます
- hash値の桁数を上から10桁分の数値におとします`prefix`
- hash値の上から10桁を抜いた数を取得します`suffix`
- それを足します(ここは自己流ですw簡単に10桁にするためにこれでよいかなと....) 

```example.swift
let str: String = "hmhm"
var hash = str.hashValue // 4799450094189775535

let round = Int(powf(10, Float(num - 10))) // 1000000000
let prefix = hash / round // 4799450094
let suffix = hash - prefix * round // 189775535
var value = prefix + suffix // 4989225629
```

とりあえず、10桁の数値ができました

## 変数定義
```swift
let array = ["おいしいパスタ", "作った", "お前", "家庭的な", "女が", "タイプの", "俺", "一目惚れ", "大貧民負けて", "マジギレ"]
var result: [String] = []
let output = 10
```
先ほどと同じです

## 文字列を選択

```swift
for _ in 0..<output {
	let random = value % 10
	value = value / 10
	result.append(array[random])
}
print(result.reduce("", +))
```
先ほどhash値から求めた10桁の数字を使います

- 10桁の数字を10で割った余りを計算（1の位の値を取得）
- 10桁の数字を10で割る（10の位の数値を1の位にするため）
- 結果の配列に足していく

```example.swift
var value = prefix + suffix // 4989225629
for _ in 0..<output {
	let random = value % 10
	print("random: \(random)")
	value = value / 10
	print("value: \(value)")
	result.append(array[random])
}

// print
random: 9
value: 498922562

random: 2
value: 49892256

random: 6
value: 4989225

random: 5
value: 498922

random: 2
value: 49892

random: 2
value: 4989

random: 9
value: 498

random: 8
value: 49

random: 9
value: 4

random: 4
value: 0
```
10桁だった数字が減っていくのと、`random`に1の位が代入されているのがわかると思います
`random`に入った数値の場所を、文字列の配列`array`から取り出していきます

## 実行結果
```swift
func sindanmaker_fixed(str: String) {
	var hash = str.hashValue
	let num = Int(log10(Double(hash)) + 1)
	if num < 10 {
		return
	}

	let round = Int(powf(10, Float(num - 10)))
	let prefix = hash / round
	let suffix = hash - prefix * round
	var value = prefix + suffix

	let array = ["おいしいパスタ", "作った", "お前", "家庭的な", "女が", "タイプの", "俺", "一目惚れ", "大貧民負けて", "マジギレ"]
	var result: [String] = []
	let output = 10
	
	for _ in 0..<output {
		let random = value % 10
		value = value / 10
		result.append(array[random])
	}
	print(result.reduce("", +))
}

sindanmaker_fixed(str: "hmhm")

// result print
マジギレお前俺タイプのお前お前マジギレ大貧民負けてマジギレ女が
```
とりあえず診断結果は出てる！
肝心なのはもう一度同じ名前でやって同じ結果になるか...

```swift
sindanmaker_fixed(str: "hmhm")
sindanmaker_fixed(str: "hmhm")

// result print
マジギレお前俺タイプのお前お前マジギレ大貧民負けてマジギレ女が

マジギレお前俺タイプのお前お前マジギレ大貧民負けてマジギレ女が
```
やったー！
違うのもやってみよう

```swift
sindanmaker_fixed(str: "hmhmsh")

// result print
一目惚れ大貧民負けて作った大貧民負けてお前大貧民負けて作った俺大貧民負けて女が
```
大丈夫そう！
ということでこれで終われる！

# 最後に
ながながと書いてしまいましたが、読んでいただきありがとうございます
書いてる途中で、「ここは判定が足りなかったorz」などとなりましたが...
とりあえず、（自己流ですが）某サイトのようなアルゴリズムを作れたのではないかなと:relaxed: