Tuesday, May 11, 2021

#1 2021-04-28 07:42:47 pm

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 872

Manipulating a list of lists

Is there some way to accomplish the following without the repeat loop? Thanks for the help.

Applescript:

use framework "Foundation"
use scripting additions

set theListOfLists to {{"a", 10}, {"b", 20}, {"c", 30}}

set theListOfLetters to {}
repeat with aList in theListOfLists
   set end of theListOfLetters to item 1 of aList
end repeat

set theLetter to text returned of (display dialog "Enter a letter" default answer "a, b, or c")

set theArray to current application's NSArray's arrayWithArray:theListOfLetters
set i to ((theArray's indexOfObject:theLetter) + 1)
set theNumber to item 2 of (item i of theListOfLists)

Last edited by peavine (2021-04-28 07:47:17 pm)


2018 Mac mini - macOS Catalina

Offline

 

#2 2021-04-28 10:51:02 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 6628

Re: Manipulating a list of lists

I don't believe there's any way around a repeat loop (ignoring third-party frameworks).


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/
latenightsw.com

Offline

 

#3 2021-04-29 12:26:42 am

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 872

Re: Manipulating a list of lists

Thanks Shane--I won't spend any more time looking at that option.

I ran some timing tests with a repeat loop using core AppleScript (enhanced with an implicit script object) with 3,000 items in the list of lists, and it only took 3 milliseconds. My approach from post 1 took over ten times longer.

Applescript:

repeat with i from 1 to (count my theListOfLists)
   if item 1 of (item i of my theListOfLists) = 3000 then
       set theCharacter to item 2 of (item i of my theListOfLists)
       exit repeat
   end if
end repeat


2018 Mac mini - macOS Catalina

Offline

 

#4 2021-04-29 04:02:48 am

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 5381

Re: Manipulating a list of lists

Hi peavine.

An array filter would do the job, I think.

Applescript:

use framework "Foundation"
use scripting additions

set theListOfLists to {{"a", 10}, {"b", 20}, {"c", 30}}
set theArrayofArrays to current application's NSArray's arrayWithArray:theListOfLists

set theLetter to text returned of (display dialog "Enter a letter" default answer "a, b, or c")

set filter to current application's NSPredicate's predicateWithFormat_("self[FIRST] == %@", theLetter) -- or "self[0] == %@"
set theNumber to ((theArrayofArrays's filteredArrayUsingPredicate:(filter)) as list)'s end's end

(* -- Or if there's a chance the user may enter a non-existent letter, change the last line to this:
set filteredList to (theArrayofArrays's filteredArrayUsingPredicate:(filter)) as list
if (filteredList is {}) then error "No sublists begin with '" & theLetter & "'"
set theNumber to filteredList's end's end
*)


NG

Offline

 

#5 2021-04-29 05:50:44 am

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 6628

Re: Manipulating a list of lists

Nice, Nigel cool


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/
latenightsw.com

Offline

 

#6 2021-04-29 09:26:07 am

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 872

Re: Manipulating a list of lists

Thanks Nigel. That works great.

BTW, I was a bit stumped at first by the code "end's end" but came to realize that it was equivalent to item -1 of item -1. Also, the ability to specify an item number after "self" will be quite useful in other situations when working with lists of lists.

Last edited by peavine (2021-04-30 10:03:00 am)


2018 Mac mini - macOS Catalina

Offline

 

#7 2021-04-30 01:43:36 pm

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 5381

Re: Manipulating a list of lists

peavine wrote:

Also, the ability to specify an item number after "self" will be quite useful in other situations when working with lists of lists.


Yeah. It's a shame we can't use negative indices too, but there is at least the expression "LAST", which of course is the opposite of the "FIRST" in my script above. The index number itself can be passed as a parameter if the occasion demands:

Applescript:

set filter to current application's NSPredicate's predicateWithFormat_("self[%@] == %@", 0, theLetter)


NG

Offline

 

#8 2021-05-01 07:31:39 am

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 872

Re: Manipulating a list of lists

Thanks Nigel. Just for myself and others new to ASObjC, I thought I'd write a simple script utilizing the stuff discussed in this thread. I made the script work without regard to case, although this can be changed by removing [c].

Applescript:

use framework "Foundation"
use scripting additions

set theList to {{"John", "Doe", "New York"}, {"John", "Smith", "Chicago"}, {"Jane", "Doe", "Los Angeles"}}

display dialog "Enter a first or last name." default answer "Doe" buttons {"Cancel", "First Name", "Last Name"} default button 3
set {findOption, findText} to {button returned, text returned} of result

if findOption = "First Name" then
   set findOption to 0
else
   set findOption to 1
end if

set theArray to current application's NSArray's arrayWithArray:theList
set thePredicate to current application's NSPredicate's predicateWithFormat_("self[%@] ==[c] %@", findOption, findText)
set matchingPersons to (theArray's filteredArrayUsingPredicate:(thePredicate)) as list

Last edited by peavine (2021-05-01 08:32:33 am)


2018 Mac mini - macOS Catalina

Offline

 

#9 2021-05-01 04:15:09 pm

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 872

Re: Manipulating a list of lists

I'm just finishing my work researching ASObjC and list of lists and had one final question, which I haven't been able to resolve. The following returns a list of every item of every list in a list of lists:

Applescript:

use framework "Foundation"

set theList to {{"a", 1}, {"b", 2}, {"c", 3}}
set theArray to current application's class "NSArray"'s arrayWithArray:theList
(theArray's valueForKeyPath:"@unionOfArrays.self") as list --> {"a", 1, "b", 2, "c", 3}

What I'd like it to do is return a list of the first item of every list in the list of lists: {"a", "b", "c"}. I spent a lot of time on this but just couldn't get it to work. Thanks.


2018 Mac mini - macOS Catalina

Offline

 

#10 2021-05-01 04:41:51 pm

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 5381

Re: Manipulating a list of lists

peavine wrote:

What I'd like it to do is return a list of the first item of every list in the list of lists: {"a", "b", "c"}.


I don't think that is possible using valueForKey: or valueForKeyPath:. In the current case, you could finish with a bit more AS:

Applescript:

use framework "Foundation"

set theList to {{"a", 1}, {"b", 2}, {"c", 3}}
set theArray to current application's class "NSArray"'s arrayWithArray:theList
((theArray's valueForKeyPath:"@unionOfArrays.self") as list)'s text --> {"a", "b", "c"}


NG

Offline

 

#11 2021-05-01 05:21:49 pm

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 872

Re: Manipulating a list of lists

Thanks Nigel. I kept trying different stuff like adding firstObject after "self" but nothing worked. I won't spend any more time on this.


2018 Mac mini - macOS Catalina

Offline

 

#12 2021-05-02 09:08:28 am

KniazidisR
Member
From:: Greece
Registered: 2019-03-03
Posts: 1797

Re: Manipulating a list of lists

peavine wrote:

Applescript:

use framework "Foundation"

set theList to {{"a", 1}, {"b", 2}, {"c", 3}}
set theArray to current application's class "NSArray"'s arrayWithArray:theList
(theArray's valueForKeyPath:"@unionOfArrays.self") as list --> {"a", 1, "b", 2, "c", 3}

What I'd like it to do is return a list of the first item of every list in the list of lists: {"a", "b", "c"}. I spent a lot of time on this but just couldn't get it to work. Thanks.


Peavine,

Using plain AppleScript's flattening handler you can perform this task 7 times faster than using ASObjC. I compared following script with Nigel's AsObjC script. Plain AppleScript will work with any type items and will return result as is, and not as text, as well:

Applescript:


set bigListOfLists to {}
repeat with i from 1 to 3000
   if i < 3000 then
       set theCharacter to "a"
   else
       set theCharacter to "b"
   end if
   set the end of bigListOfLists to {i, theCharacter}
end repeat

set theFirstItems to flatten(bigListOfLists) of me

on flatten(l)
   script o
       property fl : {}
       on flttn(l)
           script p
               property lol : l
           end script
           repeat with i from 1 to (count l)
               set v to item 1 of item i of p's lol -- THIS: retrieves every first item
               if (v's class is list) then
                   flttn(v)
               else
                   set end of my fl to v
               end if
           end repeat
       end flttn
   end script
   tell o
       flttn(l)
       return its fl
   end tell
end flatten

NOTE: regarding the first question of this topic: to flatten all items of list of lists, and not only first ones, you should  set v to item i of p's lol

Applescript:


set littleListOfLists to {{"a", 1}, {file, 2}, {list, 3}}

set theFirstItems to flatten(littleListOfLists) of me

on flatten(l)
   script o
       property fl : {}
       on flttn(l)
           script p
               property lol : l
           end script
           repeat with i from 1 to (count l)
               set v to item i of p's lol -- THIS
               if (v's class is list) then
                   flttn(v)
               else
                   set end of my fl to v
               end if
           end repeat
       end flttn
   end script
   tell o
       flttn(l)
       return its fl
   end tell
end flatten

Last edited by KniazidisR (2021-05-02 09:59:13 am)


Model: MacBook Pro
OS X: Catalina 10.15.4
Web Browser: Safari 14.1
Ram: 4 GB

Offline

 

#13 2021-05-02 10:21:06 am

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 872

Re: Manipulating a list of lists

KniazidisR wrote:

Using plain AppleScript's flattening handler you can perform this task 7 times faster than using ASObjC. I compared following script with Nigel's AsObjC script. Plain AppleScript will work with any type items and will return result as is, and not as text, as well:



Thanks KniazidisR. I always like to look at new and faster ways to do stuff.

I ran timing tests on Nigel's suggestion (edited to work with the modified list of lists), your suggestion, and a script-object-enhanced repeat loop, and the results were:

Nigel's suggestion - 0.059 second
KniazidisR's suggestion - 0.018 second
Enhanced repeat loop - 0.008 second

Just as a point of information, Nigel's suggestion was in response to my request for an ASObjC solution and wasn't presented as being particularly fast.

My test script (comment-out scripts not being tested):

Applescript:

use framework "Foundation"
use scripting additions

-- untimed code
set bigListOfLists to {}
repeat with i from 1 to 3000
   if i < 3000 then
       set theCharacter to "a"
   else
       set theCharacter to "b"
   end if
   set the end of bigListOfLists to {i, theCharacter}
end repeat

-- start time
set startTime to current application's CFAbsoluteTimeGetCurrent()

-- timed code --> 0.059 second
set theArray to current application's class "NSArray"'s arrayWithArray:bigListOfLists
set theFirstItems to ((theArray's valueForKeyPath:"@unionOfArrays.self") as list)'s integers

-- timed code --> 0.018 second
set theFirstItems to flatten(bigListOfLists) of me
on flatten(l)
   script o
       property fl : {}
       on flttn(l)
           script p
               property lol : l
           end script
           repeat with i from 1 to (count l)
               set v to item 1 of item i of p's lol -- THIS: retrieves every first item
               if (v's class is list) then
                   flttn(v)
               else
                   set end of my fl to v
               end if
           end repeat
       end flttn
   end script
   tell o
       flttn(l)
       return its fl
   end tell
end flatten

-- timed code --> 0.008 second
set theFirstItems to my {}
repeat with i from 1 to (count my bigListOfLists)
   set end of my theFirstItems to item 1 of (item i of my bigListOfLists)
end repeat

-- elapsed time
set elapsedTime to (current application's CFAbsoluteTimeGetCurrent()) - startTime
set nf to current application's NSNumberFormatter's new()
nf's setFormat:("0.000")
set elapsedTime to ((nf's stringFromNumber:elapsedTime) as text) & " seconds"

-- result
elapsedTime


2018 Mac mini - macOS Catalina

Offline

 

#14 2021-05-02 11:33:22 am

Fredrik71
Member
Registered: 2019-10-23
Posts: 707

Re: Manipulating a list of lists

Here is other version if the even number of a list item is what we want...

Applescript:

use framework "Foundation"

set theList to {{"a", 1}, {"b", 2}, {"c", 3}}
set theArray to current application's NSMutableArray's arrayWithArray:theList
set theList to {}
repeat with anItem in {0, 2, 4}
   set the end of theList to ((theArray's valueForKeyPath:"@unionOfArrays.self")'s objectAtIndex:anItem) as text
end repeat
return theList as list


if you are the expert, who will you call if its not your imagination.

Offline

 

#15 2021-05-02 12:54:30 pm

KniazidisR
Member
From:: Greece
Registered: 2019-03-03
Posts: 1797

Re: Manipulating a list of lists

peavine wrote:

Nigel's suggestion - 0.059 second
KniazidisR's suggestion - 0.018 second
Enhanced repeat loop - 0.008 second


Peavine,

I myself love simple and fast scripts. This time I did not notice that your original list is structured as a special case (2 levels) of nested lists. And flattening handler in my examples is designed to flatten a list of any nesting level. Therefore, in this case, you correctly used its simplified version. smile

Last edited by KniazidisR (2021-05-02 01:29:24 pm)


Model: MacBook Pro
OS X: Catalina 10.15.4
Web Browser: Safari 14.1
Ram: 4 GB

Offline

 

#16 2021-05-02 02:30:24 pm

KniazidisR
Member
From:: Greece
Registered: 2019-03-03
Posts: 1797

Re: Manipulating a list of lists

Fredrik71 wrote:

Here is other version if the even number of a list item is what we want...


I tried to fix some things of your script to get it faster (using my specifier to create implicit script objects for theArray and theList) and without hardcoding of array's indexes. I provide it here:

Applescript:


use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

set bigListOfLists to {}
repeat with i from 1 to 3000
   if i < 3000 then
       set theCharacter to "a"
   else
       set theCharacter to "b"
   end if
   set the end of bigListOfLists to {i, theCharacter}
end repeat

set theArray to ((current application's class "NSArray"'s arrayWithArray:bigListOfLists)'s valueForKeyPath:"@unionOfArrays.self") as list
set firstItems to my {}
repeat with i from 1 to (count theArray) - 1 by 2
   set end of my firstItems to item i of my theArray
end repeat
return firstItems

Times I tested was as follows:

Nigel Garvey -  0.375 seconds
Fredrik71 -----  0.308 seconds
KniazidisR ----  0.058 seconds
Peavine -------  0.031 seconds

Last edited by KniazidisR (2021-05-02 02:40:06 pm)


Model: MacBook Pro
OS X: Catalina 10.15.4
Web Browser: Safari 14.1
Ram: 4 GB

Offline

 

#17 2021-05-02 02:51:21 pm

Fredrik71
Member
Registered: 2019-10-23
Posts: 707

Re: Manipulating a list of lists

I have not use IndexSet before... but I got this to work without repeat loop.

Applescript:

use framework "Foundation"

set theList to {{"a", 1}, {"b", 2}, {"c", 3}}
set theArray to current application's class "NSArray"'s arrayWithArray:theList
set theIndex to current application's NSMutableIndexSet's alloc()'s init()
theIndex's addIndex:0
theIndex's addIndex:2
theIndex's addIndex:4
((theArray's valueForKeyPath:"@unionOfArrays.self")'s objectsAtIndexes:theIndex) as list


if you are the expert, who will you call if its not your imagination.

Offline

 

#18 2021-05-02 03:23:26 pm

KniazidisR
Member
From:: Greece
Registered: 2019-03-03
Posts: 1797

Re: Manipulating a list of lists

Fredrik71 wrote:

I have not use IndexSet before... but I got this to work without repeat loop.


Congratulations, Fredrik71,

You managed to create the slowest script (33.823 seconds) in this topic. sad  Because not only are you not bypassing the repeat loop, but you are adding new time-consuming instructions:

Applescript:


use framework "Foundation"

set bigListOfLists to {}
repeat with i from 1 to 3000
   if i < 3000 then
       set theCharacter to "a"
   else
       set theCharacter to "b"
   end if
   set the end of bigListOfLists to {i, theCharacter}
end repeat

-- start time
set startTime to current application's CFAbsoluteTimeGetCurrent()

set theArray to (current application's class "NSArray"'s arrayWithArray:bigListOfLists)'s valueForKeyPath:"@unionOfArrays.self"
set theIndex to current application's NSMutableIndexSet's alloc()'s init()
repeat with i from 0 to ((theArray's |count|()) - 1) by 2
   (theIndex's addIndex:i)
end repeat
(theArray's objectsAtIndexes:theIndex) as list

-- elapsed time
set elapsedTime to (current application's CFAbsoluteTimeGetCurrent()) - startTime
set nf to current application's NSNumberFormatter's new()
nf's setFormat:("0.000")
set elapsedTime to ((nf's stringFromNumber:elapsedTime) as text) & " seconds"

Last edited by KniazidisR (2021-05-02 03:36:07 pm)


Model: MacBook Pro
OS X: Catalina 10.15.4
Web Browser: Safari 14.1
Ram: 4 GB

Offline

 

#19 2021-05-02 03:43:12 pm

Fredrik71
Member
Registered: 2019-10-23
Posts: 707

Re: Manipulating a list of lists

Try this one...

@peavine ask for return a, b, c or the first object from the list in list and that is what I have done.

Applescript:

use framework "Foundation"

set theList to {{"a", 1}, {"b", 2}, {"c", 3}}
set theArray to current application's class "NSArray"'s arrayWithArray:theList
set theIndex to current application's NSMutableIndexSet's alloc()'s init()
repeat with anIndex in {0, 2, 4}
   (theIndex's addIndex:anIndex)
end repeat
((theArray's valueForKeyPath:"@unionOfArrays.self")'s objectsAtIndexes:theIndex) as list

Last edited by Fredrik71 (2021-05-02 03:56:06 pm)


if you are the expert, who will you call if its not your imagination.

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)