リスト(配列)の中で隣り合う同じ値をグループ化する

リストでも配列でもいいけど、つまりこういうのを

[1, 1, 2, 2, 3, 1, 1]

こうしたい。

[[1, 1], [2, 2], [3], [1, 1]]

Ruby でやってみた。

def adjacent_group(ary)
  result = []
  current = nil
  ary.each do |x|
    if current.nil?
      current = x
      result << [x]
    elsif x == current
      result[-1] << x
    else
      current = x
      result << [x]
    end
  end
  result
end

p adjacent_group([1, 1, 2, 2, 3, 1, 1])
^o^ >ruby adjacent_group.rb
[[1, 1], [2, 2], [3], [1, 1]]

おなじく Python で。

def adjacent_group(lis):
    result = []
    current = None
    for x in lis:
        if current is None:
            current = x
            result.append([x])
        elif x == current:
            result[-1].append(x)
        else:
            current = x
            result.append([x])
    return result

print(adjacent_group([1, 1, 2, 2, 3, 1, 1]))
^o^ >python adjacent_group.py
[[1, 1], [2, 2], [3], [1, 1]]

ちょっとベタだな。もう少しスマートにいかないものか、と考えて Ruby の Array#inject が使えそうだと思いついた。

def adjacent_group(ary)
  ary.inject([[]]) do |a, x|
    if a[-1].empty? || x == a[-1][-1]
      a[-1] << x
    else
      a << [x]
    end
    a
  end
end

p adjacent_group([1, 1, 2, 2, 3, 1, 1])
^o^ >ruby adjacent_group2.rb
[[1, 1], [2, 2], [3], [1, 1]]

うまくいった。

さて、じゃ、Python ではどうか。reduce を使えば同じことができると考えたけど、Python の reduce は初期値がとれない。まあ、それはリストの頭に初期値をつけてやれば済む話ではあるけど、もうひとつ問題がある。Ruby の Array#inject はブロックをとれるけど、Python の reduce には関数を渡してやらなきゃいけないので、関数定義がひとつ増えてしまう。一行では書けないので lambda 式は使えない。
というわけで、上のようにベタに書いたほうがまだマシそうだ。何かいい書き方があったら、誰か教えてください。

[追記](9/25)

Ruby の2つ目の実装では、引数に空の配列を渡したときに期待通りに動作しない([[]] が返ってきてしまう)。そこでちょっと直したのがこれ。

def adjacent_group(ary)
  ary.inject([]) do |a, x|
    if !a.empty? && x == a[-1][-1]
      a[-1] << x
    else
      a << [x]
    end
    a
  end
end

p adjacent_group([1, 1, 2, 2, 3, 1, 1])
p adjacent_group([])
^o^ >ruby adjacent_group.rb
[[1, 1], [2, 2], [3], [1, 1]]
[]

これでいいだろう。

「リスト(配列)の中で隣り合う同じ値をグループ化する」への1件のフィードバック

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください