Cook me a character meal

Chef Avillez is about to cook us some really nice meal. He is just waiting for us to give him some ingredients and to request a meal.

Given a list of ingredients (strings matching /[a-z]+/) and a requested meal (string matching /[a-z][a-z ]*/) output the integer amount of meals Chef Avillez can make.

Algorithm

Each letter ([a-z]) in the ingredient list contributes with one character for the soon-to-be-cooked meals. Each portion of our requested meal costs as many of each character as there are in the request string, excluding spaces.

For example, if our requested meal is "bacon" and the ingredients are "banana" and "coconut", the output is 1 because in "bananacoconut" there is only one b, and for each portion of "bacon" we need one "b".

Input

A list of ingredients in any reasonable format, like

• a list of strings
• a list of lists of characters
• a (whatever-you-please)-separated list of ingredients, in a single string

and a requested meal in any reasonable format, like

• a string
• a list of characters

Output

A non-negative integer representing the amount of meals that can be cooked.

Test cases

A Python reference implementation is available.

['spam', 'spam', 'spam', 'spam', 'bacon', 'eggs', 'eggs', 'bacon', 'spam'], 'beans' -> 2
['bacon', 'bacon', 'bacon', 'bacon', 'bacon'], 'bacon' -> 5
['banana', 'coconut'], 'bacon' -> 1
['acon', 'bcon', 'baon', 'bacn', 'baco'], 'bacon' -> 4
['tomato', 'oregano', 'pizza', 'chocolate'], 'bacon' -> 0
['strawberries', 'figs', 'chocolate', 'sardines'], 'cod fish' -> 1
['these', 'are', 'some', 'random', 'words', 'wow'], 'or' -> 3
['some', 'more', 'delicious', 'ingredients', 'here'], 'bolognese' -> 0
['some', 'delicious', 'ingredients', 'here', 'are', 'bliss'], 'bolognese' -> 1
['some', 'bountiful', 'bagful', 'of', 'ingredients', 'here', 'are', 'bliss'], 'bolognese' -> 1

• May we take input as a list of characters? (Ignoring that there are separate words, since they serve no purpose.) – Robin Ryder Mar 24 at 19:21
• The point here is that the ingredient list does represent a list, so I don't know if I'm comfortable allowing that. – RGS Mar 24 at 19:25
• @RGS Imagine a comma-separated list without commas. (in fact, a comma-separated list will also often work similarly to a normal list of characters) Is it intended that in the examples, no meal uses the same character twice? – my pronoun is monicareinstate Mar 24 at 19:31
• Suggest something like ['some', 'bountiful', 'bagful', 'of', 'ingredients', 'here', 'are', 'bliss'] bolognese -> 1 as a test case, since it makes the duplicated letter the limiting factor, and includes an incomplete portion for a recipe (3 os total, 2 needed for a recipe). – Xcali Mar 24 at 20:28
• @OlivierGrégoire yes it does, the meal regex has a space next to the character range a-z! – RGS Mar 25 at 13:01

Python 2, 957170 55 bytes

lambda i,m:min(i.count(c)/m.count(c)for c in m if" "<c)


Try it online!

Input: Ingredients i as a comma-separated string, and a meal m as a string.
Output: Max number of meals that can be made.

How: Divides the frequency of each character in the ingredient by its corresponding character in the meal, then takes the minimum.

Ruby, 55 50 bytes

Meal (m) is a list of characters, and Ingredients (i) is a single string with ingredients separated by spaces.

-5 bytes from Dingus.

->m,i{(m-[' ']).map{|c|i.count(c)/m.count(c)}.min}


Try it online!

• Do you need .uniq? – Dingus Mar 25 at 3:24
• @Dingus you are correct; I don't. – Value Ink Mar 25 at 15:30

J, 2927 26 bytes

<./@(=/<.@%&(+/)]=/]);@cut


Try it online!

-2 bytes thanks to Bubbler

-1 byte thanks to FrownyFrog

Inspired by ValueInk's ruby answer -- be sure to upvote him.

Both args are strings. Meal is right arg. Ingredients are left arg and taken as space separated string.

Consider the example:

'banana coconut' f 'ba con'


-.&' ' removes the spaces from the right arg:

'banana coconut' <./@(=/<.@%&(+/)]=/]) 'bacon'


Now the main verb is a fork whose tines are =/ and ]=/]. ] is the right arg so that the right tine runs as 'bacon' =/ 'bacon':

1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 1


And the left tine becomes 'banana coconut' =/ 'bacon':

1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 1
1 0 0 0 0
0 1 0 0 0
0 0 0 0 1
0 1 0 0 0
0 0 0 0 1
0 1 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 1
0 0 0 0 0
0 0 0 0 0


Now take the rowwise sum &(+/) of each, which results in:

1 1 1 1 1  NB. right tine
1 3 2 2 3  NB. left tine


In the right tine (meal), the number at index i is the count of meal letter i within meal (all 1 in this example because the letters are unique).

In the left tine (ingredients), the number at index i is the count of meal letter i within ingredients.

We divide those elementwise 1 3 2 2 3 % 1 1 1 1 1 = 1 3 2 2 3, rounding down <.@ each element to handle fractional amounts (not relevant in this example).

Finally we take the min <./@ of the whole result, which in this case is 1. This reflects the constraint of having a single b in our ingredients, limiting the number of meals we can make to 1.

• Fails at ingredients bb bb, meal b b. I think you need to filter out the spaces from the meal (or both args) first. – Bubbler Mar 24 at 23:08
• @Bubbler Thanks. Fixed. It's almost double APL now. See any way to improve? – Jonah Mar 25 at 0:16
• 27, also fixing possibly fractional outputs on bbb bb / b b. – Bubbler Mar 25 at 0:44
• ;@cut to remove spaces – FrownyFrog 12 hours ago
• @FrownyFrog Good one, thanks. – Jonah 11 hours ago

Java (JDK), 94 bytes

a->s->{var z=new int[91];for(var c:a)z[c]++;for(;;z[0]++)for(var c:s)if(z[c]--<1)return z[0];}


Try it online!

Both inputs are uppercase letters to save a byte. If not allowed, please tell me, I'll fix it and add the byte.

Credits

• Kevin Cruijssen for tidying up the inputs
• @KevinCruijssen Yeah, I'm lazy this morning. But since you did the job, I incuded it, thanks ;-) – Olivier Grégoire Mar 25 at 11:13
• And taking the input as uppercase is allowed. The JavaScript answers does the same after it was asked in the comments to OP. – Kevin Cruijssen Mar 25 at 11:14

JavaScript (ES6),  59 ... 51  50 bytes

Takes input as (ingredients)(meal), where ingredients is the list of ingredients as a comma-separated string and meal is a list of characters. All names are expected in upper case.

s=>g=m=>m.every(c=>s<(s=s.replace(c))|++c)&&1+g(m)


Try it online!

Commented

s =>                   // s = list of ingredients
g = m =>             // g is a recursive function taking m[] = meal,
// as a list of characters
m.every(c =>       // for each character c in m[]:
s < (            //   test whether s is less than ...
s =            //     ... the updated value of s where ...
s.replace(c) //       ... the 1st occurrence of c is replaced with 'undefined'
| ++c            //   force a truthy result if c is a space
) &&               // end of every(); if successful:
1 + g(m)         //   increment the final result and do a recursive call

• If it cannot make the recipe, that returns false. That doesn't count as an integer, does it? – Xcali Mar 24 at 20:35
• @Xcali As per Meta consensus, if it quacks like a number, it's a number. – Arnauld Mar 24 at 20:38
• And yet, empty string, which quacks like a number in many languages, isn't allowed for zero. I think we need to get consistent, but that's a discussion for meta, not here. – Xcali Mar 24 at 20:41
• Now with some lovely SPAM! – RGS Mar 25 at 20:31
• I think many answers will probably fail with such input, RGS will no doubt guarantee at least one non-space character. – Jonathan Allan Mar 25 at 21:30

APL (Dyalog Extended), 15 bytes

⌊⌂dab⍛(⌊/⍧÷⊣⍧⊣)


Try it online!

A dyadic train which takes the meal as its left arg and space-separated ingredients as right arg. (Comma-separated ingredients should work equally well.)

How it works

⌊⌂dab⍛(⌊/⍧÷⊣⍧⊣)  ⍝ Left: meal, Right: ingredients
⌂dab⍛(       )  ⍝ Remove all spaces from the meal
⍧       ⍝ Counts of each char of meal in the ingredients
÷      ⍝ Divided by
⊣⍧⊣   ⍝ Counts of each char of meal in the meal
⌊/        ⍝ Minimum
⌊                ⍝ Floor (the result of division might be fractional)


Without the space-handling requirement, the code would be 9 bytes:

⌊/⍤⌊⍧÷⊣⍧⊣


Try it online!

05AB1E, 10 9 bytes

-1 byte thanks to Kevin Cruijssen

Jsθáδ¢÷ß


Takes the ingredients as a list of strings, and the meal as a list of characters.

J                  # join each input
sθ                # get the last input (meal)
á               # keep only letters
δ¢             # double-vectorized count occurences
# dump to the stack
÷           # integer division
ß          # minimum


C# (Visual C# Interactive Compiler), 5463 60 bytes

s=>t=>t.Min(x=>x>32?s?.Count(c=>c==x)/t.Count(c=>c==x):null)


Min can calculate the minimum selectively if int? objects are used. To obtain such objects, I use the ?. operator: s will never be null, but it casts int to int? for 1 byte anyway.

Try it online!

Retina, 48 bytes



%O.
L$\G((.)\2*)(?=.*¶.*?(\1)+)?$#3
N
1G


Try it online! Link includes test suite. Takes input as dish on the first line and space-separated ingredients on the second line but the test suite uses a more convenient comma separator. Explanation:





Delete spaces in the dish and ingredients.

%O.


Separately sort the letters in the dish and ingredients.

L$\G((.)\2*)(?=.*¶.*?(\1)+)?$#3


For each distinct letter in the dish, count the number of times its appearance in the dish divides into its appearance in the ingredients.

N


Sort the counts.

1G


Take the minimum.

Perl 5-nlF, 5447 39 bytes

Shoutout to @Grimmy for helping me fix an issue with no net gain of bytes

$_=<>;$j++while s/$F[$j%@F]//x;say$j/@F  Try it online! First line of input is the recipe; second line contains the ingredients (doesn't matter how or if they're separated). • What’s the point of $#j when you don’t use @j? Change both $#j to $j for -2. – Grimmy Mar 24 at 22:43
• @Grimmy because I need it to start at -1 so that the first ++ results in it evaluating to 0. Otherwise, I'd need to subtract 1 off of it before the final division. That would add back the two bytes you're gaining. – Xcali Mar 24 at 22:45
• You’re right, it’s only -1, not -2. – Grimmy Mar 24 at 22:48
• Also your code doesn’t handle spaces correctly. This can be fixed by adding /x. – Grimmy Mar 24 at 22:51
• @mypronounismonicareinstate I don't see the problem there. Why isn't 1 the correct answer? – Xcali Mar 25 at 5:52

Pyth, 16 bytes

hSmL//hQd/eQdsce


Try it online!

Explanation

hSmL//hQd/eQdsce(Q)
(Q)  : Implicit evaluated input
e     : Get last element of input
c      : Split string at spaces
s       : Concatenate split strings
L                 : Lambda with argument named d
Q             : Evaluated input
h              : Get first element of input
/  d            : Count occurrences of d in first element of input
Q         : Evaluated input
e          : Get last element of input
/  d        : Count occurrences of d in last element of input
/                : Divided occurrences of d in first element of input by occurrences of d in last element of input
m                  : Map the lambda over last element of input
S                   : Sort the result of the map
h                    : Get the first element from result of sort


C (gcc), 135 133 bytes

Expects ingredients and request as command line arguments (the last one is the request). The return value of the program is the result.

l[128],n;char*a;main(c,v)char**v;{for(++v;c---2;)for(a=*v++;*a;++l[*a++]);for(a=*v,n=l[*a];*a;++a)n=*a-32&&l[*a]<n?l[*a]:n;return n;}


Python 3, 117 $$\\cdots\$$ 103 70 bytes

Saved a whopping 33 bytes thanks to Surculose Sputum!!!

f=lambda l,m:all(e in l and[l.remove(e)]for e in m if' '<e)and-~f(l,m)


Try it online!

• @RGS Don't understand why or how you edited my answer but you did it just after I updated my answer. – Noodle9 Mar 24 at 20:00
• I didn't mean to be rude, sorry, I just updated the TIO link you gave because I had to add 2 more test cases to the challenge and I didn't want to bother you with that ^^' – RGS Mar 24 at 20:01
• @RGS Ah, ok I'll add them myself. Maybe a comment with a TIO link wouldn't be so intrusive. – Noodle9 Mar 24 at 20:03
• You are right, that is a much better alternative -.- Thanks for being understanding. – RGS Mar 24 at 20:04
• @Noodle9 I think taking all ingredients as just one string/list will save some bytes (which seems to be allowed, by looking the other answers here). Also, recursion might get rid of the outer while loop. – Surculose Sputum Mar 24 at 20:24

Japt, 17 bytes

kS £V¬èX zU¬èXÃrm


Try it

JavaScript (V8), 136 bytes

(a,b)=>{b=b.filter(a=>" "!==a),c=-1,d=!1;do c++,d=!0,b.forEach(b=>-1==(i=a.indexOf(b))?d=!1:a.splice(i,1)),c=d?c:c--;while(d);return c};


Input:

• Ingredients: array of characters

• Meal: array of characters

Original commented code (a=ingredients, b=meal, c=meals, d=yes):

f = (ingredients, meal) => { // es6 arrow function syntax
meal = meal.filter(i => i !== ' '); // delete all the spaces
meals = -1 // set number of meals to -1, since we'll be adding one later on
yes = false // yes is whether there are any meals left to make
do { // do...while instead of while so it runs at least once
meals++; // increment meals
yes = true; // yes there is a meal to make
meal.forEach(v => { // es6 arrow function for each character of the meal
return (i = ingredients.indexOf(v)) == -1 ? // ternary operator, set i to index of character in ingredients, then check if its -1
yes = false // if it is we can't find the character, so we can't make a meal
: ingredients.splice(i, 1) // we take out the letter from the ingredients list
});
meals = // assign to meals
yes ? // ternary operator, check if we made a meal
meals : meals-- // if we didn't make a meal then decrement meals
} while (yes) // repeat if we made a meal
return meals; // return the number of meals
}


Methods mentioned:

Try it online!

Erlang (escript), 81 bytes

To fix the bug I had to switch to filtering.

f(I,M)->lists:min([length([X||X<-I,X==C])div length([X||X<-M,X==C])||C<-M,32<C]).


Try it online!

Explanation

f(I,M)->       % Function with operands I and M
lists:min(     % Find the minimum of this list.
[length(       % Find the length of:
[X||X<-I,X==C] % I items only containing C
)div           % Integer-divided by
length(        % the length of
[X||X<-M,X==C] % M items only containing C
)||C<-M,       %Ｗｈｅｒｅ　ｔｈｅ　ｉｔｅｍ　ｉｓ　ｔａｋｅｎ　ｆｒｏｍ　Ｍ
32<C]          % and the current item is larger than the space
).

• Unless I'm doing something wrong with the input (I don't know Erlang), test case "test,test","tt" outputs 1 instead of 2. – Kevin Cruijssen Mar 25 at 10:28
• @KevinCruijssen Is there anything else I need to fix? – petStorm Mar 25 at 10:47
• Not sure, I haven't tried all test cases on your answer. I was simply checking if all existing answers would work for that test case I mentioned, as well as the one mentioned by Xcali in the comment of the challenge description, and simply noticed yours didn't for the "test,test","tt". :) – Kevin Cruijssen Mar 25 at 10:50

MS SQL Server 2017, 300 bytes

CREATE FUNCTION F(@ NVARCHAR(MAX),@R NVARCHAR(MAX))RETURNS
TABLE RETURN WITH A AS(SELECT LEFT(@R,1)C,STUFF(@R,1,1,'')R
UNION ALL SELECT LEFT(R,1),STUFF(R,1,1,'')FROM A
WHERE R!=''),B AS(SELECT(LEN(@)-LEN(REPLACE(@,C,'')))/COUNT(*)OVER(PARTITION BY C)R
FROM A WHERE C LIKE'[A-Z]')SELECT MIN(R)R FROM B;


Try it on db<>fiddle.

Charcoal, 15 bytes

Ｉ⌊ＥΦη№βι÷№θι№ηι


Try it online! Link is to verbose version of code. Takes input as a space-separated list of ingredients on the first line and the dish on the second line. Explanation:

    η           Second input (dish)
Φ            Filter over characters
№          Count of
ι        Current character
β         In lowercase alphabet
Ｅ             Map over characters
№      Count of
ι    Current character
θ     In ingredients
÷       Integer divide by
№   Count of
ι Current character
η  In dish
⌊              Take the minimum
Ｉ               Cast to string
Implicitly print