Slickgrid Filtering without Dataview - slickgrid

Is it possible to filter a Slickgrid without using the DataView?
In case it isn't possible, how should the data array be structured in order to display correctly?
I don't have a working example atm. Thanks
Later edit:
After doing some more homework, a filterable datagrid is all about getting matching indexes in a nested array... to get a live sorted result-set that gets updated with grid.setData(filterData);grid render; one should do the following
function intersect(a, b) // find an intersection of 2 arrays (google result on SO
{
var ai=0, bi=0;
var a = a.sort();
var b = b.sort();
var result = new Array();
while( ai < a.length && bi < b.length )
{
if (a[ai] < b[bi] ){ ai++; }
else if (a[ai] > b[bi] ){ bi++; }
else /* they're equal */
{
result.push(a[ai]);
ai++;
bi++;
}
}
return result;
}
// given results sets are arrays of indexes matching search criteria
a = [1,2,3,4];
b = [2,3,4,5];
c = [3,4,5,6];
d = [4,5,6,7];
// should reunite in a nested array
array = [a,b,c,d];
// check intersections for each array[k] and array[k+1]
k = array[0];
for (var i = 0; i<array.length-1; i++){
k = intersect(k,array[i+1]);
}
console.log(k) // returns 4
// k array is the index array that
// is used to build filterData[i] = data[j]
// depends if id is stored in data or in case
// of a database, it is stored in data
// tested in firebug
// thanks

Filter the underlying data array and call grid.setData(filteredData).

Related

For loop runs again after condition is met

I'm working on crating a function that adds data from numerous sheets in a Google Sheets document. The function creates production sheets that has a name and data slot as well as column titles for data that the function will paste into the sheet "Print". I am having an issue where a for loop seems to run an additional time even though the conditions are met as far as I can tell. Here is the code I have so far, its ugly but I'm just building this for myself.
function pasteToPrint(){
var ui = SpreadsheetApp.getUi()
var spreadsheet = SpreadsheetApp.getActive();
var sheetName;
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var lastCol;
var lastRow;
var a;
var x;
var y;
var z;
var array1 = [];
var array2 = [];
function getNextPage(){ // spreads subsequent data set to page break for printing
x = spreadsheet.getSheetByName("Print").getLastRow();
y = x/32;
y = Math.ceil(y);
z = 32*y;
return z;
}
for(var i = sheets.length-1; i > 4; i--){ // loops through the appropriate sheets and pushes to array1
lastCol = spreadsheet.getSheetByName(sheets[i].getName()).getLastColumn()+1;
lastRow = spreadsheet.getSheetByName(sheets[i].getName()).getLastRow()+1;
array1.push(spreadsheet.getSheetByName(sheets[i].getName()).getRange(1, 1, lastRow, lastCol).getValues());
}
for(var i = 0; i < array1.length; i++){
for(var j = 0; j < array1[i].length; j++){
// Looking for data that has a value grater than 0 at .slice(2,3)
if(array1[i][j].slice(2,3) > 0){ // Ref1
for(var k = 0; k < array1[i].length; k++){ // here I push that data to array2
if(array1[i][k].slice(2,3) > 0){ // ref 2
array2.push(array1[i][k]);
}
}
ui.alert(array2+" test 1"); // lets me know the content of array2
ui.alert(array2.length); // tells me the length just to be sure
for(var l = 0; l < array2.length; l++){ // decides where to paste the next data set
if(spreadsheet.getSheetByName("Print").getLastRow()>1){ // ref 3
a = getNextPage(); // returns the next 32nd line
}
else {
a = 1; // starts at row 1
}
// this next section is column formatting for the beginning of the production sheet
// ref 4
spreadsheet.getSheetByName("Print").getRange(a,1).setValue("Name:");
spreadsheet.getSheetByName("Print").getRange(a,3).setValue("Date:");
a++; // moves to the next row
spreadsheet.getSheetByName("Print").getRange(a,1).setValue("Product");
spreadsheet.getSheetByName("Print").getRange(a,2).setValue("Work Order");
spreadsheet.getSheetByName("Print").getRange(a,3).setValue("Planned Production");
spreadsheet.getSheetByName("Print").getRange(a,4).setValue("Product Produced");
spreadsheet.getSheetByName("Print").getRange(a,5).setValue("Steel Shortage");
spreadsheet.getSheetByName("Print").getRange(a,6).setValue("Notes");
a++ // moves to the next row
// ref 5
for(var m = 0; m < array2[l].length-1; m++){ // HERE IS THE PROBLEM LOOP
ui.alert(array2[m]+" test 2"); // I see this run an additional time after m = length
spreadsheet.getSheetByName("Print").getRange(a,1).setValue(array2[m][0]);
spreadsheet.getSheetByName("Print").getRange(a,2).setValue(array2[m][4]);
spreadsheet.getSheetByName("Print").getRange(a,3).setValue(array2[m][2]);
a++;
}
}
array2 = []; // array2 is emptied
break; // First if statement checking .slice(2,3) now has the function go to the next sheet
}
}
}
ui.alert("Function ended"); // just letting me know
};
imagine that there are numerous sheets in this document with data like the following except that there might be hundreds of rows after the column titles with different part numbers and so on
Material Req Planned Quantity MTs Planned Order
AI UPC300 1 1 86 12744851
The Plan Quantity column is what the .slice(2,3) is looking for. By default when I paste the original data into the sheets from my inventory system the value of Planned Quantity will be 0. After I have gone through the document and planed production, this means I have assigned a value to planned quantity greater than 0, I will run the function to paste the data to the print sheet. There could be hundred of items but I will only plan a portion of them, that's why this is handy. I can't share the full document I'm working with because it has sensitive data in it. If you create a google sheet with one sheet named print and a second sheet named whatever you want you can then paste the example data on rows 1 and 2 of the second sheet and you will see the error I get which is
TypeError: Cannot read property '0' of undefined (line 138, file "arrayTest")
You must also change for(var i = sheets.length-1; i > 4; i--) to for(var i = sheets.length-1; i > 1; i--), change the 4 to a 1. I have more code in my document but it is comprised of separate functions from this function.
I have added an image below that showcases how this would look before I utilize the function as well as how the Print sheet would look after the function has run. I had to screenshot this unfortunately so the data is condensed. Imagine that each section of data you see, green, blue, and white, are on their own sheets. For clarity the data in green would be contained in TestSheet1, the data in blue in TestSheet2, and the Print sheet would be totally blank before this function runs. Once I have entered my planned quantities in the Planned Quantity columns of TestSheet 1 and 2 I would then run the function. This would result in the data being pasted to the print page. I have added "ref" comments to the code to show where each step of the following explanation occurs. The function runs in this order: 1) The function will determine if a sheet has any values greater than zero in the Planned Quantity column, "ref 1". 2) The function would them loop through the sheet and push any row that met this criteria to array2, "ref 2". 3) The function will determine where to paste the data into the Print sheet based on data that may already by pated to the sheet, row 1, row 32, row 64, and so on, "ref 3". 4) the function will paste a header above the soon to be pasted data. This header include name, date, and column titles. 5) Once the header is placed the function will loop through array2 and paste is contents to the Print sheet, "ref 5". 6) the function will set array2 so that array2 = [] and will then repeat the process for the next sheet until all the sheets have been gone over and there is no more data to paste into Print, "ref 6".
The m loop is unnecessary. It's looping over current row(ls) columns. A mcve looks like this:
const array2d = [1, 2, 3].map(num => new Array(6).fill(num));//boilerplate to simulate array2
console.log({array2d});
for (var l = 0; l < array2d.length; l++) {
for (var m = 0; m < array2d[l].length - 1; m++) {
// HERE IS THE PROBLEM LOOP
// alert(array2d[m] + ' test 2'); // I see this run an additional time after m = length
console.log(array2d[m][0], array2d[m][2], array2d[m][4]);
}
}
Solution:
Remove the loop entirely.
a++; // moves to the next row
// for(var m = 0; m < array2[l].length-1; m++){ // HERE IS THE PROBLEM LOOP
// ui.alert(array2[m]+" test 2"); // I see this run an additional time after m = length
spreadsheet
.getSheetByName('Print')
.getRange(a, 1)
.setValue(array2[l][0]);
spreadsheet
.getSheetByName('Print')
.getRange(a, 2)
.setValue(array2[l][4]);
spreadsheet
.getSheetByName('Print')
.getRange(a, 3)
.setValue(array2[l][2]);
a++;
// }
}
Best practices:
Essential optimization

Increment Key, Decrement Key, Find Max Key, Find Min key in O(1) time

I was asked this question in the interview but could not solve it. Design a data structure which does the following
Inc(Key) -> Takes a key and increment its value by 1. If the key comes first time then make its value as 1.
Dec(Key) -> Takes a key and decrement its value by 1. It is given that its value is minimum 1.
Findmaxkey() -> Returns the key which has the maximum value corresponding to it. If there are multiple such keys then you can output any of them.
Findminkey() -> Returns the key which has the minimum value corresponding to it. If there are multiple such keys then you can output any of them.
You have to do all the operations in O(1) time.
Hint: The interviewer was asking me to use a dictionary(hashmap) with a doubly-linked list.
The data structure could be constructed as follows:
Store all keys that have the same count in a HashSet keys, and accompany that set with the value for count: let's call this pair of count and keys a "bucket".
For each count value for which there is at least a key, you'd have such a bucket. Put the buckets in a doubly linked list bucketList, and keep them ordered by count.
Also create a HashMap bucketsByKey that maps a key to the bucket where that key is currently stored (the key is listed in the bucket's keys set)
The FindMinKey operation is then simple: get the first bucket from bucketList, grab a key from it's keys set (no matter which), and return it. Similar for FindMaxKey.
The Inc(key) operation would perform the following steps:
Get the bucket corresponding to key from bucketsByKey
If that bucket exists, delete the key from it's keys set.
If that set happens to become empty, remove the bucket from bucketList
If the next bucket in bucketList has a count that is one more, then add the key to it's set, and update bucketsByKey so that it refers to this bucket for this key.
If the next bucket in bucketList has a different count (or there are no more buckets), then create a new bucket with the right count and key and insert it just before the earlier found bucket in bucketList -- or if no next bucket was found, just add the new one at the end.
If in step 2 there was no bucket found for this key, then assume its count was 0, and take the first bucket from bucketList and use it as the "next bucket" from step 4 onwards.
The process for Dec(key) is similar except that when the count is found to be already 1, nothing happens.
Here is an interactive snippet in JavaScript which you can run here. It uses the native Map for the HashMap, the native Set for the HashSet, and implements a doubly linked list as a circular one, where the start/end is marked by a "sentinel" node (without data).
You can press the Inc/Dec buttons for a key of your choice and monitor the output of FindMinKey and FindMaxKey, as well as a simple view on the data structure.
class Bucket {
constructor(count) {
this.keys = new Set; // keys in this hashset all have the same count:
this.count = count; // will never change. It's the unique key identifying this bucket
this.next = this; // next bucket in a doubly linked, circular list
this.prev = this; // previous bucket in the list
}
delete() { // detach this bucket from the list it is in
this.next.prev = this.prev;
this.prev.next = this.next;
this.next = this;
this.prev = this;
}
insertBefore(node) { // inject `this` into the list that `node` is in, right before it
this.next = node;
this.prev = node.prev;
this.prev.next = this;
this.next.prev = this;
}
* nextBuckets() { // iterate all following buckets until the "sentinel" bucket is encountered
for (let bucket = this.next; bucket.count; bucket = bucket.next) {
yield bucket;
}
}
}
class MinMaxMap {
constructor() {
this.bucketsByKey = new Map; // hashmap of key -> bucket
this.bucketList = new Bucket(0); // a sentinel node of a circular doubly linked list of buckets
}
inc(key) {
this.add(key, 1);
}
dec(key) {
this.add(key, -1);
}
add(key, one) {
let nextBucket, count = 1;
let bucket = this.bucketsByKey.get(key);
if (bucket === undefined) {
nextBucket = this.bucketList.next;
} else {
count = bucket.count + one;
if (count < 1) return;
bucket.keys.delete(key);
nextBucket = one === 1 ? bucket.next : bucket.prev;
if (bucket.keys.size === 0) bucket.delete(); // remove from its list
}
if (nextBucket.count !== count) {
bucket = new Bucket(count);
bucket.insertBefore(one === 1 ? nextBucket : nextBucket.next);
} else {
bucket = nextBucket;
}
bucket.keys.add(key);
this.bucketsByKey.set(key, bucket);
}
findMaxKey() {
if (this.bucketList.prev.count === 0) return null; // the list is empty
return this.bucketList.prev.keys.values().next().value; // get any key from first bucket
}
findMinKey() {
if (this.bucketList.next.count === 0) return null; // the list is empty
return this.bucketList.next.keys.values().next().value; // get any key from last bucket
}
toString() {
return JSON.stringify(Array.from(this.bucketList.nextBuckets(), ({count, keys}) => [count, ...keys]))
}
}
// I/O handling
let inpKey = document.querySelector("input");
let [btnInc, btnDec] = document.querySelectorAll("button");
let [outData, outMin, outMax] = document.querySelectorAll("span");
let minMaxMap = new MinMaxMap;
btnInc.addEventListener("click", function () {
minMaxMap.inc(inpKey.value);
refresh();
});
btnDec.addEventListener("click", function () {
minMaxMap.dec(inpKey.value);
refresh();
});
function refresh() {
outData.textContent = minMaxMap.toString();
outMin.textContent = minMaxMap.findMinKey();
outMax.textContent = minMaxMap.findMaxKey();
}
key: <input> <button>Inc</button> <button>Dec</button><br>
data structure (linked list): <span></span><br>
findMinKey = <span></span><br>
findMaxKey = <span></span>
Here is my answer, still I'm not sure that I haven't broken any of the circumstances that your interviewer had in mind.
We will keep a LinkedList where each element has the key and values it's corresponding to, and a pointer to its previous and next element and is always sorted by values. We store a pointer for every key, where it is placed in the LinkedList. Furthermore, for every new number that we see, we add two elements which are supposed to view the start and end element of each number and we will store a pointer to them. Since we are adding these extra elements at most two for each operation, it's still of O(1).
now for every operation (say increment), we can find where the element corresponding to this key is placed in the LinkedList using a dictionary (assuming dictionaries work in time complexity of O(1)) now, we find the last element in the LinkedList which has the same value (we can do it using the element corresponding to the end of that value and come one element backwards) and swap these two's pointers (it's only a simple swap, and this swap does not affect other elements) next we swap this element with it's next one for two times so that it falls in the segment of the next number (we may need to add that number as well), the last things to keep track of, is the value of minimum and maximum which has to be updated if the element which is changing is either the current minimum or maximum and there is no number with the same value (the start and end elements for that value are consecutive in the LinkedList)
Still, I think this approach can be improved.
The key is the problem only asks for dec(1) or inc(1). Therefore, the algorithm only needs to move a block forward or backward. That's a strong prior and gives a lot of information.
My tested code:
template <typename K, uint32_t N>
struct DumbStructure {
private:
const int head_ = 0, tail_ = N - 1;
std::unordered_map<K, int> dic_;
int l_[N], r_[N], min_ = -1, max_ = -1;
std::unordered_set<K> keys_[N];
void NewKey(const K &key) {
if (min_ < 0) {
// nothing on the list
l_[1] = head_;
r_[1] = tail_;
r_[head_] = 1;
l_[tail_] = 1;
min_ = max_ = 1;
} else if (min_ == 1) {
} else {
// min_ > 1
l_[1] = head_;
r_[1] = min_;
r_[head_] = 1;
l_[min_] = 1;
min_ = 1;
}
keys_[1].insert(key);
}
void MoveKey(const K &key, int from_value, int to_value) {
int prev_from_value = l_[from_value];
int succ_from_value = r_[from_value];
if (keys_[from_value].size() >= 2) {
} else {
r_[prev_from_value] = succ_from_value;
l_[succ_from_value] = prev_from_value;
if (min_ == from_value) min_ = succ_from_value;
if (max_ == from_value) max_ = prev_from_value;
}
keys_[from_value].erase(key);
if (keys_[to_value].size() >= 1) {
} else {
if (to_value > from_value) {
// move forward
l_[to_value] =
keys_[from_value].size() > 0 ? from_value : prev_from_value;
r_[to_value] = succ_from_value;
r_[l_[to_value]] = to_value;
l_[r_[to_value]] = to_value;
} else {
// move backward
l_[to_value] = prev_from_value;
r_[to_value] =
keys_[from_value].size() > 0 ? from_value : succ_from_value;
r_[l_[to_value]] = to_value;
l_[r_[to_value]] = to_value;
}
}
keys_[to_value].insert(key);
min_ = std::min(min_, to_value);
max_ = std::max(max_, to_value);
}
public:
DumbStructure() {
l_[head_] = -1;
r_[head_] = tail_;
l_[tail_] = head_;
r_[tail_] = -1;
}
void Inc(const K &key) {
if (dic_.count(key) == 0) {
dic_[key] = 1;
NewKey(key);
} else {
MoveKey(key, dic_[key], dic_[key] + 1);
dic_[key] += 1;
}
}
void Dec(const K &key) {
if (dic_.count(key) == 0 || dic_[key] == 1) {
// invalid
return;
} else {
MoveKey(key, dic_[key], dic_[key] - 1);
dic_[key] -= 1;
}
}
K GetMaxKey() const { return *keys_[max_].begin(); }
K GetMinKey() const { return *keys_[min_].begin(); }
};

CouchDB null value when sort descending

I have CouchDB view that gives me a correct value in natural order and a null when sorted descending, here are the Futon screenshots:
Natural order
Descending order
Here is the view code:
"informe_precios": {
"map": "function(doc){if(doc.doc_type=='precio'){emit([doc.comprador,doc.fecha.substr(0,4),doc.fecha.substr(5,2)],{precio:doc.precio,litros:doc.litros});}}",
"reduce": "function(keys, values, rereduce){var importe= 0; var totallitros = 0;for(var i = 0; i < values.length; i++) {importe += values[i].precio*values[i].litros;totallitros += values[i].litros;}return importe/totallitros;}"
}
I need it descending because I want to get 12 last values.
TIA
Diego
You're always assuming that your reduce function is called with the output of your map function, ie. you're not handling the rereduce situation.
In the rereduce your values will be the importe/totallitros values from previous reduce calls.
Your reduce function is getting a "price per liter" average for each month, so because it's an average there's no way for your rereduce function to actually handle that data because for the multiple values coming in there's no way to know their weight in the average.
So, you'll need to change your function to return the count so that you can use that to weight the average in the rereduce function (we're also using the inbuilt sum function to make things simpler):
function(keys, values, rereduce) {
if (rereduce) {
var length = sum(values.map(function(v){return v[1]}));
var avg = sum(values.map(function(v){
return v[0] * (v[1] / length)
}));
return [avg, length];
}
else {
var importe= 0;
var totallitros = 0;
for( var i = 0; i < values.length; i++) {
importe += values[i].precio * values[i].litros;
totallitros += values[i].litros;
}
return [ importe/totallitros, values.length ];
}
}
The final result you'll see in your view here will be an array, so you'll always need to pick out the first element of that in your client code.

Generate a two dimensional array via LINQ

I am trying to create a matrix of doubles, representing a correlation between entities.
Here's how I'm doing it via LINQ
double[][] correlationsRaw = (from e in entitiesInOrder
select
(from f in entitiesInOrder
select correlations.GetCorrelation(e, f)
).ToArray()).ToArray();
That works fine.
But what I want is a two dimensional array (double[,]), not a jagged array.
Obviously, I can write some nested for loop to convert one into the other.
But is there some elegant LINQ trick I can use here?
I don't think there's an easy way of directly returning a multidimensional array from a Linq query... however you could create a function that takes a jagged array and return a multidimensional array :
public T[,] JaggedToMultidimensional<T>(T[][] jaggedArray)
{
int rows = jaggedArray.Length;
int cols = jaggedArray.Max(subArray => subArray.Length);
T[,] array = new T[rows, cols];
for(int i = 0; i < rows; i++)
{
cols = jaggedArray[i].Length;
for(int j = 0; j < cols; j++)
{
array[i, j] = jaggedArray[i][j];
}
}
return array;
}
By the way, it could be an extension method, allowing you to use it in a Linq query...

Algorithm to find first index where strings are different?

I've got a collection of strings, and I need to know the first index where they all differ. I can think of two ways to do this: (the following pseudo code is just off the top of my head and may be heavily bug-laden)
First Way:
var minLength = [go through all strings finding min length];
var set = new set()
for(i=0;i<minlength;i++)
{
for(str in strings)
{
var substring = str.substring(0,i);
if(set.contains(substring))
break; // not all different yet, increment i
set.add(substring)
}
set.clear(); // prepare for next length of substring
}
This strikes me as gross because of the use of a set data structure where it seems like one should not be needed.
Second Way:
var minLength = [go through all strings finding min length];
strings.sort();
for(i=0;i<minlength;i++)
{
boolean done = true;
char last = null;
for(str in strings)
{
char c = str[i];
if(c == last)
{
// not all different yet, increment i
done = false;
break;
}
last = c;
}
if(done)
return i;
}
But it annoys me that I have to run the sort first, because the sorting algorithm, by its very nature, has access to the information that I'm looking for.
Surely there must be a more efficient way than what I have listed above. Eventually I'd like to abstract it out to any type of array, but that will be trivial and it's simpler to think of it as a string problem.
Any help?
**UPDATE: I apparently didn't explain myself very well. If my strings are ["apple", "banana", "cucumber", "banking"], I want the function to return 3, because there were two strings ("banana" and "banking") that matched through index 0, 1, and 2, so 3 is the first index where they are all unique.
As Daniel mentioned below, a better way to state my needs is that: "I want to find index i where calling substring(0,i) on all my strings will result in all unique values."**
This is untested, but here's my attempt. (I may be making it more complicated than I have to, but I think it's a different way to look at it.)
The basic idea is to compile groups of items that match at the first element, then find the max unique index for each group, checking elements at each successive index.
int FirstUniqueIndex<T>(IEnumerable<IEnumerable<T>> myArrayCollection)
{
//just an overload so you don't have to specify index 0 all the time
return FirstUniqueIndex(myArrayCollection, 0);
}
int FirstUniqueIndex<T>(IEnumerable<IEnumerable<T>> myArrayCollection, int StartIndex)
{
/* Group the current collection by the element at StartIndex, and
* return a collection of these groups. Additionally, we're only interested
* in the groups with more than one element, so only get those.*/
var groupsWithMatches = from var item in myArrayCollection //for each item in the collection (called "item")
where item.Length > StartIndex //that are long enough
group by item[StartIndex] into g //group them by the element at StartIndex, and call the group "g"
where g.Skip(1).Any() //only want groups with more than one element
select g; //add the group to the collection
/* Now "groupsWithMatches" is an enumeration of groups of inner matches of
* your original arrays. Let's process them... */
if(groupsWithMatches.Any())
//some matches were found - check the next index for each group
//(get the maximum unique index of all the matched groups)
return groupsWithMatches.Max(group => FirstUniqueIndex(group, StartIndex + 1));
else
//no matches found, all unique at this index
return StartIndex;
}
And for the non-LINQ version of the above (I'll change it to use a List collection, but any collection will do). I'll even remove the lambda. Again untested, so try not to aim sharp implements in my direction.
int FirstUniqueIndex<T>(List<List<T>> myArrayCollection, int StartIndex)
{
/* Group the current collection by the element at StartIndex, and
* return a collection of these groups. Additionally, we're only interested
* in the groups with more than one element, so only get those.*/
Dictionary<T, List<List<T>>> groupsWithMatches = new Dictionary<T, List<List<T>>>();
//group all the items by the element at StartIndex
foreach(var item in myArrayCollection)
{
if(item.Count > StartIndex)
{
List<List<T>> group;
if(!groups.TryGetValue(item[StartIndex], out group))
{
//new group, so make it first
group = new List<List<T>>();
groups.Add(item[StartIndex], group);
}
group.Add(Item);
}
}
/* Now "groups" is an enumeration of groups of inner matches of
* your original arrays. Let's get the groups with more than one item. */
List<List<List<T>>> groupsWithMatches = new List<List<List<T>>>(groups.Count);
foreach(List<List<T> group in groupsWithMatches)
{
if(group.Count > 1)
groupsWithMatches.Add(group);
}
if(groupsWithMatches.Count > 0)
{
//some matches were found - check the next index for each group
//(get the maximum unique index of all the matched groups)
int max = -1;
foreach(List<List<T>> group in groupsWithMatches)
{
int index = FirstUniqueIndex(group, StartIndex + 1);
max = index > max ? index : max;
}
return max;
}
else
{
//no matches found, all unique at this index
return StartIndex;
}
}
have you looked at a Patricia trie? (Java implementation available on google code)
Build the trie, then traverse the data structure to find the maximum string position of all the internal nodes (black dots in the function above).
This seems like it should be an O(n) operation. I'm not sure whether your set implementation is O(n) or not -- it "smells" like O(n2) but I'm not sure.
Use the set as you proposed, that's exactly the right thing to do.
You should be able to do this without sorting, and with only looking at each character in each string once in the worst case.
here is a ruby script that puts the index to the console:
mystrings = ["apple", "banana", "cucumber", "banking"]
minlength = getMinLengthString(mystrings) #not defined here
char_set = {}
(0..minlength).each do |char_index|
char_set[mystrings[0][char_index].chr] = 1
(1..mystrings.length).each do |string_index|
comparing_char = mystrings[string_index][char_index].chr
break if char_set[comparing_char]
if string_index == (mystrings.length - 1) then
puts string_index
exit
else
char_set[comparing_char] = 1
end
end
char_set.clear
end
puts minlength
the result is 3.
Here's the same general snippet in C#, if it is more legible for you:
string[] mystrings = { "apple", "banana", "cucumber", "banking" };
//defined elsewhere...
int minlength = GetMinStringLengthFromStringArray(mystrings);
Dictionary<char, int> charSet = new Dictionary<char, int>();
for (int char_index = 0; char_index < minlength; char_index++)
{
charSet.Add(mystrings[0][char_index], 1);
for (int string_index = 1; string_index < mystrings.Length; string_index++)
{
char comparing_char = mystrings[string_index][char_index];
if (charSet.ContainsKey(comparing_char))
{
break;
}
else
{
if (string_index == mystrings.Length - 1)
{
Console.Out.WriteLine("Index is: " + string_index.ToString());
return;
}
else
{
charSet.Add(comparing_char, 1);
}
}
}
charSet.Clear();
}
Console.Out.WriteLine("Index is: " + minlength.ToString());
int i = 0;
while(true)
{
Set set = new Set();
for(int j = 0; j < strings.length; j++)
{
if(i >= strings[j].length) return i;
String chr = strings[j].charAt(i);
if(set.hasElement(chr))
break;
else
set.addElement(chr);
}
if(set.size() == strings.length)
return i;
i++;
}
Gotta check preconditions first.
EDIT: Using a set now. Changed langauge.
Here's my solution in Python:
words = ["apple", "banana", "cucumber", "banking"]
for i in range(len(min(words))):
d = defaultdict(int)
for word in words:
d[word[i]] += 1
if max(d.values()) == 1:
return i
I didn't write in anything to handle the case where no minimum index is found by the time you reach the end of the shortest word, but I'm sure you get the idea.

Resources