0

I have two arrays; one of them, b, is probably a subset of the other, a; how can I check if b is a subsequence of a?

Basically, [3, 4, 5] would be a subsequence of (1..10).to_a, but not (1..10).to_a.shuffle.

Jeremy Rodi
  • 2,485
  • 2
  • 21
  • 40
  • can elements occur multiple times, e.g. `[3, 3, 4]`? – Stefan Jun 04 '14 at 19:01
  • 1
    Shouldn't the last paragraph say `[3, 4, 5] ⊆ (1..10).to_a`? – acsmith Jun 04 '14 at 19:02
  • @Stefan Depends on what you define equivalence to be; The arrays would have instances of classes that have their equivalence operators overwritten such that it would work well within a hash (i.e., two instances of the same class with the same values would be equal). – Jeremy Rodi Jun 04 '14 at 19:06
  • @acsmith probably, fixed. – Jeremy Rodi Jun 04 '14 at 19:06
  • Are you sure you don't just want to check that one array is a [subsequence](http://en.wikipedia.org/wiki/Subsequence) (specifically, a [substring](http://en.wikipedia.org/wiki/Substring)) of the other, rather than a [subset](http://en.wikipedia.org/wiki/Subset)? It's just that, since order matters, it sounds like you want a substring, and a lot of the answers you're getting use `sort` because people aren't reading the last sentence of the question. – Parthian Shot Jun 04 '14 at 19:08
  • @ParthianShot a substring is exactly what I'm looking for, thanks! – Jeremy Rodi Jun 04 '14 at 19:10
  • Brilliant question !! – Abs Jun 04 '14 at 19:35
  • Jeremy, I don't understand the question, which is frustrating, because others appear to comprehend. How can `[3, 4, 5]` be a substring of `(1..10).to_a` when neither are strings? Do you mean to apply `to_s` to each and not really mean `substring`, but mean the characters in the first appear in the second, in the same order? – Cary Swoveland Jun 04 '14 at 19:41
  • Updated my answer using each consecutive arr = [1,2,3] (1..10).to_a.each_cons(arr.size).include? arr – Abs Jun 04 '14 at 19:42
  • Why won't you correct the wording of your question? `[3, 4, 5] would be a substring of (1..10).to_a...` does not make sense, as those two objects are arrays, not strings. I assume you are aware that a substring is a string. Also, when I guess what you are looking for, I assume you want all the elements of `a` to appear in `b` in the same order, but I don't know if you mean them to all be adjacent in `b`. – Cary Swoveland Jun 04 '14 at 23:23
  • @CarySwoveland assume that a "string" is just basically an array of characters. In that context, substring makes sense. Basically, it would be like trying to find if `[?f, ?o, ?o]` is a substring of `%w(f o o b a r)` or `%w(h e l l o o f)`; the first is true, but the second isn't, even though it contains all the proper characters. While in this context I would be able to do `to_s` and check, in the original context, I would not. – Jeremy Rodi Jun 05 '14 at 00:15
  • OK, now I see what you want, but you still need to edit your question to clarify. Hundreds, maybe thousands of others may read your question in future. They shouldn't be required to wade through comments to figure out what you are asking. Among other things, please avoid the term "substring", as it is not correct. – Cary Swoveland Jun 05 '14 at 01:19
  • 1
    @Swoveland Actually, Wikipedia and the rest of the world disagrees: °A string may also denote more general arrays or other sequence (or list) data types and structures.° I'll grant that usage is less common, but it is correct. – Parthian Shot Jun 05 '14 at 12:06
  • @ParthianShot, what you are really arguing is that use of the term "substring" in this context is not incorrect. That may be true (but as a Rubiest raised to believe that a string is an instance of the class `String`, I have a problem with that), but it is beside the point. The relevant question is whether use of the term "substring" made the question clear and unambiguous to all readers, and there is indisputable evidence that it did not. – Cary Swoveland Jun 05 '14 at 13:16
  • @CarySwoveland Maybe so. I just didn't think the computer science definition of substring was rare enough to avoid/ And in any case the substitution he used, "subsequence" is too broad a categorization. A substring is a *contiguous* subsequence. Without that modifier, the answers will be incorrect and inefficient. But more broadly, someone else with the same question will find the accepted answer doesn't work for them. – Parthian Shot Jun 05 '14 at 13:50

8 Answers8

3
a = [1,2,3]
b = (1..10).to_a

a_sorted = a.sort
b.each_cons(a.size).any?{ |c| c.sort == a_sorted }

And original solution

b.each_cons(a.size).any?{ |c| c == a }
fl00r
  • 82,987
  • 33
  • 217
  • 237
  • Doesn't meet the requirement of order in this question. – Jeremy Rodi Jun 04 '14 at 19:10
  • I say "most likely" because `shuffle` randomly sorts the items of the array; it is possible that `[3,4,5]` would be a *ahem* substring of the array even after randomly sorting it, but it's not very likely. – Jeremy Rodi Jun 04 '14 at 19:13
  • I can't sort the arrays, because the arrays were already in a specific order. – Jeremy Rodi Jun 04 '14 at 19:16
  • Sort is not mutate original array. I am not sure I understand your pain about sorting here ;) – fl00r Jun 04 '14 at 19:18
  • I need to check that the specific order is the same; sorting them would change the order, and that's part of what I'm checking. – Jeremy Rodi Jun 04 '14 at 19:20
  • 2
    Ok, I am confused. What was wrong in original version? `b.each_cons(a.size).any?{ |c| c == a }`. In this case `[1,2,3] == [1,2,3]` and `[1,2,3] != [3,2,1]` – fl00r Jun 04 '14 at 19:22
  • i think this should stay +1 – Abs Jun 04 '14 at 19:51
  • I don't see why you have anything other than your "original solution". btw, it could also be written `([a] & b.each_cons(a.size).to_a).any?`. – Cary Swoveland Jun 05 '14 at 01:25
  • @CarySwoveland, author said that it was wrong solution ;) "Doesn't meet the requirement of order in this question" – fl00r Jun 05 '14 at 08:17
  • It's not your problem that the question was muddled. You made a perfectly reasonable interpretation and gave a good answer for that. Let it stand. Btw, if not obvious, "c" was for "crying". – Cary Swoveland Jun 05 '14 at 13:24
2

Using Enumerable each consecutive to iterate

arr = [1,2,3]
(1..10).to_a.each_cons(arr.size).include? arr
# => true

arr = [1,3,2]
(1..10).to_a.each_cons(arr.size).include? arr
# => false
Uri Agassi
  • 36,848
  • 14
  • 76
  • 93
Abs
  • 3,902
  • 1
  • 31
  • 30
  • The problem with this is that `[1,2,3] == [3,2,1]` is false; if the left side of the union had all of the elements of the right side of the union, then you could drop the right side; so, the second statement, `a.shuffle | b == a`, could be restated as `a.shuffle == a`, which is false in most cases. – Jeremy Rodi Jun 04 '14 at 19:25
  • 1
    This is a great solution – Farrukh Abdulkadyrov Jun 04 '14 at 19:45
  • `arr=[1,2,4]; [1,2,3,4].each_cons(arr.size).include? arr #=> false`. I can't tell from the question whether the asker expects that to be `true` or `false`. – Cary Swoveland Jun 04 '14 at 23:45
  • That's true, @fl00r, but you answered 10 minutes earlier. Don't you mean, "col"? – Cary Swoveland Jun 04 '14 at 23:48
1

You could use Enumerable's #find_index method with each element of the potential subset and ensure that the indices are in the same order in the potential superset.

Try this:

a = (1..10).to_a
b = [3,4,5]
# Use #compact to remove all nils
b_indices = b.map {|x| a.find_index(x)}.compact
b_indices == b_indices.sort # use #sort to ensure ordering
acsmith
  • 1,466
  • 11
  • 16
  • Very nice, efficient and reads well, but I'm beginning to think neither of our solutions are for the problem the asker had in mind. We'll see. – Cary Swoveland Jun 04 '14 at 23:39
1

Very simple and effective approach (not nil-safe):

leftovers = array.reduce(sub) { |a, e| e == a[0] ? a[1..-1] : a }
puts "sub is subsequence of array!" if leftovers.empty?

See: https://ruby-doc.org/core-2.2.0/Enumerable.html#method-i-reduce

0

You can use either Set or Array's default methods.

For Array look at it: How can I get the intersection, union, and subset of arrays in Ruby?

For Set: http://www.ruby-doc.org/stdlib-2.1.2/libdoc/set/rdoc/Set.html

Community
  • 1
  • 1
Zhomart
  • 702
  • 1
  • 7
  • 19
  • `Set`s don't allow values that are equal, and the `Array` question doesn't answer the specifics of this question. – Jeremy Rodi Jun 04 '14 at 19:09
0

Maybe not the most elegant solution, but should work for sure. Simple brute force:

def substring?(a, b)
  position = 0
  a.each_with_index do |v, i|
    if v == b[position] and (i + b.size - position) <= a.size
      position += 1
    elsif position == b.size
      return true
    else
      position = 0
    end
  end

  position == b.size
end

where b is your array and a is a candidate subset.

Jeremy Rodi
  • 2,485
  • 2
  • 21
  • 40
  • `a = [3,4,5]; b = (1..10).to_a; substring?(a,b) #=> false` Or I am missing something? – fl00r Jun 04 '14 at 19:40
  • My edit switched the parameters around; `a` is the array, and `b` is the substring. – Jeremy Rodi Jun 04 '14 at 19:41
  • @JeremyRodi, but why? `a = (1..5).to_a; b = [3,4,5]; substring?(a, b) #=> false` – fl00r Jun 04 '14 at 19:47
  • while `a.each_cons(b.size).any?{ |c| c == b } #=> true` – fl00r Jun 04 '14 at 19:48
  • @fl00r, my problem with `each_cons` is that it grabs the next `m` items of the array (`m` being the length of the substring) regardless of whether or not the substring would match that portion. I believe this code would perform faster, and use less memory. This is why I accepted this answer. – Jeremy Rodi Jun 04 '14 at 21:11
  • @JeremyRodi, No, each_cons won't generate all arrays because it is Enumerator. But yes, it is not as fast as non using Enumerators. But it is idiomatic Ruby. Also Farrukh's solution is not the fastest as well if it is so important for you :). – fl00r Jun 05 '14 at 08:22
  • @fl00r why not accept JetAbe's answer then? It does the same thing as yours, except it's even _more_ idiomatic. Which answer I choose should be the answer I used, and in this case I used this answer, even though both this answer and your answer did correctly answer the question. – Jeremy Rodi Jun 05 '14 at 15:06
  • @JeremyRodi it's up to you to choose answer you like, I only don't understand why did you say that my answer was wrong – fl00r Jun 05 '14 at 17:26
  • @fl00r I can't remember what the first answer was, but I believe that either I didn't read the code correctly, or it was an edit of your answer that didn't do what I wanted it to do – Jeremy Rodi Jun 05 '14 at 18:44
0

Here's another way. This solution conforms with my understanding of the problem, but it may not be what the asker had in mind. See my comments on the question. (Edit: upon clarification of the question, it is not what the asker wanted, but I'll leave it up for its possible educational value.)

Code

def subsequence?(a,b)
  c = b.map.with_index.to_h.values_at(*a)
  return false if c.any?(&:nil?)
  c == c.sort
end

Examples

subsequence?([3,4,5], (1..10).to_a)         #=> true
subsequence?([3,5,4], (1..10).to_a)         #=> false
subsequence?([3,4,5], (1..10).to_a.reverse) #=> false
subsequence?([3,4,5], [1,2,3,4,6])          #=> false
subsequence?([3,4,5], [3,4,2,5])            #=> true

Explanation

After computing

c = b.map.with_index.to_h.values_at(*a)

c.any?(&:nil?)

determines whether b contains all the elements of a. If it does

c == c.sort

checks to see if they are in the same order.

Example 1

a = [3,4,5]
b = (1..10).to_a

then

d = b.map.with_index.to_h
  #=> {1=>0, 2=>1, 3=>2, 4=>3, 5=>4, 6=>5, 7=>6, 8=>7, 9=>8, 10=>9}
c = d.values_at(*a)
  #=> [2, 3, 4]
c.any?(&:nil?)
  #=> false

So we see the values of a are contained in b, we need to see if they are in order:

c == c.sort
  #=> [2, 3, 4] == [2, 3, 4]
  #=> true

Example 2

a = [3,5,4]
b = (1..10).to_a

then

d = b.map.with_index.to_h
  #=> {1=>0, 2=>1, 3=>2, 4=>3, 5=>4, 6=>5, 7=>6, 8=>7, 9=>8, 10=>9}
c = d.values_at(*a)
  #=> [2, 4, 3]
c.any?(&:nil?)
  #=> false

so again we need to see if they are in order:

c == c.sort
  #=> [2, 4, 3] == [2, 3, 4]
  #=> false

Example 3

a = [3,5,4]
b = (1..10).to_a.reverse

then

d = b.map.with_index.to_h
  #=> {10=>0, 9=>1, 8=>2, 7=>3, 6=>4, 5=>5, 4=>6, 3=>7, 2=>8, 1=>9}
c = d.values_at(*a)
  #=> [7, 5, 6]
c.any?(&:nil?)
  #=> true
c == c.sort
  #=> false

Example 4

a = [3,5,4]
b = [1,2,3,4,6]

then

d = b.map.with_index.to_h
  #=> {1=>0, 2=>1, 3=>2, 4=>3, 6=>4}
c = d.values_at(*a)
  #=> [2, nil, 3]
c.any?(&:nil?) 
  #=> true

Example 5

a = [3,4,5]
b = [3,4,2,5]

then

d = b.map.with_index.to_h
  #=> {3=>0, 4=>1, 2=>2, 5=>3}
c = d.values_at(*a)
  #=> [0, 1, 3]
c.any?(&:nil?)
  #=> false
c == c.sort
  #=> true

Yet another solution

def subsequence?(a,b)
  ([a] & b.combination(a.size).to_a).any?
end
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
0

One more. This solution conforms with my understanding of the problem, but it may not be what the asker had in mind. See my comments on the question. (Edit: upon clarification of the question, it is not what the asker wanted, but I'll leave it up for its possible educational value.)

Code

def subsequence?(a,b)
  enum_a = a.each
  enum_b = b.each
  loop do
    av = enum_a.next
    loop do
      begin
        bv = enum_b.next
        break if (av == bv)
      rescue StopIteration
        return false
      end
    end  
  end    
  true
end 

Examples

subsequence?([3,4,5], (1..10).to_a)         #=> true
subsequence?([3,5,4], (1..10).to_a)         #=> false
subsequence?([3,4,5], (1..10).to_a.reverse) #=> false
subsequence?([3,4,5], (1..10).to_a.reverse) #=> false
subsequence?([3,4,5], [1,2,3,4,6])          #=> false
subsequence?([3,4,5], [3,4,2,5])            #=> true
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100