Spy
Spy

Reputation: 149

Applescript compare items in the same array

I have this list

set myList to {{1, "Bob"}, {1.5, "Jane"}, {3, "Joe"}, {1, "Jack"}}

repeat with a in myList
    --something like that a+
    if item 1 of a is equal to item 1 of a+ then
        --create list 
    end if
end repeat

Is there a way to compare the items based on their first property? I want to add to a list all the items that their first item is the same

Upvotes: 0

Views: 469

Answers (1)

CJK
CJK

Reputation: 6092

Here's one way to do it, which also happens to be a very efficient way if you have very large lists:

to filterItems from (L as list) ¬
    into |L*| as list : null ¬
    thru filter as handler
    local L, |L*|, filter
    
    if |L*| = null then set |L*| to {}
    
    script filteredItems
        property array : L
        property fn : filter
        property list : |L*|
    end script
    
    tell the filteredItems to repeat with x in its array
        if fn(x) = true then set ¬
            end of its list ¬
            to x's contents
    end repeat
    
    return the list of filteredItems
end filterItems

This function (handler), as its name suggests, takes a list and filters it according to some criterion. It then optionally stores the filtered items in a new list that you can pass into the handler to be populated; or you can allow the handler to return the filtered list as its result, then assign that to a new variable.

The filter is provided by means of another handler, which you define. This is a function that acts upon each element of the list, returning true or false depending on whether or not it passes some test that you define.

In your specific case, the test you want it to pass is that first element of each item is a specific value, e.g. 1. That way, you end up with a filtered list containing items with matching first elements.

The handler for this filter looks like this:

on firstItemEquals1(x)
    item 1 of x = 1
end firstItemEquals1

A simple, one-line test to make sure the first element of each item is 1.

Then, when you run the following line:

filterItems from myList thru firstItemEquals1

the result is this:

{{1, "Bob"}, {1, "Jack"}}

We could, instead, define another filter handler that picks out only those items where its first element is an odd number; or where its second element begins with the letter "J":

on firstItemIsOdd(x)
    (item 1 of x) mod 2 = 1
end firstItemIsOdd


on secondItemStartsWithJ(x)
    item 2 of x starts with "J"
end secondItemStartsWithJ

Then:

filterItems from myList thru firstItemIsOdd
    --> {{1, "Bob"}, {3, "Joe"}, {1, "Jack"}}


filterItems from myList thru secondItemStartsWithJ
    --> {{1.5, "Jane"}, {3, "Joe"}, {1, "Jack"}}

EDIT: Handling Multiple Similar Cases

what if I have a lot of duplicate first values? I have to create handlers for each one of them ?

No, but we do have to make a slight adjustment to the filterItems handler, and create another handler that works alongside it that will allow us to use nested functions to perform laborious tasks repeatedly without having to then create additional handlers for each task.

to filterItems from (L as list) ¬
    into |L*| as list : null ¬
    thru filter
    local L, |L*|, filter
    
    if |L*| = null then set |L*| to {}
    
    script itemsToBeFiltered
        property array : L
    end script
    script filteredItems
        property list : |L*|
    end script
    
    repeat with x in the array of itemsToBeFiltered
        tell wrapper(filter) to if fn(x) = true ¬
            then set end of filteredItems's list ¬
            to contents of x
    end repeat
    
    return the list of filteredItems
end filterItems

The main change of significance is the introduction of the tell wrapper(filter) line. We're introducing a helper function to nest our filter handlers inside a script object. The wrapper function looks like this:

on wrapper(function)
    if function's class = script then return function
    
    script
        property fn : function
    end script
end wrapper

Now, we can define filter handlers that look like this:

on firstItemEquals(N)
    script
        on fn(x)
            item 1 of x = N
        end fn
    end script
end firstItemEquals

This new version of the handler from before allows us to choose the value with which to compare the first element of each item in your list. This way, we don't need to write separate handlers; we can just do this:

filterItems from myList thru firstItemEquals(1)
    --> {{1, "Bob"}, {1, "Jack"}}

and then this:

filterItems from myList thru firstItemEquals(3)
    --> {{3, "Joe"}}

Upvotes: 1

Related Questions