Reference Link List Length Python? - python

EDIT: The terminology I was looking for was is called Cycle Detection. Thanks to #dhke for referring that in the comments.
I'm trying to figure out a better way to process a list of indexes and what it's length is if a list has a loop in its reference. I have a function that works but it passes the next index value and counter. I've been trying to figure out a way to do it by just passing the list into the function. It always starts as index 0.
Given a list, each node in the list references the index of some other node. I'm trying to get the length of the linked list not the number of nodes in the list.
# This list would have a length of 4, index 0->1->3->6->0
four_links_list = [1,3,4,6,0,4,0]
two_links_list = [3,2,1,0]
def my_ideal_func(list):
# Some better way to iterate over the list and count
def my_func(list, index, counter):
# We're just starting out
if index == 0 and counter == 0:
counter += 1
return my_func(list, list[index], counter)
# Keep going through the list as long as we're not looping back around
elif index != 0:
counter += 1
return my_func(list, list[index], counter)
# Stop once we hit a node with an index reference of 0
else:
return counter

If you don't want extra data structures:
def tortoise_and_hare(l):
tort = 0
hare = 0
count = 0
while tort != hare or count == 0:
count += 1
if l[tort] == 0:
return count
tort = l[tort]
hare = l[hare]
hare = l[hare]
return -1
>>> tortoise_and_hare([1,3,4,6,0,4,0])
4
>>> tortoise_and_hare([3,2,1,0])
2
>>> tortoise_and_hare([1,2,3,1,2,1,2,1])
-1

You can use a set to keep track of all nodes you've visited (sets have very fast membership tests). And there is absolutely no need for recursion here, a loop will do nicely:
def my_ideal_func(list):
visited_nodes= set()
index= 0
length= 0
while True:
node= list[index]
if node in visited_nodes:
return length
visited_nodes.add(node)
length+= 1
index= list[index]

There's no need for recursion:
def link_len(l):
cnt, idx = 0, 0
while not cnt or idx:
cnt = cnt + 1
idx = l[idx]
return cnt
This assumes the list loops back to 0.

Related

How to duplicate every occurrence of specific item in list

A given list looks like this
list = [1,2,3,4,5,6,4,0]
I would like to duplicate every occurrence of 4
so the output looks like this:
[1,2,3,4,4,5,6,4,4,0]
I tried to do it like this:
def dup(list):
counter = 0
for value in list:
if value == 4:
print(counter,value)
list.insert(counter,4)
counter += 1
print(dup([1,2,3,4,5,6,4,0]))
I stumbled on a problem when I inserted int into a list index shifts with it. And I am stuck in an infinite loop of inserting 4
Also I would like the change to be done in place.
l = []
for n in nums:
l.append(num)
if n == 4:
l.append(4)
or inplace (you need to increment the counter twice)
i = 0
while i < len(nums):
if nums[i] == 4:
nums.insert(i, 4)
i += 1
i += 1

Eliminating nearest elements from a list or range

So what I have to do is find the smallest number among those that are in the list, and remove from the list both that number and whichever of its current immediate neighbors is larger. This function should repeat until the largest number in the original list gets eliminated and I have to return the number of times it took to achieve this goal. For example, [1,6,4,2,5,3] would remove element 1 and its current largest neighbor element which is 6, thus reaching the goal in 1 step. My code worked for a list but it did not work when I inputted a range in items. So could anybody help?
def elements(items):
max_value = max(items)
count = 0
while max_value in items:
n = len(items)
right_of_items = 0
left_of_items = 0
min_value = min(items)
min_value_index = items.index(min_value)
if (min_value_index -1 >= 0):
left_of_items = items[min_value_index-1]
if (min_value_index+1 < n):
right_of_items = items[min_value_index+1]
if left_of_items > right_of_items:
items.remove(left_of_items)
items.remove(min_value)
count += 1
else:
items.remove(right_of_items)
items.remove(min_value)
count += 1
return count
The problem with range is that it doesn't support access to a specific item.
So that, you either need to build a list from that range, or use more advanced algorithm which will be able to find the solution in a single pass.
As we won't know the smallest and largest items unless we're analysed the whole list, I guess, there's no such algorithm, that could solve in less than 2 passes, so consider just doing
items = list(items)
at the beginning of your function.
Also I would suggest to not to store the values of a smallest and largest items themselves. Better store their positions. So that you'll be able to access (and remove) neighbours much more effeciently. Like:
def find_largest_neighbour(lst, pos):
if pos <= 0:
return pos + 1
if pos + 1 >= len(lst):
return pos - 1
if lst[pos - 1] <= lst[pos + 1]:
return pos - 1
else:
return pos + 1
def find_min_and_max(lst):
min_pos = max_pos = 0
for i, v in enumerate(lst):
if v < lst[min_pos]:
min_pos = i
if v > lst[max_pos]:
max_pos = i
return min_pos, max_pos
def solve(lst):
number_of_iterations = 0
while True:
number_of_iterations += 1
min_pos, max_pos = find_min_and_max(lst)
max_pos_about = find_largest_neighbour(lst, min_pos)
if max_pos_about == max_pos:
# The largest item found about the smallest, we're done
break
else:
# Getting rid of the smallest item and its largest bro
del lst[min_pos]
del lst[max_pos_about]
# as we used index lookups above, we
# won't search through the list twice
return number_of_iterations

How to check elements in a list WITHOUT using for loops?

Apologies if the title of the question is phrased badly. I am currently trying to make a function that takes in a list of integers from 1 to n, where n is the length of the list. The function should return the first value that is repeated in the list. Duplicates are NOT always next to one another. If one or more integers is less than 1 or if it is not a list, the function should return -1. If there are no duplicates, return 0.
This is my current code:
def find_duplicates(ls):
if type(ls) != list:
return -1
non_dupe = []
i = 0
while i < len(ls):
if ls[i] < 1:
return -1
break
if ls.count(i) > 1:
return i
break
else:
non_dupe.append(i)
i += 1
if len(non_dupe) == len(ls):
return 0
While this code works for a majority of test cases, it doesn't seem to pass
print(find_duplicates([1, 2, 2, 0]))
as it returns 2 instead of the expected -1. I am relatively new to Python and I can't seem to be able to fix this error. I've tried searching for ways to counter this problem but I am not allowed to use for loops to check through a list. Any help is greatly appreciated.
EDIT: I am not allowed to use any of the following but anything else is accepted.
for loops
min() / max()
enumerate() / zip ()
sort()
negative indexing e.g ls[-1]
list slicing
Your code returns a duplicate prematurely; traversing the list, the function first finds 2 as a duplicate, return it, and halts the function immediately. But it has not seen the 0 at the end.
So, you need to let the function see the list all the way towards the end, looking for a negative number. If a negative number is found along the way, you can halt the function. If it does not see a negative number until the end, then let it return the duplicate value:
def find_duplicates(ls):
if not isinstance(ls, list): # check whether ls is a list
return -1
dup = 0
seen = [] # list of numbers seen so far
i = 0 # index
while i < len(ls):
if ls[i] < 1: # if a negative number is found, return -1
return -1
if ls[i] in seen and dup == 0:
dup = ls[i]
seen.append(ls[i])
i += 1
return dup
print(find_duplicates([1, 2, 2, 0])) # -1
print(find_duplicates([1, 1, 2, 2, 3])) # 1
Problem is beacause you are breaking while loop when find a duplicated. In that case, function is finding first the duplicated.
Try this:
def find_duplicates(ls):
if type(ls) is not list:
return -1
duplicated = 0
i = 0
while i < len(ls):
if ls[i] < 1:
return -1
if ls.count(ls[i]) > 1 and duplicated == 0
duplicated = ls[i]
i += 1
return duplicated
Your test case returns 2 because 2 stay at lower indexes comparing to 0.
I would suggest to sort the list before moving on:
def find_duplicates(ls):
if type(ls) != list:
return -1
sorted_list = ls.sorted() #Assign sorted `ls` to another variable, while keeping the order of `ls` intact
non_dupe = []
i = 0
while i < len(ls):
if ls[i] < 1:
return -1
break
if ls.count(i) > 1:
return i
break
else:
non_dupe.append(i)
i += 1
if len(non_dupe) == len(ls):
return 0
Another method I would recommend is using set - a built-in data type of Python. Maybe you should consider trying this approach later on when all test cases are passed. Have a look at this Tutorial for set usage: https://www.w3schools.com/python/python_sets.asp.
You were very close. Try this:
def find_duplicates(ls):
if type(ls) != list:
return -1
non_dupe = []
i = 0
while i < len(ls):
if ls[i] < 1:
return -1
elif ls[i] in non_dupe:
return ls[i]
else:
non_dupe.append(i)
i += 1
return 0
my_list = [1,2,2,0]
result = list(set(filter(lambda x: my_list.count(x) > 1 , my_list)))
# Result => [2]
I hope this solves your problem

Count does not work when using index to access list

I am trying to count the instances of "1" appearing in a list. When I used index to access the elements in the list and implement a counter, the total number of count is always 1.
However, when I used the normal "for" iterations, the count actually works. Could anyone enlighten me?
Here's a snippet of what DOES NOT work:
for entry in range(len(nums)):
count = counts = 0
if nums[entry] == 1:
count = count + 1
print(count)
Here's a snippet of what works:
for num in nums:
if num == 1:
counts = counts + 1
This is because you define the counter inside the for-loop and thus it gets reset to 0 in each iteration.
Just make sure to define the counter outside of the for loop
count = 0
for entry in range(len(nums)):
if nums[entry] == 1:
count = count + 1
print(count)

Minimum count to sort an array in Python by sending the element to the end

Here is the explanation of what I'm trying to say:-
Input:- 5 1 3 2 7
Output:- 3
Explanation:
In first move, we move 3 to the end. Our list becomes 5,1,2,7,3
In second move, we move 5 to the end. Our list becomes 1,2,7,3,5
In third move, we move 7 to the end. Our final list = 1,2,3,5,7
So, total moves are:- 3.
Here is what I tried to do, but failed.
a = [int(i) for i in input().split()]
count = 0
n = 0
while (n < len(a) - 1):
for i in range(0,n+1):
while (a[i] > a[i + 1]):
temp = a[i]
a.pop(i)
a.append(temp)
count += 1
n += 1
print(count, end='')
I'd like to request your assistance in helping in solving this question.
jdehesa's answer is basically right, but not optimal for cases, when there is more element of same value. Maybe more complex solution?
def min_moves(a):
c = 0
while(1):
tmp = None
for i in range(0, len(a)):
if a[i] != min(a[i:]) and (tmp is None or a[i] < a[tmp]):
tmp = i
if tmp is None:
return c
else:
a.append(a.pop(tmp))
c += 1
Edit:
Or if you don't need ordered list, there's much more easier solution just to count items that are out of order for the reason from jdehesa's solution :-D
def min_moves(a):
c = 0
for i in range(0, len(a)):
if a[i] != min(a[i:]):
c += 1
return c
Edit 2:
Or if you like jdehesa's answer more, small fix is to reduce lst to set, so it will get smallest index
sorted_index = {elem: i for i, elem in enumerate(sorted(set(lst)))}
I cannot comment yet.
I don't know if it can be done better, but I think the following algorithm gives the right answer:
def num_move_end_sort(lst):
# dict that maps each list element to its index in the sorted list
sorted_index = {elem: i for i, elem in enumerate(sorted(lst))}
moves = 0
for idx, elem in enumerate(lst):
if idx != sorted_index[elem] + moves:
moves += 1
return moves
print(num_move_end_sort([5, 1, 3, 2, 7]))
# 3
The idea is as follows. Each element of the list would have to be moved to the end at most once (it should be easy to see that a solution that moves the same element to the end more than once can be simplified). So each element in the list may or may not need to be moved once to the end. If an element does not need to be moved is because it ended up in the right position after all the moves. So, if an element is currently at position i and should end up in position j, then the element will not need to be moved if the number of previous elements that need to be moved, n, satisfies j == i + n (because, after those n moves, the element will indeed be at position j).
So in order to compute that, I sorted the list and took the indices of each element in the sorted list. Then you just count the number of elements that are not in the right position.
Note this algorithm does not tell you the actual sequence of steps you would need to take (the order in which the elements would have to be moved), only the count. The complexity is O(n·log(n)) (due to the sorting).
I think you can simplify your problem,
Counting elements that need to be pushed at the end is equivalent to counting the length of the elements that are not in sorted order.
l = [5, 1, 3, 2, 7]
sorted_l = sorted(l)
current_element = sorted_l[0]
current_index = 0
ans = 0
for element in l:
if current_element == element:
current_index += 1
if current_index < len(l):
current_element = sorted_l[current_index]
else:
ans += 1
print(ans)
Here the answer is 3

Categories

Resources