僕もコンピュータを使ってでもパズル王に勝ちたい!!
この動画に触発されて僕もパズル王に勝ちたくなったのでやっていく。
まずはシンプルなmake10を考えてみる。
計算式は下記の5パターンを考えれば良い。
ということで、make10を解くために、最大で
括弧による優先順位を考慮するプログラムを作るのは面倒なので、逆ポーランド記法を使って解くことにする。
逆ポーランド記法で書き直した式は下記の通り。
ひとまず、標準入力から問題を受け付ける部分を実装する。
use std::io::{self, Write};
fn main() {
print!("Input your numbers: ");
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).expect("input error");
solve(input);
}
fn solve(input: String) {
let split_numbers = input.trim().split(" ")
.map(|x| x.parse::<u8>().expect("not a positive number"))
.collect::<Vec<_>>();
let (expect_answer, numbers) = split_numbers.split_last().expect("no enough numbers");
println!("{:?} = {}", numbers, expect_answer);
}
実行してみる。
Input your numbers: 1 2 3 4 10
[1, 2, 3, 4] = 10
逆ポーランド記法の計算式を生成する。
fn gen_formulas(numbers: Vec<u8>, patterns: Vec<String>) -> Vec<String> {
let number_patterns = gen_permutation(&numbers);
let operator_patterns = gen_operator_patterns(numbers.len() - 1);
let mut formulas = vec![];
for pattern in patterns {
for number_pattern in number_patterns.iter() {
for operator_pattern in operator_patterns.iter() {
let mut buf = vec![];
let mut number_cursor = 0;
let mut operator_cursor = 0;
for ch in pattern.chars() {
match ch {
'0' => {
buf.push(format!("{}", number_pattern.iter().nth(number_cursor).unwrap()));
number_cursor += 1;
},
_ => {
buf.push(format!("{}", operator_pattern.iter().nth(operator_cursor).unwrap()));
operator_cursor += 1;
}
}
}
formulas.push(buf.join(" "));
}
}
}
formulas
}
fn gen_permutation<T>(arr: &Vec<T>) -> Vec<Vec<&T>> {
fn inner<'a, T>(arr: Vec<&'a T>, stack: Vec<&'a T>, output: &mut Vec<Vec<&'a T>>) {
if arr.is_empty() {
output.push(stack);
} else {
for (i, &v) in arr.iter().enumerate() {
let new_arr = arr.iter()
.enumerate()
.filter_map(|(j, &w)| if i != j { Some(w) } else { None })
.collect();
let mut new_stack: Vec<&T> = stack.clone();
new_stack.push(v);
inner(new_arr, new_stack, output);
}
}
}
let mut result = vec![];
inner(arr.iter().collect(), vec![], &mut result);
result
}
fn gen_operator_patterns(size: usize) -> Vec<Vec<&'static str>> {
fn inner(size: usize, stack: Vec<&'static str>, output: &mut Vec<Vec<&'static str>>) {
if stack.len() == size {
output.push(stack);
} else {
inner(size, vec![stack.clone(), vec!["+"]].concat(), output);
inner(size, vec![stack.clone(), vec!["-"]].concat(), output);
inner(size, vec![stack.clone(), vec!["*"]].concat(), output);
inner(size, vec![stack.clone(), vec!["/"]].concat(), output);
}
}
let mut result = vec![];
inner(size, vec![], &mut result);
result
}
fn solve(input: String) {
let split_numbers = input.trim().split(" ")
.map(|x| x.parse::<u8>().expect("not a positive number"))
.collect::<Vec<_>>();
let (expect_answer, numbers) = split_numbers.split_last().expect("no enough numbers");
+
+ let patterns = vec![
+ "00*0*0*".to_string(),
+ "000**0*".to_string(),
+ "00*00**".to_string(),
+ "000*0**".to_string(),
+ "0000***".to_string()
+ ];
+ let formulas = gen_formulas(numbers.to_vec(), patterns);
println!("{:?} = {}", numbers, expect_answer);
+ println!("{}", formulas.join("\n"));
+ println!("{}", formulas.len());
}
実行してみる。
Input your numbers: 1 2 3 4 10
[1, 2, 3, 4] = 10
1 2 + 3 + 4 +
1 2 + 3 + 4 -
1 2 + 3 + 4 *
1 2 + 3 + 4 /
1 2 + 3 - 4 +
(中略)
4 3 2 1 / * /
4 3 2 1 / / +
4 3 2 1 / / -
4 3 2 1 / / *
4 3 2 1 / / /
7680
できてそう。
逆ポーランド記法の式を計算するロジックを実装する。
fn calc_rpn(formula: String) -> Option<(f32, String)> {
fn calc(op: &str, stack: &mut Vec<f32>, formula_stack: &mut Vec<String>) -> bool {
let v2 = stack.pop().unwrap();
let f2 = formula_stack.pop().unwrap();
let v1 = stack.pop().unwrap();
let f1 = formula_stack.pop().unwrap();
let result = match op {
"+" => Some((v1 + v2, format!("({} + {})", f1, f2))),
"-" => Some((v1 - v2, format!("({} - {})", f1, f2))),
"*" => Some((v1 * v2, format!("({} * {})", f1, f2))),
"/" => {
if v2 == 0.0 {
None
} else {
Some((v1 / v2, format!("({} / {})", f1, f2)))
}
},
_ => None
};
match result {
Some((answer, formula)) => {
stack.push(answer);
formula_stack.push(formula);
true
}
None => false
}
}
let tokens = formula.split(" ").collect::<Vec<_>>();
let mut stack: Vec<f32> = vec![];
let mut formula_stack: Vec<String> = vec![];
for token in tokens {
match token {
"+" | "-" | "*" | "/" => {
let is_succeeded = calc(token, &mut stack, &mut formula_stack);
if !is_succeeded {
return None;
}
},
_ => {
let n = token.parse::<f32>().unwrap();
stack.push(n);
formula_stack.push(token.to_string());
}
}
}
Some((stack.pop().unwrap(), formula_stack.pop().unwrap()))
}
fn solve(input: String) {
let split_numbers = input.trim().split(" ")
.map(|x| x.parse::<u8>().expect("not a positive number"))
.collect::<Vec<_>>();
let (expect_answer, numbers) = split_numbers.split_last().expect("no enough numbers");
let patterns = vec![
"00*0*0*".to_string(),
"000**0*".to_string(),
"00*00**".to_string(),
"000*0**".to_string(),
"0000***".to_string()
];
let formulas = gen_formulas(numbers.to_vec(), patterns);
- println!("{:?} = {}", numbers, expect_answer);
- println!("{}", formulas.join("\n"));
- println!("{}", formulas.len());
+ for formula in formulas {
+ match calc_rpn(formula) {
+ Some((answer, infix_formula)) => {
+ if answer == *expect_answer as f32 {
+ println!("{} = {}", infix_formula, expect_answer);
+ break;
+ }
+ }
+ None => {}
+ }
+ }
}
ここまでで動画の問題を解けるだけ解いてみる。
Input your numbers: 2 5 5 9 13
(((9 - 5) * 2) + 5) = 13
Input your numbers: 4 8 8 9 23
(((8 - 4) * 8) - 9) = 23
Input your numbers: 3 5 8 9 24
(((3 * 9) + 5) - 8) = 24
Input your numbers: 1 3 5 7 19
(((1 + 7) * 3) - 5) = 19
できてそう。
数値が4つではないパターンを考えてみる。
数値が2つのときのRPNパターンは下記の通り。
- 1 1 +
数値が3つのときのパターンは下記の通り。
- 1 1 1 + +
- 1 1 + 1 +
数値が4つのときのパターンは下記の通り。
- 1 1 1 1 + + +
- 1 1 1 + 1 + +
- 1 1 1 + + 1 +
- 1 1 + 1 + 1 +
- 1 1 + 1 1 + +
- 1 1 + + 1 1 +
ただし、最後の行は途中でスタックが0になってしまうので、RPNとしては無効である。
以上を踏まえると、下記の通りパターンを生成すれば良さそうである。
- 最初の2つは必ず数値になる
- 最後は必ず記号になる
-
個の数字では、間に入る数値と記号はそれぞれN 個であるN-2 - 途中でスタックが0になってしまうRPNは除外する
計算式パターンを洗い出す処理を実装する。
fn gen_patterns(n: usize) -> Vec<String> {
let mid = (0..n-2).map(|_| vec!["0", "*"]).collect::<Vec<Vec<&str>>>().concat();
let patterns = gen_permutation(&mid)
.into_iter()
.map(|x| {
let mid = x.into_iter().map(|y| *y).collect::<Vec<_>>();
format!("00{}*", mid.join(""))
})
.collect::<Vec<_>>();
let patterns: HashSet<String> = patterns.into_iter().collect();
patterns.into_iter()
.filter(|x| {
let mut stack_cursor = 0;
for ch in x.chars() {
match ch {
'0' => stack_cursor += 1,
_ => {
stack_cursor -= 1;
if stack_cursor == 0 {
return false;
}
}
}
}
true
})
.collect()
}
fn solve(input: String) {
let split_numbers = input.trim().split(" ")
.map(|x| x.parse::<u8>().expect("not a positive number"))
.collect::<Vec<_>>();
let (expect_answer, numbers) = split_numbers.split_last().expect("no enough numbers");
- let patterns = vec![
- "00*0*0*".to_string(),
- "000**0*".to_string(),
- "00*00**".to_string(),
- "000*0**".to_string(),
- "0000***".to_string()
- ];
+ let patterns = gen_patterns(numbers.len());
let formulas = gen_formulas(numbers.to_vec(), patterns);
for formula in formulas {
match calc_rpn(formula) {
Some((answer, infix_formula)) => {
if answer == *expect_answer as f32 {
println!("{} = {}", infix_formula, expect_answer);
break;
}
}
None => {}
}
}
}
実行してみる。
Input your numbers: 1 2 2 6 7 9 16
結果が表示されなくなってしまった😇
デバッグコードを仕込んでみる。
let patterns = gen_patterns(numbers.len());
+ println!("number of patterns: {}", patterns.len());
let formulas = gen_formulas(numbers.to_vec(), patterns);
+ println!("number of formulas: {}", formulas.len());
Input your numbers: 1 2 3 4 10
number of patterns: 5
number of formulas: 7680
((1 + 2) + (3 + 4)) = 10
Input your numbers: 1 2 3 4 5 15
number of patterns: 14
number of formulas: 430080
(1 + (2 + ((3 + 4) + 5))) = 15
Input your numbers: 1 2 3 4 5 6 21
number of patterns: 42
事前に生成するパターンが指数的に増えるため、生成処理に時間がかかっているっぽい。
gen_formulas
を、クロージャを受け取るように変更し、随時判定するようにしてみる。
fn gen_formulas<F>(numbers: Vec<u8>, patterns: Vec<String>, mut f: F) where F: FnMut(String) -> bool {
let number_patterns = gen_permutation(&numbers);
let operator_patterns = gen_operator_patterns(numbers.len() - 1);
for pattern in patterns {
for number_pattern in number_patterns.iter() {
for operator_pattern in operator_patterns.iter() {
let mut buf = vec![];
let mut number_cursor = 0;
let mut operator_cursor = 0;
for ch in pattern.chars() {
match ch {
'0' => {
buf.push(format!("{}", number_pattern.iter().nth(number_cursor).unwrap()));
number_cursor += 1;
},
_ => {
buf.push(format!("{}", operator_pattern.iter().nth(operator_cursor).unwrap()));
operator_cursor += 1;
}
}
}
if !f(buf.join(" ")) {
return;
}
}
}
}
}
fn solve(input: String) {
let split_numbers = input.trim().split(" ")
.map(|x| x.parse::<u8>().expect("not a positive number"))
.collect::<Vec<_>>();
let (expect_answer, numbers) = split_numbers.split_last().expect("no enough numbers");
let patterns = gen_patterns(numbers.len());
gen_formulas(numbers.to_vec(), patterns, |formula| {
match calc_rpn(formula) {
Some((answer, infix_formula)) => {
if answer == *expect_answer as f32 {
println!("{} = {}", infix_formula, answer);
return false;
}
}
None => {}
}
true
});
}
再度実行してみる。
Input your numbers: 1 2 2 6 7 9 16
(1 * ((2 + 2) * ((6 + 7) - 9))) = 16
解けた。
(福良さんとは違う式が出てきているが、数が増えるほど別解が発生しやすくなるので仕方がない)
動画に出てきた残りの問題についても解いていく。
Input your numbers: 1 2 2 6 7 9 16
(1 * ((2 + 2) * ((6 + 7) - 9))) = 16
Input your numbers: 1 2 3 3 3 9 13
(1 - (2 * (((3 + 3) - 3) - 9))) = 13
Input your numbers: 1 4 5 6 6 6 9
((1 - 4) + (5 + ((6 / 6) + 6))) = 9
Input your numbers: 1 5 6 7 13
(1 - (6 * (5 - 7))) = 13
Input your numbers: 1 2 3 6 6 24
(1 * ((2 / 3) * (6 * 6))) = 24
Input your numbers: 2 3 4 4 19
(3 + ((4 + 4) * 2)) = 19
Input your numbers: 2 4 7 9 9 14
((2 + (4 * (9 - 9))) * 7) = 14
Input your numbers: 3 4 7 8 8 9 8
((3 + 4) - ((7 - (8 + 8)) / 9)) = 8
Input your numbers: 1 3 4 6 8
(((1 - 3) + 4) + 6) = 8
Input your numbers: 1 3 4 6 9
(1 + ((4 / 3) * 6)) = 9
Input your numbers: 2 3 9 9 14
(2 + ((9 / 3) + 9)) = 14
Input your numbers: 1 3 4 4 5 22
(((1 + (3 * 4)) + 4) + 5) = 22
Input your numbers: 1 4 6 6 8
(((1 * 6) - 4) + 6) = 8
Input your numbers: 1 1 4 6 6 8 17
(1 * ((1 - 4) + ((6 + 6) + 8))) = 17
Input your numbers: 4 4 5 7 8 16
((4 - (4 + (5 - 7))) * 8) = 16
Input your numbers: 3 3 4 7 25
(3 * (7 + (4 / 3))) = 25
Input your numbers: 2 2 6 8 9 17
((2 * (2 + 6)) - (8 - 9)) = 17
Input your numbers: 1 8 9 9 16
(8 * (1 + (9 / 9))) = 16
Input your numbers: 2 5 5 6 6 8 15
(2 - (5 - (6 * ((5 + 6) - 8)))) = 15
Input your numbers: 2 2 3 6 8 12
(2 * (((3 / 2) * 8) - 6)) = 12
Input your numbers: 6 6 7 8 9 22
((6 + (6 - 7)) + (8 + 9)) = 22
Input your numbers: 1 6 8 8 18
((1 + 8) * (8 - 6)) = 18
鶴崎さんが止まった26問目も解けたので、ここまでにする。
ソースコード
use std::collections::HashSet;
use std::io::{self, Write};
fn main() {
print!("Input your numbers: ");
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).expect("input error");
solve(input);
}
fn solve(input: String) {
let split_numbers = input.trim().split(" ")
.map(|x| x.parse::<u8>().expect("not a positive number"))
.collect::<Vec<_>>();
let (expect_answer, numbers) = split_numbers.split_last().expect("no enough numbers");
let patterns = gen_patterns(numbers.len());
gen_formulas(numbers.to_vec(), patterns, |formula| {
match calc_rpn(formula) {
Some((answer, infix_formula)) => {
if answer == *expect_answer as f32 {
println!("{} = {}", infix_formula, answer);
return false;
}
}
None => {}
}
true
});
}
fn gen_formulas<F>(numbers: Vec<u8>, patterns: Vec<String>, mut f: F) where F: FnMut(String) -> bool {
let number_patterns = gen_permutation(&numbers);
let operator_patterns = gen_operator_patterns(numbers.len() - 1);
for pattern in patterns {
for number_pattern in number_patterns.iter() {
for operator_pattern in operator_patterns.iter() {
let mut buf = vec![];
let mut number_cursor = 0;
let mut operator_cursor = 0;
for ch in pattern.chars() {
match ch {
'0' => {
buf.push(format!("{}", number_pattern.iter().nth(number_cursor).unwrap()));
number_cursor += 1;
},
_ => {
buf.push(format!("{}", operator_pattern.iter().nth(operator_cursor).unwrap()));
operator_cursor += 1;
}
}
}
if !f(buf.join(" ")) {
return;
}
}
}
}
}
fn gen_permutation<T>(arr: &Vec<T>) -> Vec<Vec<&T>> {
fn inner<'a, T>(arr: Vec<&'a T>, stack: Vec<&'a T>, output: &mut Vec<Vec<&'a T>>) {
if arr.is_empty() {
output.push(stack);
} else {
for (i, &v) in arr.iter().enumerate() {
let new_arr = arr.iter()
.enumerate()
.filter_map(|(j, &w)| if i != j { Some(w) } else { None })
.collect();
let mut new_stack: Vec<&T> = stack.clone();
new_stack.push(v);
inner(new_arr, new_stack, output);
}
}
}
let mut result = vec![];
inner(arr.iter().collect(), vec![], &mut result);
result
}
fn gen_operator_patterns(size: usize) -> Vec<Vec<&'static str>> {
fn inner(size: usize, stack: Vec<&'static str>, output: &mut Vec<Vec<&'static str>>) {
if stack.len() == size {
output.push(stack);
} else {
inner(size, vec![stack.clone(), vec!["+"]].concat(), output);
inner(size, vec![stack.clone(), vec!["-"]].concat(), output);
inner(size, vec![stack.clone(), vec!["*"]].concat(), output);
inner(size, vec![stack.clone(), vec!["/"]].concat(), output);
}
}
let mut result = vec![];
inner(size, vec![], &mut result);
result
}
fn gen_patterns(n: usize) -> Vec<String> {
let mid = (0..n-2).map(|_| vec!["0", "*"]).collect::<Vec<Vec<&str>>>().concat();
let patterns = gen_permutation(&mid)
.into_iter()
.map(|x| {
let mid = x.into_iter().map(|y| *y).collect::<Vec<_>>();
format!("00{}*", mid.join(""))
})
.collect::<Vec<_>>();
let patterns: HashSet<String> = patterns.into_iter().collect();
patterns.into_iter()
.filter(|x| {
let mut stack_cursor = 0;
for ch in x.chars() {
match ch {
'0' => stack_cursor += 1,
_ => {
stack_cursor -= 1;
if stack_cursor == 0 {
return false;
}
}
}
}
true
})
.collect()
}
fn calc_rpn(formula: String) -> Option<(f32, String)> {
fn calc(op: &str, stack: &mut Vec<f32>, formula_stack: &mut Vec<String>) -> bool {
let v2 = stack.pop().unwrap();
let f2 = formula_stack.pop().unwrap();
let v1 = stack.pop().unwrap();
let f1 = formula_stack.pop().unwrap();
let result = match op {
"+" => Some((v1 + v2, format!("({} + {})", f1, f2))),
"-" => Some((v1 - v2, format!("({} - {})", f1, f2))),
"*" => Some((v1 * v2, format!("({} * {})", f1, f2))),
"/" => {
if v2 == 0.0 {
None
} else {
Some((v1 / v2, format!("({} / {})", f1, f2)))
}
},
_ => None
};
match result {
Some((answer, formula)) => {
stack.push(answer);
formula_stack.push(formula);
true
}
None => false
}
}
let tokens = formula.split(" ").collect::<Vec<_>>();
let mut stack: Vec<f32> = vec![];
let mut formula_stack: Vec<String> = vec![];
for token in tokens {
match token {
"+" | "-" | "*" | "/" => {
let is_succeeded = calc(token, &mut stack, &mut formula_stack);
if !is_succeeded {
return None;
}
},
_ => {
let n = token.parse::<f32>().unwrap();
stack.push(n);
formula_stack.push(token.to_string());
}
}
}
Some((stack.pop().unwrap(), formula_stack.pop().unwrap()))
}
結論
限られた時間で作り上げる鶴崎さんもすごいし、脳内でこれをやってしまう福良さんもすごい。