challenge 仲間はずれの判定

リストxsが渡されたときに
  • 全部の要素が同じ値である(例:[1, 1, 1, 1])、
  • 一つだけ仲間はずれがある(例:[1, 2, 1, 1, 1])、
  • その他
を識別する関数を作ってください。 また判定後に「全部の要素が同じ値」の場合にはその値、 「一つだけ仲間はずれがある」の場合にはその仲間はずれの値と多数派の値を複雑な処理なしに取得できるようにしてください。

型にうるさい言語のために:リストの中身は非負の整数だと仮定して負の値を未定義値代わりに使っても構いません。

追記:リストの長さは3以上であると仮定して構いません。(2個で異なる値の時にどちらが仲間はずれか決まらないので。) nobsunさん、noriさん、ご指摘ありがとうございます。

もっとスリム化できそうですがとりあえず。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.*;
public class NakamaHazure {
    public static void main(String[] args) {
        check(Arrays.asList(1,1,1,1));    // => [1]
        check(Arrays.asList(1,2,1,1,1));    // => [2,1]
        check(Arrays.asList(1,2,2,1,1));    // => []
        check(Arrays.asList(1,2,2,1,3));    // => []
    }
    private static void check(List<Integer> list) {
        HashSet<Integer> set = new HashSet<Integer>();
        HashSet<Integer> minor = new HashSet<Integer>();;
        Integer major = null;
        for (Integer i : list) {
            if (!set.add(i)) {
                if (major != null && major != i) {
                    System.out.println("[]");
                    return;
                }
                minor.remove(i);
                major = i;
            } else {
                minor.add(i);
            }
        }
        if (set.size() == 1) {
            System.out.println("[" + set.iterator().next() + "]");
        } else if (set.size() == 2) {
            System.out.println("[" + minor.iterator().next() + "," + major + "]");
        } else {
            System.out.println("[]");
        }
    }
}

array_count_values便利ですね。あえて使わないで作ってみる事で便利さを際実感

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$a[]= array(1,1,1,1);#1
$a[]= array(1,2,1,1,1);#2
$a[]= array(2,1,2,2);#その他
$a[]= array(1,2,2,2);#その他
$a[]= array(1,1,2,2);#その他
$a[]= array(1,1,2,3);#その他
function check($_array){
    $uniques = sizeof(array_unique($_array));
    switch($uniques){
        case(1):
            return array("仲間"=>array_pop($_array));
        case(2):
            sort($_array);
            #仲間はずれ
            if( $_array[0]!=$_array[1] ){
                return array( "仲間はずれ"=>array_shift($_array),"仲間"=>array_pop($_array) );
            }else if($_array[0]==$_array[1] && $_array[sizeof($_array)-1] != $_array[sizeof($_array)-2] ){
                return array( "仲間はずれ"=>array_pop($_array),"仲間"=>array_shift($_array) );
            }
        default:
            return "その他";
    }


}

foreach( $a as $b ){
    print_r( check($b) ).PHP_EOL;
}

Posted feedbacks - Nested

Flatten Hidden
効率は悪いけれど短い版。

実行例:
gosh> (classify '(1 1 1 1 1))
(homo 1)
gosh> (classify '(1 1 2 1 1))
(quasi-homo 1 2)
gosh> (classify '(2 1 1 1 1))
(quasi-homo 1 2)
gosh> (classify '(2 1 4 1 1))
hetero
gosh> (classify '())
hetero
1
2
3
4
5
6
7
8
9
(use gauche.collection)
(use util.match)

(define (classify lis)
  (match (group-collection lis)
    [((x ...))     `(homo ,(car x))]
    [((x ...) (y)) `(quasi-homo ,(car x) ,y)]
    [((y) (x ...)) `(quasi-homo ,(car x) ,y)]
    [_ 'hetero]))
ディクショナリ使ってる。
ちょっと頭悪い実装。
すべて同じ時 0、一個違うとき1、その他2を返します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def check(xs):
    c = {}
    for i in xs:
        if not c.has_key(i):
            c[i] = 0
        c[i] += 1
        if (len(c) == 3):
            return 2

    if len(c) == 1:
        return 0
    if len(c) == 2:
        a = c.items()[0][1]
        b = c.items()[1][1]
        if min(a,b) > 1:
            return 2
        return 1
    else:
        return 2
        

print check((1,1,1,1))
print check((1,1,1,2))
print check((1,1,2,1))
print check((1,1,2,3))
print check((1,2,2,1))
print check((1,2,2,1))
あー、全力で問題を勘違いしてますね。
その他の場合はnilを。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def classify(ary)
  hash = ary.inject(Hash.new(0)){|h,x| h[x]+=1; h }
  first = hash.keys.first
  case hash.keys.length
  when 1; [:homo, first]
  when 2
    second = hash.keys[1]
    hash[first]==1 ? [:quasi_homo, first, second] : [:quasi_homo, second, first]
  end
end

classify [1,1,1,1]              # => [:homo, 1]
classify [1,2,1,1,1]            # => [:quasi_homo, 2, 1]
classify [2,1,1,1,1]            # => [:quasi_homo, 2, 1]
classify [1,2,3,4]              # => nil
しまった![2,2,1,1,1]のケースが漏れてた
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def classify(ary)
  hash = ary.inject(Hash.new(0)){|h,x| h[x]+=1; h }
  first = hash.keys.first
  case hash.keys.length
  when 1; [:homo, first]
  when 2
    second = hash.keys[1]
    hash[first]==1 ? [:quasi_homo, first, second] : [:quasi_homo, second, first] if hash.values.include?(1)
  end
end

classify [1,1,1,1]              # => [:homo, 1]
classify [1,2,1,1,1]            # => [:quasi_homo, 2, 1]
classify [2,1,1,1,1]            # => [:quasi_homo, 2, 1]
classify [2,2,1,1,1]            # => nil
classify [2,4,1,1,1]            # => nil
classify [1,2,3,4]              # => nil
再帰版。リストを先頭から見ていき、条件をみたさなくなったら即座に終了する。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(defun classify (list)
  (labels ((rec (list alist)
             (let ((pair (assoc (car list) alist))
                   (len (length alist)))
               (cond ((null list)
                      (if (= len 1)
                          (values :homo (caar alist))
                          (let ((one (position 1 (mapcar #'cdr alist))))
                            (case one
                              (0 (values :quasi-homo (caar alist) (caadr alist)))
                              (1 (values :quasi-homo (caadr alist) (caar alist)))
                              (t nil)))))
                     ((and (>= len 2) (null pair))
                      (return-from classify nil))
                     ((null pair)
                      (rec (cdr list) (cons (cons (car list) 1) alist)))
                     (t
                      (incf (cdr pair))
                      (rec (cdr list) alist))))))
    (rec list ())))
(multiple-value-list (classify '(1 1 1 1)))   ; => (:HOMO 1)
(multiple-value-list (classify '(1 2 1 1 1))) ; => (:QUASI-HOMO 2 1)
(multiple-value-list (classify '(1 1 1 1 2))) ; => (:QUASI-HOMO 2 1)
(multiple-value-list (classify '(2 2 1 1 1))) ; => (NIL)
(multiple-value-list (classify '(2 4 1 1 1))) ; => (NIL)
(multiple-value-list (classify '(1 2 3 4)))   ; => (NIL)
(multiple-value-list (classify '()))          ; => (NIL)

	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <stdio.h>
enum { hetero, homo, quasi_homo };
typedef struct {
    int type;                   /* hetero, homo, quasi_homo */
    int one;
    int other;
} classify_result;

classify_result classify(int *p, int len) {
    int temp[] = {  -1,  0,  -1,    0 };
    /*            key1 val1 key2 val2 */
    int cur, i;
    static classify_result hetero_result = {hetero, -1, -1};
    classify_result res = hetero_result;

    if (len == 0) return hetero_result;
    for (i=0; i<len; i++) {
        cur = p[i];
        if (temp[0] == cur) temp[1]++;
        else if (temp[2] == cur) temp[3]++;
        else if (temp[0] == -1) { temp[0]=cur; temp[1]=1; }
        else if (temp[2] == -1) { temp[2]=cur; temp[3]=1; }
        else return hetero_result;
    }
    if (temp[2] == -1) { res.type = homo; res.one = temp[1]; }
    else if (temp[1] == 1) { res.type = quasi_homo; res.one = temp[0]; res.other = temp[2]; }
    else if (temp[3] == 1) { res.type = quasi_homo; res.one = temp[2]; res.other = temp[0]; }
    else return hetero_result;
    return res;
}
/* コードはここまで。以後テスト。 */

void print_classify_result(classify_result x) {
    switch(x.type) {
    case hetero:
        printf("<hetero>\n");
        break;
    case homo:
        printf("<homo %d>\n", x.one);
        break;
    case quasi_homo:
        printf("<quasi_homo %d %d>\n", x.one, x.other);
        break;
    default:
        printf("BUG!\n");
    }
}
int main() {
    {
        int ary[] = {1,1,1,1};
        print_classify_result(classify(ary, sizeof(ary)/sizeof(int)));
    }
    {
        int ary[] = {1,2,1,1,1};
        print_classify_result(classify(ary, sizeof(ary)/sizeof(int)));
    }
    {
        int ary[] = {1,1,1,1,2};
        print_classify_result(classify(ary, sizeof(ary)/sizeof(int)));
    }
    {
        int ary[] = {2,2,1,1,1,1};
        print_classify_result(classify(ary, sizeof(ary)/sizeof(int)));
    }
    {
        int ary[] = {2,4,1,1,1,1};
        print_classify_result(classify(ary, sizeof(ary)/sizeof(int)));
    }
    {
        int ary[] = {1,2,3,4};
        print_classify_result(classify(ary, sizeof(ary)/sizeof(int)));
    }
    {
        int ary[] = {};
        print_classify_result(classify(ary, sizeof(ary)/sizeof(int)));
    }
}
そのまま実装

*Main> classify [1,1,1,1,1]                                                                                                                
("homo",[1])
it :: ([Char], [Integer])                                                                                                                  
*Main> classify [1,1,2,1,1]
("quasi-homo",[1,2])                                                                                                                       
it :: ([Char], [Integer])
*Main> classify [2,1,1,1,1]                                                                                                                
("quasi-homo",[1,2])
it :: ([Char], [Integer])                                                                                                                  
*Main> classify [2,2,1,2,2]
("quasi-homo",[2,1])                                                                                                                       
it :: ([Char], [Integer])
*Main> classify [2,3,2,1,5]                                                                                                                
("hetero",[])
it :: ([Char], [Integer])            
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
otherList :: (Eq a) => [a] -> [a]
otherList (x:xs) = [ y |  y<-xs , y /= x ]

quasiHomo1 :: (Eq a) => [a] -> Bool
quasiHomo1     = (== 1) . length . otherList

quasiHomo2 :: (Eq a) => [a] -> Bool
quasiHomo2 xs  = (== 1) $ (length xs ) - (length $ otherList xs)

quasiHomoList :: (Eq a) => [a] -> [a]
quasiHomoList xs = map head $ [xs] ++ [otherList xs]

homo :: (Eq a) => [a] -> Bool
homo (xs) = xs == [ y | y <- xs , y == (head xs)]

classify :: (Eq a) => [a] ->  ([Char],[a])
classify xs | homo xs = ("homo" , [head xs])
            | quasiHomo1 xs = ("quasi-homo", quasiHomoList xs)
            | quasiHomo2 xs = ("quasi-homo", reverse $ quasiHomoList xs)
            | otherwise = ("hetero",[])
すみません、間違えて評価を下げてしまいました。
評価は後から何度でも変更できますよ。
マイナス評価を取り消したいのならは0を押せばOKです。
ただ、ログアウトしている状態で投票はできないはずですが…
おお!なるほど。
どうもありがとうございます。

前のコメントは恥ずかしかったのでログアウトしてから書き込みました。
非負整数に限定せず、文字列(日本語を含む)もOK
% awk -f nakama.awk
1 1 1 1
全部の要素が同じ値: 1
1 2 1 1 1
一つだけ仲間はずれがある: 仲間はずれ(2),多数派(1)
1 2 1 2
その他
a a a a b a a
一つだけ仲間はずれがある: 仲間はずれ(b),多数派(a)
cat cat cat        
全部の要素が同じ値: cat
Wii Wii Wii PS3 Wii
一つだけ仲間はずれがある: 仲間はずれ(PS3),多数派(Wii)
渋谷 赤坂 赤坂 赤坂
一つだけ仲間はずれがある: 仲間はずれ(渋谷),多数派(赤坂)
★ ★ ★ ★ ★
全部の要素が同じ値: ★
☆ ☆ ☆ ★ ☆
一つだけ仲間はずれがある: 仲間はずれ(★),多数派(☆)
☆ ★ ☆ ★ ★
その他
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
{
	delete xs
	for (i=1;i<=NF;i++) xs[i] = $i
	result = nakama(xs)
	if (result ~ /unanimity/) {
		printf("全部の要素が同じ値: %s\n", MAJORITY)  ## %dでなく%s
	} else if (result ~ /left alone/) {
		printf("一つだけ仲間はずれがある: 仲間はずれ(%s),多数派(%s)\n", MINORITY, MAJORITY)  ## %dでなく%s
	} else if (result ~ /(empty|two different values|other)/) {
		printf("その他\n")
	}
}

function nakama(xs,  i,len,x_count,member,m,m1,m2)
{
	len = 0 ; for (i in xs) len++
	if (len == 0) return "empty"
	if (len == 1) {
		MAJORITY = xs[1] ; MINORITY = "" ; return "unanimity"
	}

	delete x_count

	member = ":" xs[1] ":"
	x_count[xs[1]] = 1

	for (i=2; i<=len; i++) {
		x = xs[i]

		if (member !~ ":" x ":") member = member x ":"
		x_count[x]++
	}

	gsub(/(^:|:$)/,"",member)
	member_count = split(member,m,":")
	if (member_count == 1) {
		MAJORITY = m[1] ; MINORITY = "" ; return "unanimity"
	} else if (member_count == 2) {
		m1 = m[1] ; m2 = m[2]
		if (x_count[m1] == 1) {
			if (x_count[m2] == 1) {
				MAJORITY = m1 ; MINORITY = m2 ; return "two different values"
			} else {
				MAJORITY = m2 ; MINORITY = m1 ; return "left alone"
			}
		} else if (x_count[m2] == 1) {
			MAJORITY = m1 ; MINORITY = m2 ; return "left alone"
		} else {
			MAJORITY = MINORITY = "" ; return "other"
		}
	} else {
		MAJORITY = MINORITY = "" ; return "other"
	}
}
入力リストの長さが0,1,2のときの扱いを
いれたのでちょっとスマートじゃないけど
効率は悪くない(はず)
データ型のフィールドラベルについては
Haskell98の仕様に記述があるので参考にどうぞ。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
data Case a = Zero 
            | One {first :: a}
            | Two {first, second :: a}
            | Homo {homo :: a} 
            | QuasiHomo {homo, strange :: a}
            | Hetero
  deriving Show

classify :: Eq a => [a] -> Case a
classify []     = Zero
classify (x:xs) = judge (One x) xs

judge c [] = c
judge (One x) (y:ys) 
      | x == y    = judge (Homo x) ys
      | otherwise = judge (Two x y) ys
judge (Homo x) (y:ys)
      | x == y    = judge (Homo x) ys
      | otherwise = judge (QuasiHomo x y) ys
judge (Two x y) (z:zs)
      | x == z    = judge (QuasiHomo x y) zs
      | y == z    = judge (QuasiHomo y x) zs
judge (QuasiHomo x y) (z:zs)
      | x == z    = judge (QuasiHomo x y) zs
judge _ _         = Hetero

{-
*Main> classify [1,1,1,1,1]
Homo {homo = 1}
*Main> classify [1,2,1,1,1]
QuasiHomo {homo = 1, strange = 2}
*Main> classify [1,2,1,2,1]
Hetero
-}
無限リストでも hetero であることがわかれば止まるのがよいですね。
強引に mapAccumL 使ってみました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
judge c xs = fromJust $ msum $ ys ++ [Just s] where
  (s, ys) = mapAccumL f c xs
  f (One x) y
    | x == y    = (Homo x,        Nothing)
    | otherwise = (Two x y,       Nothing)
  f (Homo x) y
    | x == y    = (Homo x,        Nothing)
    | otherwise = (QuasiHomo x y, Nothing)
  f (Two x y) z
    | x == z    = (QuasiHomo x y, Nothing)
    | y == z    = (QuasiHomo y x, Nothing)
  f (QuasiHomo x y) z
    | x == z    = (QuasiHomo x y, Nothing)
  f _ _         = (Hetero,        Just Hetero)
サーチする中で4つ状態があるんですよね。それぞれをそのまま関数にしてみました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
type 'a t = H of 'a | Q of 'a * 'a | Other

let classify (x::xs) =
  let rec one a = function
  | [] -> H a
  | x::xs when x = a -> one' a xs
  | x::xs -> two a x xs
  and one' a = function
  | [] -> H a
  | x::xs when x = a -> one' a xs
  | x::xs -> two' a x xs
  and two a b = function
  | [] -> Q (a, b)
  | x::xs when x = a -> two' a b xs
  | x::xs when x = b -> two' b a xs
  | _ -> Other
  and two' a b = function
  | [] -> Q (a, b)
  | x::xs when x = a -> Q (a, b)
  | _ -> Other
  in
  one x xs
まだまだ感が否めない。。。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def lookfor(target):
    count = {}
    for v in target:
        if not count.has_key(v):
            count[v] = target.count(v)
    #2つ以上要素があって、1回出現の要素がない時はその他
    #要素が2つより多い場合もその他
    if (1 not in count.values() and len(count.keys()) >= 2)\
            or len(count.keys()) > 2:
        print '%s その他' % (target)
    #要素が1つしかない時は全部同じ
    elif len(count.keys()) == 1:
        print '%s 全て同じ' % (target)
    #この時点で1回出現の要素と多数出現の要素の2つが残っている
    else:
        for k, v in count.iteritems():
            if v == 1:
                outofgroup = k
            else:
                group = k
        print '%s 多数派: %s, 仲間外れ: %s' % (target, group, outofgroup)
戻り値は配列で、要素数が
1なら全部同じで[共通項]
2なら仲間はずれ有りで[共通項, 仲間ハズレ]
0ならその他
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Array.prototype.classify = function () {
  if(this.length == 0) return [];
  var a , b, ac = 0, bc = 0;
  for(var i=0, n=this.length; i<n; i++) {
    if(ac == 0 || a == this[i]) {a = this[i]; ac++; }
    else if(bc == 0 || ac == 1 && b == this[i]) {b = this[i]; bc++;}
    else return [];
  }
  return a == void 0 ? [b] : 
         b == void 0 ? [a] :
         ac < bc     ? [b, a] : [a, b];
};

alert([2,2,1,2].classify());// => [2, 1]
alert([1,1,1].classify());  // => [1]
alert([2, 1, 1, 1].classify()); // => [1, 2]
alert([2, 1, 0, 1].classify()); // => []
alert([1, 2, 1, 2, 1].classify()); // => []
間違えた。これでは値にundefinedを扱えませんでした。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
   }
-  return a == void 0 ? [b] :
-         b == void 0 ? [a] :
-         ac == bc    ? [] :
-         ac < bc     ? [b, a] : [a, b];
+  return ac == 0  ? [b] :
+         bc == 0  ? [a] :
+         ac == bc ? [] :
+         ac < bc  ? [b, a] : [a, b];
 };
len(xs)==0のときは全部同じなのかね?
集合論的にはどうなんでしょうね。

辞書を使って脱力実装。

最初は辞書を返して終わりにしようと思ったけど、気が引けたのでやめた。

しかも書いてみたら辞書を生成する部分より長いしw。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def classify_r(xs):
  if len(xs) == 0 :
    return dict()
  else:
    d = classify_r(xs[1:])
    c = d.get(xs[0], 0)
    d.update({xs[0]: c + 1})
    return d

def classify(xs):
  d = classify_r(xs)
  if len(d) == 0:
    return "Empty"
  elif len(d) == 1:
    return d.keys()[0]
  elif len(d) == 2:
    a = d.keys()[0]
    b = d.keys()[1]
    if d[a] >= d[b]:
      return (a, b)
    else:
      return (b, a)
  else:
    return "Mixed"
以下を返値とする。
全部の要素が同じ値である: 1
一つだけ仲間はずれがある: 2
その他: 0

また、dup_check.majorとdup_check.minorで必要な値を取り出せる。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
try: set
except NameError: from sets import Set as set

def dup_check(xs):
	for attr in ['major', 'minor']:
		if hasattr(dup_check, attr): delattr(dup_check, attr)
	L = list(set(xs))
	if len(L) == 1:
		dup_check.major = L[0]
		return 1
	elif len(L) == 2:
		L.sort(key=xs.count)
		dup_check.minor = L[0]
		dup_check.major = L[1]
		return 2
	else:
		return 0

print 'ret =', dup_check([1,1,1,1])
print 'uniq =', dup_check.major
print
print 'ret =', dup_check([1,2,1,1,1])
print 'major =', dup_check.major
print 'minor =', dup_check.minor
print
print 'ret =', dup_check([1,2,3,4])

出力:
ret = 1
uniq = 1

ret = 2
major = 1
minor = 2

ret = 0