Are these lists equal?

19

As you may very well know python has lists. As you might not know these lists can contain themselves.

a = []
a.append(a)

Python 2

Python 3

These are cool and there are lots of interesting things you can do with them, however you cannot compare them.

a = []
a.append(a)
b = []
b.append(b)
a == b

Python 2

Python 3

Task

Your job is to write a function in Python (or any language that can handle python objects directly) that will take two lists that may contain themselves and compare them.

Two lists are equal if they are the same length and there does not exist sequence of numbers such that indexing both of the lists by that sequence results in two objects that are not equal under this definition of equal. All non-list objects contained in a list will be python integers for simplicity and should be compared with python's builtin equality for integers.

Your program should not rely on the recursion depth of python to determine if a list is infinitely deep. That is:

def isInfinite(a,b):
 try:
  a==b
  return False
 except RunTimeError:
  return True

Is not a valid way of determining if two lists are self referential.

Testcases

Assumes you define a function equal

a = []
a.append(a)
b = []
b.append(b)
print(equal(a,b))

True

a = []
b = []
a.append(b)
b.append(a)
print(equal(a,b))

True

a = []
b = []
a.append(1)
a.append(b)
b.append(1)
b.append(a)
print(equal(a,b))

True

a = []
a.append(a)
b = [a]
print(equal(a,b))

True

a = []
b = []
c = []
a.append(b)
b.append(c)
c.append(a)
equal(a,b)

True

a=[1,[2]]
b=[1,[2,[1]]]
a[1].append(a)
b[1][1].append(b[1])

True

a = []
a.append(a)
b = [1]
b.append(a)
c = [1]
c.append([c])
print(equal(b,c))

False

a = []
b = []
a.append(1)
a.append(b)
b.append(a)
b.append(1)
print(equal(a,b))

False

a = []
b = []
a.append(a)
b.append(b)
b.append(b)
print f(a,b)

False

Post Rock Garf Hunter

Posted 2017-04-20T18:18:42.183

Reputation: 55 382

17

As a side note to potential voters: Note that generally language specific challenges are frowned upon except for in certain circumstances (like tasks that are only interesting in certain languages). IMO, this is a wonderful example of a language-specific challenge.

– James – 2017-04-20T18:25:13.753

@WheatWizard That's not exactly enough -- the nested lists need to also be the same lengths as well. – xnor – 2017-04-20T19:09:51.957

@WheatWizard You actually can compare them. In Python, you only get a "recursion limited exceeded" if they aren't equal. https://tio.run/nexus/python2#@5@oYKsQHcuVqJdYUJCal6KRqMmVBBFKQhIqKMrMK1FItLVN@v8fAA

– mbomb007 – 2017-04-20T19:10:10.697

@mbomb007 Thats because python by default compares the references. If you have two identical objects that have different references it fails, hence the challenge. – Post Rock Garf Hunter – 2017-04-20T19:12:58.297

Additional test case: a = [] b = [] c = [] a.append(b) b.append(c) c.append(a) f(a, b). Tests loops of size > 1. – isaacg – 2017-04-20T19:49:41.310

I don't know if it is OK to golf this in another language, because the code will be obviously longer than python solution... – Keyu Gan – 2017-07-12T03:40:46.137

Should the submissions be reusable? E.g. should it be possible to call the function many times and always get the correct answer? – isaacg – 2017-07-12T20:31:55.950

@isaacg Yes they should. – Post Rock Garf Hunter – 2017-07-12T20:35:11.277

2Can you extend this challenge to all languages where lists can contain themselves? – CalculatorFeline – 2017-07-12T21:38:41.557

Is it okay to destroy the input lists? Or should they remain unmutated? – Akshat Mahajan – 2017-07-12T23:38:04.210

@AkshatMahajan They should remain unmuted. – Post Rock Garf Hunter – 2017-07-12T23:38:52.607

@KeyuGan, Python code can be shorter in non-Python languages!

– Not a tree – 2017-07-13T01:26:39.533

Technically, in a = [];a.append(a);b = [a], they're not equal. The infinity of a would be one less than the infinity of b. If you can assume they're not equal, then I believe repr(a)==repr(b) would work (tested in 3.6.1). – zgrep – 2017-07-13T04:24:00.010

@zgrep Since the infinity we are concerned with is cardinal not ordinal they are the same size. – Post Rock Garf Hunter – 2017-07-13T04:24:51.170

@WheatWizard Ah, I see. Okay. – zgrep – 2017-07-13T04:25:17.710

Answers

9

Python 2, 94 bytes

g=lambda c,*p:lambda a,b:c in p or all(map(g((id(a),id(b)),c,*p),a,b))if a>[]<b else a==b
g(0)

Try it online!

An improvement on isaacg's very clever solution of storing the id pairs of lists being processed and declaring them equal if the same comparison comes up on a lower level.

The recursive step all(map(...,a,b)) says that a and b are equal if all corresponding pair of elements in them are equal. This works nicely to reject unequal-length because map pads the shortest list with None, unlike zip which truncates. Since none of the actual lists contain None, these padded lists will always be rejected.

xnor

Posted 2017-04-20T18:18:42.183

Reputation: 115 687

What is the purpose of the , after the c? – Post Rock Garf Hunter – 2017-04-20T21:01:41.613

It makes it a tuple. – mbomb007 – 2017-04-20T21:02:00.273

a=[];a+=[a,1];b=[];b+=[b,2];f(a,b) overflows the stack, and a=[1];b=[2];f(a,b);f(a,b) looks like a reusability problem. – Anders Kaseorg – 2017-07-12T18:09:09.073

@AndersKaseorg I see, mutating the list was asking for trouble. I think this fixes it. – xnor – 2017-07-12T23:35:40.350

1@AndersKaseorg And I see you wrote basically the same function-in-a-function solution. There's a 95-byte solution without that: f=lambda a,b,p=[0]:p[0]in p[1:]or all(map(f,a,b,[[(id(a),id(b))]+p]*len(a)))if a>[]<b else a==b. Maybe there's a nicer way to handle the map. – xnor – 2017-07-12T23:55:03.667

@xnor So I had a really bad idea

– Anders Kaseorg – 2017-07-13T01:54:13.063

5

Python, 233 218 197 217 bytes

d=id
def q(a,b,w):
 w[(d(a),d(b))]=0
 if d(a)==d(b):return 1
 if(a>[]and[]<b)-1:return a==b
 if len(a)!=len(b):return 0
 for x,y in zip(a,b):
  if((d(x),d(y))in w or q(x,y,w))-1:return 0
 return 1
lambda a,b:q(a,b,{})

The anonymous function on the last line performs the desired function.

This is still in the process of being golfed, I just wanted to show that this is possible.

Essentially, we put an entry in w if we're working on a given check. Two things are equal if they're the same object, if they're not lists, and they're equal, or if all their elements are either equal or being worked on.

isaacg

Posted 2017-04-20T18:18:42.183

Reputation: 39 268

Can't you use a>[] instead of i(a,list)? – mbomb007 – 2017-04-20T19:48:55.153

@mbomb007 This was written before the "Everything is lists or ints" rule was added. Will update. – isaacg – 2017-04-20T19:59:27.093

You can use a>[]<b and len(a)-len(b) – mbomb007 – 2017-04-20T20:03:57.940

@ETHproductions Oh, his byte count is wrong. That's why – mbomb007 – 2017-04-20T20:13:10.633

Can d(a)==d(b) be a is b? That would cut two uses of d. – xnor – 2017-04-20T20:15:02.433

You can do w[d(a),d(b)]=0 instead of w[(d(a),d(b))]=0. – Post Rock Garf Hunter – 2017-04-20T20:19:27.113

Test suite – mbomb007 – 2017-04-20T20:20:48.397

The 4th and 5th lines can be combined to make if(a>[]<b)-1or a is b:return a==b – Post Rock Garf Hunter – 2017-04-20T20:24:56.867

In fact the 6th line can also be combined because python checks that the lists are the same size before it starts the recursion. if(a>[]<b)-1or len(a)-len(b)or a is b:return a==b – Post Rock Garf Hunter – 2017-04-20T20:27:46.357

... and the last 3 lines can be combined into a list comprehension. link

– Post Rock Garf Hunter – 2017-04-20T20:31:23.887

3 more bytes – Post Rock Garf Hunter – 2017-04-20T20:36:02.587

Another 3 more by putting it on one line – mbomb007 – 2017-04-20T20:58:51.983

a=[1];b=[2];q(a,b);q(a,b) looks like a reusability problem. – Anders Kaseorg – 2017-07-12T18:16:07.067

@AndersKaseorg Is there an expectation of re-usability? If so, I can clear w after each run, but I don't think that's a requirement. – isaacg – 2017-07-12T19:03:29.350

Do function submissions have to be reusable? – Anders Kaseorg – 2017-07-12T20:00:07.803