My suggestion would be to slurp the large list into a hash set, then use that to match items from the small list.
A hash set is a structure that stores elements in an indexable memory structure, like an array, where the position of the element is equal to some hash value calculated using the object. That means that looking for a value in the hashset is a relatively fast operation; calculate the hash of the object you're looking for, go to that index, and check the actual objects stored there, which for a good implementation will be a very small number (hashset implementations have to strike a balance between the size of the hash and therefore the number of first-dimension elements, and the number of collisions and therefore the average number of items in each element).
Ideally, hashsets approach a constant lookup time (specifically it's O(log2^HN) where H is the bitsize of the hash function, so for all N < 2^H it's effectively constant), so overall, your matching algorithm would approach linear complexity. Two major downsides are first that unless you have access to a built-in efficient implementation (Java's HashMap is built on this structure, as is .NET's Dictionary class), you have to roll your own which is quite a bit of code, and second, hashsets are real memory hogs because there's virtually guaranteed to be a lot of empty spaces in the array unless your implementation varies its hash function based on expected or actual capacity (which could, if naively done, involve re-hashing every element several times as the first dimension is extended to limit growth in the second dimension).
Answer from KeithS on Stack ExchangeMy suggestion would be to slurp the large list into a hash set, then use that to match items from the small list.
A hash set is a structure that stores elements in an indexable memory structure, like an array, where the position of the element is equal to some hash value calculated using the object. That means that looking for a value in the hashset is a relatively fast operation; calculate the hash of the object you're looking for, go to that index, and check the actual objects stored there, which for a good implementation will be a very small number (hashset implementations have to strike a balance between the size of the hash and therefore the number of first-dimension elements, and the number of collisions and therefore the average number of items in each element).
Ideally, hashsets approach a constant lookup time (specifically it's O(log2^HN) where H is the bitsize of the hash function, so for all N < 2^H it's effectively constant), so overall, your matching algorithm would approach linear complexity. Two major downsides are first that unless you have access to a built-in efficient implementation (Java's HashMap is built on this structure, as is .NET's Dictionary class), you have to roll your own which is quite a bit of code, and second, hashsets are real memory hogs because there's virtually guaranteed to be a lot of empty spaces in the array unless your implementation varies its hash function based on expected or actual capacity (which could, if naively done, involve re-hashing every element several times as the first dimension is extended to limit growth in the second dimension).
Sort both lists with an efficient sorting algorithm (or ensure that the lists are "pre-sorted" by whoever/whatever created them).
Then, if the first name in both lists is the same you've found a match, otherwise discard whichever name is "earlier"; and do that until one of the lists are empty.
Some crude pseudo-code:
do {
status = compare(shortList[i], longList[j]);
if(status == EQUAL) {
// Found match!
i++;
j++;
} else if(status == EARLIER) {
// No match, discard first entry in short list
i++;
} else {
// No match, discard first entry in long list
j++;
}
} while( (i < shortListEntries) && (j < longListEntries) );
You could use itertools.combinations.
import itertools
for a, b in itertools.combinations(items, 2):
sim[a][b] = sim[b][a] = calc_sim(a, b)
If you need just a general algorithm to reduce number iterations, you can limit the range of the inner loop
for i, A in enumerate(items):
for B in items[:i]:
sim[A][B] = calc_sim(A, B)
But if you are looking for Python-specific optimization, it would be much better to use numpy vectorization. For example, if calc_sim(a, b) computes squared difference between a and b, then it can be vectorized the following way:
import numpy as np
list = [1, 2, 3]
array = np.array(list)
sim = np.square(array[:,np.newaxis] - array)
[[0 1 4]
[1 0 1]
[4 1 0]]
java - Algorithm for comparing two lists - Stack Overflow
Python algorithm to compare two sorted lists and count how many elements are the same - Stack Overflow
Algorithm to compare two large sets - Computational Science Stack Exchange
Is it faster to find matches between two lists by iterating over the 2nd list over every element in the 1st or by "remembering" stuff from past iterations?
How do I compare two lists online?
Can I compare Excel lists with this comparison tool?
Can I compare lists containing special characters or formatted text?
If i had a list and i wanted to check the whole list for a match in another list but i don't care which match it is can i somehow optimize this with math? Like compare the whole list at once to it by doing some math operations on it? Is there a solution?
If ordering doesn't matter and all you care about is whether the elements were added or removed, you might want to consider changing your data structures and using Set rather than List. The Set type is specifically designed to determine whether or not elements exist and to do so efficiently, at the cost that you no longer remember the order of the elements.
For example, using HashSet, you could do the following:
Set<T> oldElems = new HashSet<T>(originalList);
Set<T> newElems = new HashSet<T>(currentList);
for (T obj : oldElems) {
if (!newElems.contains(obj)) {
/* ... this object was removed ... */
}
}
for (T obj : newElems) {
if (!oldElems.contains(obj)) {
/* ... this object was added ... */
}
}
Hope this helps!
You can do it with one loop, by continuously maintaining the 3-way result list. If you can override equals() to depend on ids, that's fine. If not, check the code under update.
Here's the code, see the explanation below:
List<T> origList = ...;
List<T> newList = ...;
List<T> addedList = new ArrayList<T>();
List<T> deletedList = new ArrayList<T>();
List<T> changedList = new ArrayList<T>();
deletedList.addAll(origList);
for(T t : newList) {
int origIndex = deletedList.indexOf(t);
if (origIndex < 0) {
addedList.add(t);
} else {
T origT = deletedList.remove(origIndex);
if(t.compareTo(origT) != 0) {
changedList.add(t);
}
}
}
Note that I presumed that equals() will check the id, and compareTo() will check all other fields.
Explanation:
You remove all elements from deletedList that were also present in newList, so the result is the deleted items.
You add all new elements to the addedList that were not present in the original list.
If both are present and the objects differ, they'll go to the changedList.
If both are present and the objects are the same then we don't add it anywhere.
Notes:
If new objects have an ID of 0, they will not be present in origList (since we suppose they have been already created).
When I last time implemented this, I created a separate method to compare the objects field-by-field, so I could separate the comparison logic from standard Java methods (actually it was also declared on an interface)
I created this with Lists, but actually you can use it with any type of Collection. Using with List will preserve the original order.
Update:
Try overriding your equals this way (I skipped the typecheck and the casting):
public boolean equals(T other) {
if (this.id == 0) {
return this == other;
}
return this.id == other.id;
}
The not-yet-created instances are equal to themselves only. The already-created ones are checked by id.
Okay if I read your question correctly you want to find common elements in two sorted list of equals lengths and return the number of common elements. I am a little confused by the use of merge here.
Anyways if that is what you want your algorithm to do. Since it is already sorted we can simply iterate over both the lists and find the common elements in linear time.
Algorithm:
- Let
iandjbe the indices ofa1anda2respectively initialized to0 - If
a1[i] < a2[j]we know that thea1[i]does not exist ina2asiandjpoint to the smallest element in the respective arrays. So we moveiforward. - Same with
a2[j] < a1[i]. - If
a1[i] == a2[j]then we have found a common element and we advance bothiandjby 1 and continue till the end of either of the array.
the code
def find_common(a1, a2):
list_len = len(a1)
a3 = []
i = j = 0
while i < list_len and j < list_len:
if a1[i] < a2[j]:
i += 1
elif a2[j] < a1[i]:
j += 1
else:
a3.append(a1[i])
i +=1
j +=1
return a3
a = [2, 9, 15, 27, 36, 40]
b = [9, 11, 15, 23, 36, 44]
print(find_common(a, b))
Using hash table you can solve this in linear time.
You could store one list in a hash table using python dictionary where the key would be the element (in this case an integer) and the value would be the number of occurrences of the element. Running time: O(n)
Then iterate through the other list and do a hash table lookup for each element. Keep a variable for counting the common values. Running time: O(n).
To avoid counting duplicates, as you iterate check if the previous element is the same, in which case move to the next element. You will need an extra variable to keep track of the previous element.
Python sets are open-addressing hashtables with a prime probe. In other words every set value can be looked up quickly because it was inserted in such a way (hashed) to differentiate it and find it (not order it) from other values. So operations in python like:
a = set([1,2,3,4])
b = set([3,4,5,6]) #etc..
a&b
#gives you {3, 4}
a|b
#gives you {1,2,3,4,5,6}
a^b
#gives you {1,2,5,6}
a-b
#gives you {1, 2}
If you're using C++, there is support in the standard library for this (std::set_difference)
http://www.cplusplus.com/reference/algorithm/set_difference/
That documentation even includes equivalent "pseudocode" (really, just more C++) that you can use to port the idea to other languages. The algorithm is close to the "merging" part of mergesort. Note that std::set_difference operates upon sorted ranges, not std::sets (this is a good thing - means sorted std::vector's are adequate).