[paiza]脳筋プレーやめた（【殿堂入りキャンペーン】名刺バインダー管理 (paizaランク B 相当)

# 見つかるまでループするとか脳筋かよ（特大ブーメラン
[JavaScript版](https://qiita.com/AsilHatake/items/547c799b9d2ca752db6e)ではひたすらループして、
特定の数字が見つかるまで帰れまテンみたいな脳筋プレーしてましたが、
よくよく数字を並べて比較してたらソレっぽい関係性的なのが見つかったため、プログラムに落とし込んでみた。

https://paiza.jp/works/mondai/b_rank_skillcheck_archive/name_card


# チェック処理とかは端折ってる
/** ここから定型文 */
ここで記載したクラスについては、
[こっちの記事](https://qiita.com/AsilHatake/items/40204293bdf59cb9f9d8)に書いてある、標準入力をList化したりしてるMainクラスから呼び出す予定だし・なんか継承したりしてるYO!!

ちな、Mainを実行して標準入力から入力するのクソ面倒くさいので、
Java編については問題の入力例をパラメータにしたテストクラスとかも作って公開するYO!!

<details open><summary>開発・実行環境はこんな感じ</summary>

- VSCode
- Java 17
- jUnit 5.9
- maven

pom.xmlはこんな感じ

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>jp.co.asil</groupId>
    <artifactId>paiza202408</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.9.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
```

</details>
/** ここまで定型文 */

## 実装クラス 
```java:NameCard.java
package jp.co.asil.paiza202408;

import java.util.List;

public class NameCard extends Question {

  public NameCard(List<String> list) {
    super(list);
  }

  /**
   * あなたはこれまでに出会った人たちの名刺を集めています。
   * 名刺は、複数枚のファイルを閉じることができるバインダーに保存されています。
   * 
   * 1枚のファイルには、n個のポケットが横に並んでおり、表と裏の両面から名刺を眺めることができます。
   * このため、1つのポケットには、2枚の名刺が背中合わせに入っています。
   * 
   * 各名刺には1から順に通し番号が付いているため、この番号の順に名刺を眺めることができるようにポケットに入っています。
   * 1番からn番の名刺は、1枚目のファイルの表面から見たときに左詰めに並んでおり、
   * n+1番から2n番の名刺は、1枚目のファイルの裏面から見たときに左詰めに並んでいます。
   * 2枚目以降のファイルにも同様に名刺が並んでいます。
   * 
   * 各ポケットの数字はそのポケットで眺めることができる名刺の番号で、
   * 括弧で表される数字はそのポケットを反対の面から見たときに眺めることができる名刺の番号です。
   * 
   * 名刺の番号mが与えられるので、その名刺の裏側の名刺の番号を表示するプログラムを作成してください。
   * ただし、番号mの名刺の裏面には必ず名刺が存在するものとしてください。
   */
  @Override
  public List<String> answer() {
    int[] cond = List.of(list.get(0).split(" ")).stream().mapToInt(Integer::parseInt).toArray();

    // 出現ページの算出
    int pageNo = 0;
    if (cond[0] >= cond[1]) {
      // 片面に入る数より小さかったら絶対1ページ目
      pageNo = 1;
    } else {
      // とりあえず片面に入る数で割り、あまりが出る場合は1加算
      pageNo = cond[1] / cond[0];
      if (cond[1] % cond[0] != 0)
        pageNo++;
    }

    // 裏と表を足したらいくつ？を算出
    // ページの位置によって求め方が変わる
    // 奇数（右側）ページ：片面に入る枚数 * 2 * ページ数 + 1
    // 偶数（左側）ページ：片面に入る枚数 * 2 * (ページ数 - 1) + 1
    int sum = cond[0] * 2 * (pageNo - (pageNo % 2 == 0 ? 1 : 0)) + 1;
    return List.of(String.valueOf(sum - cond[1]));
  }
}
```

## テストクラス 
```java:NameCardTest.java
package jp.co.asil.paiza202408;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.List;

import org.junit.jupiter.api.Test;

public class NameCardTest {

  @Test
  void testAnswer1() {
    NameCard testClass = new NameCard(List.of("3 1"));
    assertEquals(testClass.answer().get(0), "6");
  }

  @Test
  void testAnswer2() {
    NameCard testClass = new NameCard(List.of("3 6"));
    assertEquals(testClass.answer().get(0), "1");
  }

  @Test
  void testAnswer3() {
    NameCard testClass = new NameCard(List.of("3 8"));
    assertEquals(testClass.answer().get(0), "11");
  }

  @Test
  void testAnswer4() {
    NameCard testClass = new NameCard(List.of("157 1518"));
    assertEquals(testClass.answer().get(0), "1309");
  }
}
```

# 考え方
1ページ目と2ページ目、3ページ目と4ページ目...Nページ目とN+1ページ目って、単純に裏返しただけなので中身の組み合わせって同じなんですよ。
※以下、公式から持ってきた画像
![namecard.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996378/c73ad9c1-5969-9f1e-71a0-d6b862ec5868.png)

次に裏と表を対で足す（1・2ページだと：1+6,2+5,3+4）と、
必ず同じ答え（このケースだと7）になるというルールが有るっぽいと言うことがわかってくるかと。

そこから、3・4ページだと19、5・6ページだと31と、なんか【12ずつ増えていくね】っていうのがわかり、この【12】ってなんだ？と考えると、2ページ分の枚数じゃね？ってことにたどり着きまして。

で、7とか19とか31を、与えられた数字でなんか公式作れないかなって考えていると、ソースコメントにも書いてますが次のような法則があることがわかりまして。

- 奇数（右側）ページ：片面に入る枚数 * 2 * ページ数 + 1
- 偶数（左側）ページ：片面に入る枚数 * 2 * (ページ数 - 1) + 1


これは指定された数字が左右のどっちのページにあるか割り出したら勝確じゃね？ということで例の図より1～4ページに有る数字をいくつかピックアップして、ルールを考えてみると。。。
- 指定数が片面に入る枚数以下の場合は1ページ目（1～3）
- 指定数が片面に入る枚数でピッタリ割り切れたらその答えがページ数となる(6,9,12 etc)
- 指定数が片面に入る枚数で割るとあまりが出る場合は答え+1がページ数となる(4,8 etc)

後はこれらをプログラムに落とし込んで動かしてみたら上手くいった感じでした。
脳筋プレイの【ループをひたすらぶん回す】よりかはかなり効率的になったでしょ？