覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

矩形同士の交差

左下の座標(X, Y) と 右上の座標(X, Y) が 与えられた矩形(長方形)があったとして、

f:id:nini_y:20190829212551p:plain

2つの矩形が与えられて、その矩形同士が以下のように交差(領域が被っているかを)しているか判定したいです

f:id:nini_y:20190829230458p:plain

交差の判定は

Max(矩形1.左下X, 矩形2.左下X) < Min(矩形1.右上X, 矩形2.右上X) かつ Max(矩形1.左下Y, 矩形2.左下Y) < Min(矩形1.右上Y, 矩形2.右上Y)

で、出来ます。(矩形同士の線分が接しているだけの状態は交差とは呼びにくいと思うので、<= ではなく < で判定しています


一応Javaでプログラム書いてみました。
なんか中途半端な気もしますが・・・

/**
 * 矩形を表現するクラス
 */
public final class Rectangle {

    /** 矩形の左下の点のX */
    private final int lBottomX;

    /** 矩形の左下の点のY */
    private final int lBottomY;

    /** 矩形の右上の点のX */
    private final int rTopX;

    /** 矩形の右上の点のY */
    private final int rTopY;

    public Rectangle(int lBottomX, int lBottomY, int rTopX, int rTopY) {
        // そもそも線や点の表現になってしまう場合は矩形とみなさないものとする
        // 左下と右上の位置関係にならない場合も不正とみなす
        if (lBottomX >= rTopX || lBottomY >= rTopY) {
            throw new IllegalArgumentException("Invalid parameter.");
        }

        this.lBottomX = lBottomX;
        this.lBottomY = lBottomY;
        this.rTopX = rTopX;
        this.rTopY = rTopY;
    }

    /**
     * 引数の矩形と2点が交差するかどうかを判定します
     *
     * @param rec 判定対象の矩形
     * @return 交差する場合true, それ以外の場合false
     */
    public boolean hasIntersect2point(Rectangle rec) {
        return Math.max(lBottomX, rec.lBottomX) < Math.min(rTopX, rec.rTopX) &&
                Math.max(lBottomY, rec.lBottomY) < Math.min(rTopY, rec.rTopY);
    }

    /**
     * 2点が交差する場合の交差している領域を表現する矩形を返します
     *
     * @param rec 判定対象の矩形
     * @return 交差している領域を表現する矩形
     */
    public Rectangle intersect2pointRectangle(Rectangle rec) {
        int maxLBottomX = Math.max(lBottomX, rec.lBottomX);
        int maxLBottomY = Math.max(lBottomX, rec.lBottomY);
        int minRTopX = Math.min(rTopX, rec.rTopX);
        int minRTopY = Math.min(rTopY, rec.rTopY);

        if (!hasIntersect2point(maxLBottomX, minRTopX, maxLBottomY, minRTopY)) {
            throw new IllegalStateException("not intersect 2point.");
        }

        return new Rectangle(maxLBottomX, maxLBottomY, minRTopX, minRTopY);
    }

    private boolean hasIntersect2point(int maxLBottomX, int minRTopX, int maxBottomY, int minRTopY) {
        return maxLBottomX < minRTopX && maxBottomY < minRTopY;
    }

    /**
     * 面積を求める
     *
     * @return 面積
     */
    public int area() {
        return (rTopX - lBottomX) * (rTopY - lBottomY);
    }

    @Override
    public String toString() {
        return "Rectangle{" +
                "lBottomX=" + lBottomX +
                ", lBottomY=" + lBottomY +
                ", rTopX=" + rTopX +
                ", rTopY=" + rTopY +
                ", area=" + area() +
                '}';
    }
}


交差していない矩形同士の場合

矩形同士が離れている場合

public class Main {
    public static void main(String[] args) {
        Rectangle rBase = new Rectangle(2, 2, 6, 5);

        Rectangle r1a = new Rectangle(0, 0, 1, 1);

        System.out.println(""rBase.hasIntersect2point(r1a));        
    }
}


出力結果

false

交差の判定が false になっています


矩形同士のある1辺だけが接していて領域としては重なっていない場合

public class Main {
    public static void main(String[] args) {
        Rectangle rBase = new Rectangle(2, 2, 6, 5);

        Rectangle r1b = new Rectangle(6, 3, 9, 8);

        System.out.println(""rBase.hasIntersect2point(r1b));        
    }
}


出力結果

false

2つの矩形のある辺が重なっている場合も、交差の判定は false になっています


交差する矩形同士の場合

public class Main {
    public static void main(String[] args) {
        Rectangle rBase = new Rectangle(2, 2, 6, 5);

        Rectangle r2 = new Rectangle(1, 1, 3, 4);

        System.out.println(rBase.hasIntersect2point(r2));
        System.out.println(rBase.intersect2pointRectangle(r2));
    }
}


出力結果

true
Rectangle{lBottomX=2, lBottomY=2, rTopX=3, rTopY=4, area=2}


public class Main {
    public static void main(String[] args) {
        Rectangle rBase = new Rectangle(2, 2, 6, 5);

        Rectangle r3 = new Rectangle(3, 3, 4, 4);

        System.out.println(rBase.hasIntersect2point(r3));
        System.out.println(rBase.intersect2pointRectangle(r3));
    }
}


出力結果

true
Rectangle{lBottomX=3, lBottomY=3, rTopX=4, rTopY=4, area=1}


public class Main {
    public static void main(String[] args) {
        Rectangle rBase = new Rectangle(2, 2, 6, 5);

        Rectangle r4 = new Rectangle(3, 3, 6, 7);

        System.out.println(rBase.hasIntersect2point(r4));
        System.out.println(rBase.intersect2pointRectangle(r4));
    }
}


出力結果

true
Rectangle{lBottomX=3, lBottomY=3, rTopX=6, rTopY=5, area=6}


交差の判定が true になって、交差した領域の矩形の情報(座標情報, 面積)が出力されます。

jqで基礎的な操作をしてみる

前回、jqコマンドをインストールしました。


基礎的な操作だけ試しておきます。

仮に person.json というファイルの内容が以下のようになっているとします

{ "name": { "first" : "taro", "last": "yamada" }, "age": 20 }


これを整形して表示する場合は以下になります

cat person.json | jq .

出力結果は以下の通りで整形されています。

{
  "name": {
    "first": "taro",
    "last": "yamada"
  },
  "age": 20
}


このJSONから「name」項目を取得したい場合は以下です

cat person.json | jq .name

出力結果は以下の通りで整形されています。

{
  "first": "taro",
  "last": "yamada"
}


「name」の中の 「first」の値だけ取りたいときは以下になります

cat person.json | jq .name

出力結果は以下の通り

"taro"


このやり方が望ましいかわかりませんが以下でもいけますね

cat person.json | jq .name | jq .first

出力結果は以下の通り

"taro"


出力結果を囲んでいるダブルクォートが邪魔な場合は -r オプションをつけます

cat person.json | jq -r .name

出力結果は以下の通り

taro


「name.first」 と 「name.last」 を結合した結果を出力してみます

cat person.json | jq .name | jq -r '.first + " " + .last'

出力結果は以下の通り

taro yamada


少し複雑めなJSONを操作

$ curl -s "http://geoapi.heartrails.com/api/json?method=searchByPostal&postal=1060032" | jq . 
{
  "response": {
    "location": [
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木一丁目",
        "town_kana": "ろっぽんぎ1ちょうめ",
        "x": "139.740991",
        "y": "35.665082",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木二丁目",
        "town_kana": "ろっぽんぎ2ちょうめ",
        "x": "139.737087",
        "y": "35.666974",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木三丁目",
        "town_kana": "ろっぽんぎ3ちょうめ",
        "x": "139.735452",
        "y": "35.663977",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "ç": "六本木四丁目",
        "town_kana": "ろっぽんぎ4ちょうめ",
        "x": "139.733837",
        "y": "35.665489",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木五丁目",
        "town_kana": "ろっぽんぎ5ちょうめ",
        "x": "139.735248",
        "y": "35.658358",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木六丁目",
        "town_kana": "ろっぽんぎ6ちょうめ",
        "x": "139.729932",
        "y": "35.659856",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木七丁目",
        "town_kana": "ろっぽんぎ7ちょうめ",
        "x": "139.726863",
        "y": "35.664751",
        "prefecture": "東京都",
        "postal": "1060032"
      }
    ]
  }
}


上記の中から「town」だけ列挙してみる

curl -s "http://geoapi.heartrails.com/api/json?method=searchByPostal&postal=1060032" | jq .response.location[].town
"六本木一丁目"
"六本木二丁目"
"六本木三丁目"
"六本木四丁目"
"六本木五丁目"
"六本木六丁目"
"六本木七丁目"


当初取得した値の中から 「town」,「x」, 「y」だけのJSONにしたい場合は map 使うと可能です

$ curl -s "http://geoapi.heartrails.com/api/json?method=searchByPostal&postal=1060032" | jq '.response.location | map({town: .town, x: .x, y: .y})'
[
  {
    "town": "六本木一丁目",
    "x": "139.740991",
    "y": "35.665082"
  },
  {
    "town": "六本木二丁目",
    "x": "139.737087",
    "y": "35.666974"
  },
  {
    "town": "六本木三丁目",
    "x": "139.735452",
    "y": "35.663977"
  },
  {
    "town": "六本木四丁目",
    "x": "139.733837",
    "y": "35.665489"
  },
  {
    "town": "六本木五丁目",
    "x": "139.735248",
    "y": "35.658358"
  },
  {
    "town": "六本木六丁目",
    "x": "139.729932",
    "y": "35.659856"
  },
  {
    "town": "六本木七丁目",
    "x": "139.726863",
    "y": "35.664751"
  }
]


まとめ

まとめるほど触ってませんが、まだまだ相当な機能やオプションをjqコマンドは備えています。
もっと使いこなせるようになりたいです。



関連エントリ

HomeBrew で jqをインストールしてみる

jsonデータを整形・絞り込みできるjqコマンドをmacOSにインストールしたかったので、HomeBrewでインストールしてみました。


インストール

以下を実行します。

$ brew install jq


実行時のログは以下の様な感じでした

$ brew install jq
==> Installing dependencies for git: pcre2
==> Installing git dependency: pcre2
==> Downloading https://homebrew.bintray.com/bottles/pcre2-10.32.mojave.bottle.tar.gz
Updating Homebrew...
######################################################################## 100.0%
==> Pouring pcre2-10.32.mojave.bottle.tar.gz
🍺  /usr/local/Cellar/pcre2/10.32: 224 files, 5.5MB
==> Installing git
==> Downloading https://homebrew.bintray.com/bottles/git-2.19.2.mojave.bottle.tar.gz
######################################################################## 100.0%
==> Pouring git-2.19.2.mojave.bottle.tar.gz
==> Caveats
Bash completion has been installed to:
  /usr/local/etc/bash_completion.d

zsh completions and functions have been installed to:
  /usr/local/share/zsh/site-functions

Emacs Lisp files have been installed to:
  /usr/local/share/emacs/site-lisp/git
==> Summary
🍺  /usr/local/Cellar/git/2.19.2: 1,520 files, 40.1MB
==> Caveats
==> git
Bash completion has been installed to:
  /usr/local/etc/bash_completion.d

zsh completions and functions have been installed to:
  /usr/local/share/zsh/site-functions

Emacs Lisp files have been installed to:
  /usr/local/share/emacs/site-lisp/git
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
Warning: Aleth (formerly cpp-ethereum) has been removed from Homebrew. Please install binary releases from https://github.com/ethereum/aleth/releases.
==> Auto-updated Homebrew!
Updated 3 taps (homebrew/core, homebrew/cask and ethereum/ethereum).
==> New Formulae

(中略)

==> Renamed Formulae
ark -> velero                              gnatsd -> nats-server                      todolist -> ultralist
confluent-oss -> confluent-platform        php72 -> php@7.2                           transmission -> transmission-cli
gloo-ctl -> glooctl                        resin-cli -> balena-cli
==> Deleted Formulae
apple-gcc42          gdnsd                js-test-driver       pdftoedn             rock                 tomcat@6
at-spi2-atk          gnome-doc-utils      ld64                 percona-server@5.6   ruby@1.8             typesafe-activator
at-spi2-core         go@1.4               libggz               php@5.6              ruby@2.3             varnish@4
cctools              go@1.8               libguess             php@7.0              safe                 whirr
cctools-headers      gradle@2.14          liblastfm            plan9port            scala@2.10           xmoto
compose2kube         gtk-engines          libutf               pldebugger           smlnj                zxing-cpp
cputhrottle          gtk-murrine-engine   lysp                 protobuf@2.5         solr@5.5
dsd                  guile@2.0            minisat              protobuf@2.6         solr@6.6
erlang@18            gv                   monax                pyexiv2              swig@3.04
ffmbc                hyper                node@6               rlvm                 tmux-cssh

==> Installing dependencies for jq: oniguruma
==> Installing jq dependency: oniguruma
==> Downloading https://homebrew.bintray.com/bottles/oniguruma-6.9.3.mojave.bottle.tar.gz
==> Downloading from https://akamai.bintray.com/f0/f00f8c6f8afd8875fed685a9190cb0c5e9b5ceef58ef1e489fb17a42bddc9672?__gda__=exp=1
######################################################################## 100.0%
==> Pouring oniguruma-6.9.3.mojave.bottle.tar.gz
🍺  /usr/local/Cellar/oniguruma/6.9.3: 17 files, 1.3MB
==> Installing jq
==> Downloading https://homebrew.bintray.com/bottles/jq-1.6.mojave.bottle.1.tar.gz
==> Downloading from https://akamai.bintray.com/71/71f0e76c5b22e5088426c971d5e795fe67abee7af6c2c4ae0cf4c0eb98ed21ff?__gda__=exp=1
######################################################################## 100.0%
==> Pouring jq-1.6.mojave.bottle.1.tar.gz
🍺  /usr/local/Cellar/jq/1.6: 18 files, 1MB
==> `brew cleanup` has not been run in 30 days, running now...
Removing: /Users/yuki/Library/Caches/Homebrew/git--2.19.2.mojave.bottle.tar.gz... (15.2MB)
Removing: /Users/yuki/Library/Caches/Homebrew/go--1.11.1.sierra.bottle.tar.gz... (140.5MB)
Removing: /Users/yuki/Library/Caches/Homebrew/icu4c--62.1.sierra.bottle.tar.gz... (25.4MB)
Removing: /Users/yuki/Library/Caches/Homebrew/jenv--0.4.4.tar.gz... (18KB)
Removing: /Users/yuki/Library/Caches/Homebrew/nkf--2.1.4.sierra.bottle.tar.gz... (157.8KB)
Removing: /Users/yuki/Library/Caches/Homebrew/node--11.2.0.sierra.bottle.tar.gz... (13.3MB)
Removing: /Users/yuki/Library/Caches/Homebrew/pcre2--10.32.mojave.bottle.tar.gz... (1.8MB)
Removing: /Users/yuki/Library/Caches/Homebrew/tree--1.8.0.sierra.bottle.tar.gz... (50.5KB)
Removing: /Users/yuki/Library/Caches/Homebrew/yarn--1.12.3.tar.gz... (1.1MB)
Removing: /Users/yuki/Library/Caches/Homebrew/z3-4.7.1.sierra.bottle.tar.gz... (27.3MB)
Removing: /Users/yuki/Library/Caches/Homebrew/openssl-1.0.2o_2.sierra.bottle.tar.gz... (3.7MB)
Removing: /Users/yuki/Library/Caches/Homebrew/gdbm-1.14.1_1.sierra.bottle.tar.gz... (182.5KB)
Removing: /Users/yuki/Library/Caches/Homebrew/gettext-0.19.8.1.sierra.bottle.tar.gz... (7.8MB)
Removing: /Users/yuki/Library/Caches/Homebrew/readline-7.0.3_1.sierra.bottle.tar.gz... (497.3KB)
Removing: /Users/yuki/Library/Caches/Homebrew/python@2-2.7.15.sierra.bottle.tar.gz... (18.3MB)
Removing: /Users/yuki/Library/Caches/Homebrew/wget-1.19.5.sierra.bottle.tar.gz... (1.3MB)
Removing: /Users/yuki/Library/Caches/Homebrew/cmake-3.11.3.sierra.bottle.tar.gz... (11.8MB)
Removing: /Users/yuki/Library/Caches/Homebrew/boost-1.67.0_1.sierra.bottle.tar.gz... (86.6MB)
Removing: /Users/yuki/Library/Caches/Homebrew/linkage.db... (48KB)
Removing: /Users/yuki/Library/Caches/Homebrew/sqlite-3.24.0.sierra.bottle.tar.gz... (1.7MB)
Removing: /Users/yuki/Library/Caches/Homebrew/nodebrew-1.0.0.tar.gz... (26.3KB)
Removing: /Users/yuki/Library/Caches/Homebrew/solidity-0.4.24.tar.gz... (1.1MB)
Removing: /Users/yuki/Library/Caches/Homebrew/portable-ruby-2.3.3_2.leopard_64.bottle.tar.gz... (12.4MB)
Removing: /Users/yuki/Library/Caches/Homebrew/ethereum-1.8.10.sierra.bottle.tar.gz... (64.2MB)
Removing: /Users/yuki/Library/Caches/Homebrew/portable-ruby-2.3.7.leopard_64.bottle.tar.gz... (12.4MB)
Removing: /Users/yuki/Library/Caches/Homebrew/go-1.10.3.sierra.bottle.tar.gz... (102.6MB)
Removing: /Users/yuki/Library/Caches/Homebrew/openssl-1.0.2o_1.sierra.bottle.tar.gz... (3.7MB)
Removing: /Users/yuki/Library/Caches/Homebrew/watch-3.3.15.sierra.bottle.tar.gz... (30.9KB)
Removing: /Users/yuki/Library/Caches/Homebrew/nmap-7.70.sierra.bottle.tar.gz... (7.1MB)
Removing: /Users/yuki/Library/Caches/Homebrew/libunistring-0.9.10.sierra.bottle.tar.gz... (1.4MB)
Removing: /Users/yuki/Library/Caches/Homebrew/ccache-3.4.2.sierra.bottle.tar.gz... (89.8KB)
Removing: /Users/yuki/Library/Caches/Homebrew/libidn2-2.0.5.sierra.bottle.tar.gz... (217.4KB)
Removing: /Users/yuki/Library/Caches/Homebrew/Cask/adoptopenjdk--11.0.1,13.tar.gz... (181.1MB)
Removing: /Users/yuki/Library/Logs/Homebrew/z3... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/tree... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/wget... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/libidn2... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/ccache... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/python@2... (3 files, 123.9KB)
Removing: /Users/yuki/Library/Logs/Homebrew/go... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/gdbm... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/cmake... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/boost... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/libunistring... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/icu4c... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/nkf... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/readline... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/sqlite... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/yarn... (100B)
Removing: /Users/yuki/Library/Logs/Homebrew/solidity... (6 files, 563.5KB)
Removing: /Users/yuki/Library/Logs/Homebrew/nodebrew... (104B)
Removing: /Users/yuki/Library/Logs/Homebrew/gettext... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/watch... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/node... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/jenv... (100B)
Removing: /Users/yuki/Library/Logs/Homebrew/nmap... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/openssl... (64B)
Removing: /Users/yuki/Library/Logs/Homebrew/ethereum... (64B)
Pruned 0 symbolic links and 2 directories from /usr/local


サンプル的に試してみる

例えば、以下の郵便番号による住所検索 API

curl で実行して、それを jq コマンドにパイプで渡してみます(queryパラメータの postal に指定している値が郵便番号)

curl "http://geoapi.heartrails.com/api/json?method=searchByPostal&postal=1060032" | jq


実行してみると以下のようになります。

$ curl "http://geoapi.heartrails.com/api/json?method=searchByPostal&postal=1060032" | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1882    0  1882    0     0  59882      0 --:--:-- --:--:-- --:--:-- 60709
{
  "response": {
    "location": [
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木一丁目",
        "town_kana": "ろっぽんぎ1ちょうめ",
        "x": "139.740991",
        "y": "35.665082",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木二丁目",
        "town_kana": "ろっぽんぎ2ちょうめ",
        "x": "139.737087",
        "y": "35.666974",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木三丁目",
        "town_kana": "ろっぽんぎ3ちょうめ",
        "x": "139.735452",
        "y": "35.663977",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木四丁目",
        "town_kana": "ろっぽんぎ4ちょうめ",
        "x": "139.733837",
        "y": "35.665489",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木五丁目",
        "town_kana": "ろっぽんぎ5ちょうめ",
        "x": "139.735248",
        "y": "35.658358",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木六丁目",
        "town_kana": "ろっぽんぎ6ちょうめ",
        "x": "139.729932",
        "y": "35.659856",
        "prefecture": "東京都",
        "postal": "1060032"
      },
      {
        "city": "港区",
        "city_kana": "みなとく",
        "town": "六本木七丁目",
        "town_kana": "ろっぽんぎ7ちょうめ",
        "x": "139.726863",
        "y": "35.664751",
        "prefecture": "東京都",
        "postal": "1060032"
      }
    ]
  }
}

これだけで、JSONを見やすく整形した状態で出力してくれます


jq コマンドは、かなり色々とできるはずなので、使い方調べながらもっと使いこなせるようになりたいです。

codility - Arrays OddOccurrencesInArray

codility の OddOccurrencesInArrayを解いてみます。

問題の概要

  • インプット(引数)
    • 整数の配列
      • 配列の要素の整数は奇数
      • 配列の要素数は奇数個
      • 要素の値同士のペアができるが、1要素だけペアができない値が含まれている
      • 例:[9, 3, 9, 3, 9, 7, 9]
  • アウトプット(戻り値)
    • ペアができなかった要素の値
    • 例にあげた引数の場合、7 がアウトプットになります


解答

以下の方針でやってます。

  • まず配列をソートする。
  • ソートされているので、基本的に偶数番目と奇数番目の要素の値は一致する(ペアになる)はず。
  • ペアにならなかったら、その偶数番目の値が1個だけしか存在しないものということになるので、それがそのまま答え
  • 最後の最後まで進んだら最終要素が答えとなる
import java.util.Arrays;

class Solution {

    public int solution(int[] array) {
        Arrays.sort(array);

        for (int i = 0; i < array.length - 1;){
            if (array[i] != array[i + 1]) {
                return array[i];
            }
            i = i + 2;
        }
        return array[array.length - 1];
    }
}


これでやってみると結果は以下の通りで、問題ないようです。

f:id:nini_y:20190830204710p:plain

ISBN-13を求める

ISBN や ISBN-13 の説明はWikipediaにお任せするとして

「接頭記号」 + 「グループ記号」 + 「出版者記号」 + 「書名記号」 (ハイフン除くと12桁)の値から
末尾に付与するチェックディジットまで含めたISBN-13の値を求めたい場合、
以下のようなメソッドを用意すればできます。

public final class ISBN13 {

    public static String generate(String src) {
        final String str12 = src.replace("-", "");

        if (str12.length() != 12) {
            throw new IllegalArgumentException(str12);
        }

        return str12 + checkDigit(str12);
    }

    private static int checkDigit(String str12) {
        // ウェイトの1および3
        final int[] weights = { 1, 3 };

        int sum = 0;
        for (int i = 0; i < str12.length(); i++) {
            // sum += Integer.valueOf(str12.charAt(i)) * weights[i & 1]   <- NG!!!
            sum += Character.getNumericValue(str12.charAt(i)) * weights[i & 1];
        }
        final int r = 10 - (sum % 10);
        if (r == 10) {
            return 0;
        }
        return r;
    }


試してみる

public static void main(String[] args) {
    System.out.println(ISBN13.generate("978479733720"));
    System.out.println(ISBN13.generate("978-4-7973-3720"));
    System.out.println(ISBN13.generate("978410109205"));
    System.out.println(ISBN13.generate("978030640615"));
    System.out.println(ISBN13.generate("978-0-30-640615"));
    System.out.println(ISBN13.generate("978316148410"));
}


出力結果

9784797337204
9784797337204
9784101092058
9780306406157
9780306406157
9783161484100


分かりにくいですが、ISBN-13を意味する13桁表現の値が取得できます。