Group Abstract Group Abstract

Message Boards Message Boards

2
|
4.6K Views
|
6 Replies
|
9 Total Likes
View groups...
Share
Share this post:

Test if a list contains another list, observing multiplicities?

Posted 6 years ago

As a novice I am looking for an elegant and fast function that tells me if the first list contains all elements of a second list, observing multiplicities:

TestQ[{a,b,c},{c,b}] -> True
TestQ[{a,b,c},{c,c}] -> False
TestQ[{a,b,c,c},{c,a,c}] -> True

Thank you all, Thomas

POSTED BY: Thomas Vogler
6 Replies

My solution would be as follows:

ClearAll[ContainsAllMultiplicities]
ContainsAllMultiplicities[l1_List, l2_List] := 
 Module[{c1, c2, m, il1, il2},
  c1 = {1, #} & /@ Counts[l1];
  c2 = {2, #} & /@ Counts[l2];
  m = Merge[{c1, c2}, Identity];
  m = Values[m];
  If[MemberQ[m, {{2, _}}],
   False
   ,
   m = DeleteCases[m, {{1, _}}];
   m = SortBy[First] /@ m;
   m = m[[All, All, 2]];
   AllTrue[m, GreaterEqual @@ # &]
   ]
  ]

Some test code:

ContainsAllMultiplicities[{a, b, c}, {c, b}]
ContainsAllMultiplicities[{a, b, c}, {c, c}]
ContainsAllMultiplicities[{a, b, c}, {c, c, d}]
ContainsAllMultiplicities[{a, b, c, c}, {c, a, c}]
ContainsAllMultiplicities[{a, b, c, c}, {c, a, c, b}]
ContainsAllMultiplicities[{a, b, c, c}, {c, a, c, b, c}]

returning:

True
False
False
True
True    
False

Should work fast, especially for large listsÂ…

POSTED BY: Sander Huisman
Posted 6 years ago

ContainsAll does not consider multiplicities so maybe

containsQ[l1_List, l2_List] := ContainsAll[Tally[l1], Tally[l2]];

Note that ContainsAll[list, {}] always returns True.

POSTED BY: Rohit Namjoshi

Perhaps look at ContainsAll?

POSTED BY: Sander Huisman

I'm sure this is not optimal, but one might sort both and try matching the first to a pattern that alternates blank null sequences with the second.

testQ[l1_List, l2_List] :=
 Module[
  {sl1 = Sort[l1], sl2 = Sort[l2], pat},
  pat = Riffle[ConstantArray[BlankNullSequence[], Length[sl2] + 1], 
    sl2];
  MatchQ[sl1, pat]]

testQ[{a, b, c}, {c, b}]
testQ[{a, b, c}, {c, c}]
testQ[{a, b, c, c}, {c, a, c}]

(* Out[1384]= True

Out[1385]= False

Out[1386]= True *)
POSTED BY: Daniel Lichtblau
Posted 6 years ago

But you erroneously give containsQ[{a,b,c,b},{b}] -> False

Sorry about that. I misinterpreted the requirement

observing multiplicities

to mean that the multiplicities in the superset/subset lists must match.

POSTED BY: Rohit Namjoshi

@Daniel Thank you very much. But you are slower by a factor of two than my simpleminded solution.

@Rohit: Thank you as well. But you erroneously give containsQ[{a,b,c,b},{b}] -> False

My Solution and test:

test1Q[a_, {}] := True
test1Q[a_, b_] := And[
  MemberQ[a, First[b]], 
  test1Q[
   DeleteCases[a, First[b], Infinity, 1], 
   Rest[b]
   ]
  ]

test2Q[a_, b_] := ContainsAll[Tally[a], Tally[b]]

test3Q[l1_List, l2_List] := 
 Module[{sl1 = Sort[l1], sl2 = Sort[l2], pat}, 
  pat = Riffle[ConstantArray[BlankNullSequence[], Length[sl2] + 1], 
    sl2];
  MatchQ[sl1, pat]]

runWithPredicate[test_] :=
     {
      string = "ZieglersGiantBar";
      letters = Characters[ToLowerCase[string]];
      words = WordList["KnownWords", IncludeInflections -> True];
      set = Timing[
        Select[words, test[letters, Characters[ToLowerCase[#]]] &]
        ];
      First[set],
      Length[words],
      Length[set[[2]]]
      }

     runWithPredicate[test1Q]

    {1.51693, 135364, 2698}

    runWithPredicate[test2Q]

    Out[111]= {3.43823, 135364, 187}

    runWithPredicate[test3Q]

    Out[110]= {3.15315, 135364, 2698}

What really makes me wonder, is that my simpleminded, straightforward recursive approach outperforms your solutions by a factor of two.

I offer a virtual beer for anyone who can do significantly better...

And I like to know what 2000 other words Knuth came up with: Cf. https://en.wikipedia.org/wiki/Donald_Knuth Section Early Life

POSTED BY: Thomas Vogler
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard