Skip to content

Commit de25204

Browse files
authored
Merge pull request #42 from miguelHx/miguel_2.6_palindrome
Miguel 2.6 - Palindrome [Python]
2 parents 06f0a43 + 5120f6d commit de25204

File tree

1 file changed

+267
-0
lines changed

1 file changed

+267
-0
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
"""
2+
Python version 3.7.0
3+
2.6 - Palindrome
4+
Implement a function to check if a linked list
5+
is a palindrome.
6+
7+
"""
8+
import unittest
9+
10+
11+
class Node:
12+
def __init__(self, d: int):
13+
self.data = d
14+
self.next = None
15+
16+
def __repr__(self):
17+
return self.__str__()
18+
19+
def __str__(self):
20+
return '<Node Value: {}>'.format(self.data)
21+
22+
def __eq__(self, other: object):
23+
if not isinstance(other, Node):
24+
return NotImplemented
25+
return self.data == other.data
26+
27+
28+
class LinkedList:
29+
def __init__(self, *numbers: int):
30+
self.head = None
31+
self.tail = None
32+
self.size = 0
33+
for num in numbers:
34+
self.append_to_tail(num)
35+
36+
def append_to_tail(self, e) -> None:
37+
if isinstance(e, int):
38+
self._append_num(e)
39+
elif isinstance(e, Node):
40+
self._append_node(e)
41+
42+
def _append_num(self, d: int) -> None:
43+
if self.head is None:
44+
self.head = Node(d)
45+
self.tail = self.head
46+
else:
47+
end = Node(d)
48+
self.tail.next = end
49+
self.tail = end
50+
self.size += 1
51+
52+
def _append_node(self, n: Node) -> None:
53+
if self.head is None:
54+
self.head = n
55+
self.tail = self.head
56+
else:
57+
end = n
58+
self.tail.next = end
59+
self.tail = end
60+
self.size += 1
61+
62+
def append_to_head(self, d: int) -> None:
63+
new_head = Node(d)
64+
new_head.next = self.head
65+
if self.head is None:
66+
# if list is empty and we add
67+
# out first element, head AND tail
68+
# must point to same node
69+
self.tail = new_head
70+
self.head = new_head
71+
self.size += 1
72+
73+
def get_node_at(self, index: int) -> Node:
74+
if index < 0 or index >= self.size:
75+
raise IndexError('list index out of range')
76+
n = self.head
77+
for i in range(self.size):
78+
if i == index:
79+
return n
80+
n = n.next
81+
82+
def get_value_at(self, index: int) -> int:
83+
if index < 0 or index >= self.size:
84+
raise IndexError('list index out of range')
85+
n = self.head
86+
for i in range(self.size):
87+
if i == index:
88+
return n.data
89+
n = n.next
90+
91+
def pop_head(self) -> Node:
92+
if self.head is None:
93+
raise IndexError('no head to pop')
94+
h = self.head
95+
h.next = None
96+
self.head = self.head.next
97+
self.size -= 1
98+
return h
99+
100+
def append(self, ll: 'LinkedList') -> None:
101+
self.tail.next = ll.head
102+
self.size += ll.size
103+
ll.head = None
104+
ll.size = 0
105+
106+
def reverse(self) -> None:
107+
"""
108+
Reverses this linked list in place
109+
:return:
110+
"""
111+
if self.head is None:
112+
return
113+
prev = self.head
114+
self.tail = prev
115+
curr = prev.next
116+
self.tail.next = None
117+
while curr is not None:
118+
old_next = curr.next
119+
curr.next = prev
120+
prev = curr
121+
curr = old_next
122+
self.head = prev
123+
124+
def __repr__(self):
125+
return self.__str__()
126+
127+
def __str__(self):
128+
if self.head is None:
129+
return '<empty>'
130+
ll = []
131+
n = self.head
132+
while n.next is not None:
133+
ll.append('{} -> '.format(n.data))
134+
n = n.next
135+
ll.append(str(n.data))
136+
return ''.join(ll)
137+
138+
def __eq__(self, other: object):
139+
if not isinstance(other, LinkedList):
140+
return NotImplemented
141+
a = self.head
142+
b = other.head
143+
while a is not None and b is not None:
144+
if a.data != b.data:
145+
return False
146+
# otherwise, advance both pointers
147+
a = a.next
148+
b = b.next
149+
return a is None and b is None
150+
151+
152+
def reverse_linked_list(ll: LinkedList) -> LinkedList:
153+
"""
154+
Takes in a linked list and returns a reversed copy
155+
:param ll: input linked list
156+
:return: reversed linked list
157+
"""
158+
output_ll = LinkedList()
159+
n = ll.head
160+
while n is not None:
161+
output_ll.append_to_head(n.data)
162+
n = n.next
163+
return output_ll
164+
165+
166+
def is_palindrome_constant_space(ll: LinkedList) -> bool:
167+
"""
168+
Same as the palindrome function below, but
169+
with constant space
170+
Runtime: O(n)
171+
Space Complexity: O(1)
172+
:param ll: an input linked list
173+
:return: true if ll is a palindrome, false otherwise
174+
"""
175+
# reverse half of the linked list
176+
temp_ll1 = LinkedList()
177+
temp_ll2 = LinkedList()
178+
n = ll.head
179+
for _ in range((ll.size + 1) // 2):
180+
temp_ll1.append_to_tail(n) # first half
181+
n = n.next
182+
while n is not None:
183+
temp_ll2.append_to_tail(n) # second half
184+
n = n.next
185+
# reverse second half
186+
temp_ll2.reverse()
187+
# compare temp_ll1 and temp_ll2
188+
n1 = temp_ll1.head
189+
n2 = temp_ll2.head
190+
for _ in range(temp_ll2.size):
191+
if n1.data != n2.data:
192+
temp_ll2.reverse()
193+
return False
194+
n1 = n1.next
195+
n2 = n2.next
196+
temp_ll2.reverse()
197+
return True
198+
199+
200+
def is_palindrome(ll: LinkedList) -> bool:
201+
"""
202+
Given a linked list, this function will check if the
203+
linked list is a palindrome.
204+
A palindrome is a word or phrase that is the same
205+
forwards and backwards.
206+
Since I will use integer nodes for the linked list,
207+
we will be checking if the sequence of numbers
208+
in a linked list is a palindrome.
209+
Runtime: O(n)
210+
Space Complexity: O(n)
211+
:param ll: an input linked list
212+
:return: true if ll is a palindrome, false otherwise
213+
"""
214+
return ll == reverse_linked_list(ll)
215+
216+
217+
class TestIsPalindrome(unittest.TestCase):
218+
219+
def setUp(self):
220+
self.test_cases = [
221+
(
222+
LinkedList(1, 2, 3, 4, 3, 2, 1),
223+
True
224+
),
225+
(
226+
LinkedList(1, 2, 3, 2, 1),
227+
True
228+
),
229+
(
230+
LinkedList(1, 2, 3, 4, 2, 1),
231+
False
232+
),
233+
(
234+
LinkedList(4, 5, 6, 7, 8, 9),
235+
False
236+
),
237+
(
238+
LinkedList(99, 0),
239+
False
240+
),
241+
(
242+
LinkedList(100, 100),
243+
True
244+
),
245+
(
246+
LinkedList(7),
247+
True
248+
),
249+
(
250+
LinkedList(),
251+
True
252+
)
253+
]
254+
255+
def test_is_palindrome(self):
256+
for ll, expected in self.test_cases:
257+
self.assertEqual(is_palindrome(ll), expected, msg=ll)
258+
259+
def test_is_palindrome_constant_space(self):
260+
for ll, expected in self.test_cases:
261+
ll_temp = LinkedList(*[ll.get_value_at(i) for i in range(ll.size)])
262+
self.assertEqual(is_palindrome_constant_space(ll), expected, msg=ll)
263+
self.assertEqual(ll_temp, ll)
264+
265+
266+
if __name__ == '__main__':
267+
unittest.main()

0 commit comments

Comments
 (0)