質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.99%

複数の数字入った配列を「人向けに」きれいにまとめて表示をさせる「きれいなロジック」を教えていただけませんか?

解決済

回答 7

投稿

  • 評価
  • クリップ 7
  • VIEW 1,303

takataka75

score 80

配列の中に、
複数の番号を格納しているとき、
それをまとめて表示する「綺麗なロジック」を
教えていただけないでしょうか?

data=["1","2","3","5","7","8","9","10","11","12","15","16"]


このようなデータがある時に

13,5,712,15,16


という
「人が見てきれいな」出力を作りたいのですが、
「綺麗なロジック」が思いつきません。

熟練の方、
短く、早く、美しいロジックを
ご教授願えないでしょうか?

私がrubyやVBAを使っているのでサンプルはrubyですが、
rubyやVBAが第一希望ですが、他の言語でもOKです。
ですがが、
「言語特有の(モジュール等を使った)ロジック」でない方がありがたいです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • tanat

    2017/08/02 00:35

    まずはご自身で作成されたコードを提示して、どこがきれいではないと感じているかを質問されては?

    キャンセル

  • 退会済みユーザー

    2017/08/03 10:49

    複数のユーザーから「やってほしいことだけを記載した丸投げの質問」という意見がありました
    「質問を編集する」ボタンから編集を行い、調査したこと・試したことを記入していただくと、回答が得られやすくなります。

回答 7

+7

PHPの例が出ていないので載せておきます。ネストした配列の初期化が容易にできるPHPらしい解法です。
if文や三項演算子などによる条件分岐が1つも無いのが特徴です。

整数配列の連続区間をハイフンで連結してグループ化する定番のアレ - Qiita

$data = [1, 2, 3, 4, 5, 8, 9, 10, 13, 14, 20, 22];
foreach ($data as $i => $v) {
    $pairs[$v - $i][isset($pairs[$v - $i])] = $v;
}
foreach ($pairs as $pair) {
    $ranges[] = implode('~', $pair);
}
echo implode(',', $ranges);

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/05 06:22

    そっか、別に隣と見比べるのでは無く、インデックスと値の差分でグループ化すれば良いだけだったんですね…。無駄に大変なことをしてしまった><

    キャンセル

  • 2017/08/05 14:27

    ありがとうございます。
    一番美しいかも

    キャンセル

  • 2017/08/05 14:56

    こんなのよく思いつくなぁ。。。

    キャンセル

  • 2017/08/18 11:04

    これすごいわー。
    今まさに必要なコードだったので助かりました。

    キャンセル

checkベストアンサー

+3

RubyであればEnumerable#chunk_whileが使えます。

上のリンク先のマニュアルにまさしくそのような出力をするための例が載っています。それを参考にまとめて書くと次のようになります。

data = ["1", "2", "3", "5", "7", "8", "9", "10", "11", "12", "15", "16"]
puts data.map(&:to_i)
         .chunk_while { |a, b| b == a.succ }
         .map { |x| x.size < 3 ? x : "#{x.first}#{x.last}"}
         .join(',')

ですが、chunk_whileのような隣同士をみてグルーピングするという関数やメソッドが用意されている言語は見当たらないです(group_byのようなグルーピングであればHaskellやLINQでみらえますが)。ということで、chunk_whileでしていることをEnumerable#injectで置き換えます。

data = ["1", "2", "3", "5", "7", "8", "9", "10", "11", "12", "15", "16"]
puts data.map(&:to_i)
         .inject([]) { |a, x| a.empty? || a.last.last.succ != x ?
                              a + [[x]] :
                              a.take(a.size - 1) + [(a.last + [x])] }
         .map { |x| x.size < 3 ? x : "#{x.first}#{x.last}"}
         .join(',')

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/02 10:12

    勉強になります。

    キャンセル

+3

ruby で書いてみました。
行数はすこし多いですが、他の言語にも翻訳しやすいかと思います。

# 連続した数値毎に分割する
def seq_array(array)
  ans = []
  loop {
    break if array.empty?
    # 数字が連続している先頭部分と残りに分割する
    div = array.partition.with_index {|x, idx| x == idx + array[0] }
    ans << div[0]
    array = div[1]
  }
  ans
end

def seq_array_to_s(array)
  array.map do |seq|
    (seq.length > 2) ? "#{seq[0]}#{seq[-1]}" : seq.join(',')
  end.join(',')
end

data = ["1", "2", "3", "5", "7", "8", "9", "10", "11", "12", "15", "16"]
seq_array = seq_array(data.map(&:to_i).sort)
puts seq_array_to_s(seq_array)

先頭の数字が連続している部分を取り出す処理をまず書きました。
それをループさせて残り部分が空になるまで繰り返します。(再帰で書き直すこともできるかもしれない)
すると、最初の数列全体を 連続した部分毎に分割することができます。
あとは、この分割数列を文字列化する処理を書きました。

分割処理と文字化処理が明確に分離しているので、文字列書式を変更するのは容易だと思います。

追記 2017-08-06 
seq_array を loop でなく、group_by で書き直すことができました。
さらに数値範囲を示す文字をパラメータで指定できるようにしてみました。

# 連続した数値毎に分割する
def to_seq_array(array)
  array.group_by.with_index {|x, idx| x - idx }.values
end

def to_range_array(seq_array, range_str = '〜')
  seq_array.map { |seq| seq.size > 2 ? "#{seq[0]}#{range_str}#{seq[-1]}" : seq }
end

data = ["1", "2", "3", "5", "7", "8", "9", "10", "11", "12", "15", "16"]
seq_array = to_seq_array(data.map(&:to_i))

range_array = to_range_array(seq_array)
puts range_array.join(',')

range_array = to_range_array(seq_array, "..")
puts range_array.join(' ')

実行結果:

$ ruby xxx.rb
13,5,712,15,16
1..3 5 7..12 15 16

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/06 14:21

    勉強になります。

    キャンセル

+1

楽しそうだったので書いてみました。perlで

$ perl -e '
>  @data=qw(1  3 5 7 8 9 10 11 12 15 16);
>  $i = 0;
>  ($start,$output) = ("","");
>  foreach (sort {$a <=> $b} @data) {
>   if ($start eq "") {
>    $start=$_;
>   }
>   if ($_ + 1 != $data[$i+1]) {
>    if ( $start == $_ ) {
>     $output .= sprintf "%d,",$_;
>    } elsif ( $start + 1 == $_ ) {
>     $output .= sprintf "%d,%d,",$start,$_;
>    } else {
>     $output .= sprintf "%d-%d,",$start,$_;
>    }
>    $start = "" ;
>   }
>   $i++;
>  }
>  printf "%s\n", $output ;
> '
1,3,5,7-12,15,16,

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/04 08:23

    ありがとうございました。
    参考にします。

    キャンセル

+1

あまり綺麗ではないですが,Ruby で書いてみました.

data = ["1","2","3","5","7","8","9","10","11","12","15","16"]

d = data.map{|x| x.to_i}.sort.uniq
ret = []
tmp = [d.shift]
d.each do |x|
  if tmp.last + 1 == x then
    tmp << x
  else
    ret << tmp
    tmp = [x]
  end
end
ret << tmp

puts ret.map{|x| 
  if x.length > 2 then
    "#{x.first}#{x.last}"
  elsif x.length > 1 then
    "#{x.first}, #{x.last}"
  else
    "#{x.first}"
  end
}.join(", ")
# 1~3, 5, 7~12, 15, 16

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/04 08:23

    ありがとうございました。
    参考にします。

    キャンセル

+1

Pythonで書いてみました。

data,lst,ans = ["1","2","3","5","7","8","9","10","11","12","15","16"],[],[]
for i,n in enumerate(data):
    if not lst or int(n)-int(lst[len(lst)-1]) == 1:
        lst.append(n)
        if i == len(data)-1:ans += lst
    else:
        if i == len(data)-1:lst.append(n)
        if len(lst) <= 2:ans += lst
        else:ans += ["%s~%s"%(lst[0],str(int(lst[0])+len(lst)-1))]
        lst.clear()
        lst.append(n)
print(ans)

#実行結果
['1~3', '5', '7~12', '15', '16']

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/04 08:23

    ありがとうございました。
    参考にします。

    キャンセル

+1

初心者ですが挑戦してみました。

# encoding: utf-8

data = ["1","2","3","5","7","8","9","10","11","12","15","16"]

data.map!(&:to_i)

d = (data.min..data.max).to_a
k = d - data

k.each do |_k|
  d[d.index(_k)] = '/'
end

d = d.join(',').split('/').map do |e|
  e = e.split(',').reject(&:empty?).map(&:to_i)
  e.size > 2 ? "#{e.min}~#{e.max}" : e
end

puts d.reject(&:empty?).join(', ')
# => 1~3, 5, 7~12, 15, 16

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/04 08:23

    ありがとうございました。
    参考にします。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.99%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • トップ
  • Rubyに関する質問
  • 複数の数字入った配列を「人向けに」きれいにまとめて表示をさせる「きれいなロジック」を教えていただけませんか?